Skip to content

Instantly share code, notes, and snippets.

@MrCoder
Created March 10, 2026 00:31
Show Gist options
  • Select an option

  • Save MrCoder/01bfb0b426b55935ee4360129ca145e7 to your computer and use it in GitHub Desktop.

Select an option

Save MrCoder/01bfb0b426b55935ee4360129ca145e7 to your computer and use it in GitHub Desktop.
Claude Code /diagram skill — generate ZenUML & Mermaid diagrams with optional image rendering via ZenUML web renderer

Mermaid Gotchas

LLMs generally know Mermaid syntax well. This file covers only common mistakes and non-obvious behavior.

Special Characters in Labels

Node IDs can't have spaces. Labels with special chars need quotes:

flowchart TD
    A["Label with: colons"]
    B["Label with (parens)"]
Loading

Forgetting end

Every alt, opt, loop, par, critical, and subgraph block needs end:

sequenceDiagram
    alt Success
        A->>B: OK
    else Failure
        A->>C: Error
    end
Loading

ER Diagram Cardinality

The symbols are non-obvious and easy to mix up:

||--||   Exactly one to exactly one
||--o{   One to zero-or-more
||--|{   One to one-or-more
}o--o{   Zero-or-more to zero-or-more

The o means "zero" (optional), | means "one" (required), { means "many".

Class Diagram Generic Types

Use ~T~ not <T> — angle brackets break the parser:

classDiagram
    class List~T~ {
        +add(T item)
        +get(int index) T
    }
Loading

Flowchart Direction

TD and TB are identical (top-down). LR works better for sequential/timeline flows.

Sequence Diagram Activation Shorthand

+ and - on arrows control activation bars:

sequenceDiagram
    A->>+B: Request (activates B)
    B-->>-A: Response (deactivates B)
Loading

Theme Configuration

Goes BEFORE the diagram type declaration:

%%{init: {'theme':'forest'}}%%
flowchart TD
    A --> B
Loading
name description allowed-tools
diagram
Generate diagrams as ZenUML or Mermaid DSL, saved to markdown files, with optional image rendering. Use this skill whenever the user asks to create, generate, or visualize any diagram - sequence diagrams, flowcharts, class diagrams, ER diagrams, state diagrams, or architecture diagrams. Triggers on: 'diagram', 'sequence diagram', 'flowchart', 'class diagram', 'ER diagram', 'visualize', 'draw a diagram', 'show me the flow', 'create a diagram from code', 'render diagram', 'diagram screenshot', or any request to turn code/text/ideas into a visual diagram. Also triggers when user says /diagram.
Read
Glob
Grep
Write
Bash
mcp:playwright:browser_navigate
mcp:playwright:browser_snapshot
mcp:playwright:browser_take_screenshot
mcp:playwright:browser_wait_for
mcp:playwright:browser_evaluate
mcp:playwright:browser_close

Diagram Skill

Generate diagrams using ZenUML (sequence diagrams) or Mermaid (everything else), saved as markdown files. Optionally render to PNG via the ZenUML web renderer.

DSL Selection

Diagram Type DSL Why
Sequence diagram ZenUML Natural nesting for activation bars, cleaner return semantics
Flowchart, class, ER, state, other Mermaid Broad coverage, rich notation

Respect the user's explicit DSL choice if they specify one.

Workflow

  1. Understand - What to diagram? From code, text, or data?
  2. If from code - Glob to find files, Read to examine, Grep to trace relationships
  3. Choose DSL per table above
  4. Read gotchas - references/zenuml-syntax.md or references/mermaid-syntax.md in this skill directory
  5. Generate DSL and write to .md file
  6. Render image (optional) - see "Image Rendering" section below
  7. Tell the user the file path (and image path / renderer URL if rendered)

Output Format

Write a .md file in the current working directory. Descriptive filename (e.g., auth-flow.md).

CRITICAL: Both ZenUML and Mermaid use the ```mermaid code fence. ZenUML adds zenuml as the first line inside the fence.

ZenUML:

# [Title]

[1-2 sentence description]

```mermaid
zenuml
[ZenUML DSL]
```

Mermaid:

# [Title]

[1-2 sentence description]

```mermaid
[Mermaid DSL]
```

Image Rendering

For ZenUML diagrams, you can render to a PNG image using the ZenUML web renderer.

Renderer URL

https://zenuml-web-renderer.zenuml.workers.dev/renderer?code={URL_ENCODED_ZENUML_DSL}

The DSL must be URL-encoded (e.g., spaces → %20, newlines → %0A). Use Bash with python3 -c or node -e to encode:

# Encode ZenUML DSL to URL
node -e "console.log(encodeURIComponent(require('fs').readFileSync('/dev/stdin','utf8')))" <<< 'A -> B.method() { return result }'

Rendering with Playwright

When the user asks for an image/screenshot/PNG, or when visual verification is needed:

  1. Construct the URL — URL-encode the ZenUML DSL and build the renderer URL
  2. Navigate — Use browser_navigate to open the renderer URL
  3. Wait for render — Use browser_wait_for or a short delay for the diagram to render
  4. Screenshot — Use browser_take_screenshot to capture the diagram as PNG
  5. Save — Save with a descriptive filename matching the .md file (e.g., auth-flow.png)

When to render images

  • Always render when the user says "render", "screenshot", "image", "PNG", or "show me"
  • Offer to render after generating any ZenUML diagram — mention the renderer URL
  • Skip rendering if the user only wants DSL/markdown output

Key ZenUML Gotchas

These are the things LLMs get wrong — read references/zenuml-syntax.md for details:

  • Participant names with spaces MUST be quoted: "Auth Service" not Auth Service
  • Async vs sync: A -> B: text (async, no activation) vs A -> B.method() { } (sync, activation bar)
  • return vs @return: return replies to caller, @return C: text sends to different participant
  • if conditions: single word, quoted string, or expression — NOT if (valid and approved)
  • Message length: under 20 chars, use comments for details

Quality

  • 15-20 nodes max per diagram
  • Clear, concise labels
  • Comments for non-obvious parts
  • Follow codebase naming conventions

ZenUML Gotchas

LLMs generally know ZenUML syntax. This file covers only the tricky parts that cause errors.

Code Fence (CRITICAL)

ZenUML uses the mermaid fence with a zenuml directive — NOT a zenuml fence:

```mermaid
zenuml
A -> B.method() {
  return result
}
```

Participant Names with Spaces

MUST use double quotes. This is the #1 source of parse errors:

// WRONG - breaks the parser
Client -> Auth Service.validate()

// RIGHT
Client -> "Auth Service".validate()

Async vs Sync — the Distinction Matters

// Async: colon separator, no activation bar
A -> B: fire and forget

// Sync: dot + method, creates activation bar with nesting
A -> B.process() {
  return result
}

Sync Messages with Spaces in Description

// Single word — no quotes needed
A -> B.process()

// Multiple words — MUST quote
A -> B."validate request"()

return vs @return

A -> B.process() {
  return result       // replies to A (the caller)
  @return C: notify   // sends to C (different participant)
}

return without a target always replies to the immediate caller. Use @return only for messages to a different participant.

if Condition Rules

// RIGHT
if (valid)
if ("user is admin")
if (count > 0)

// WRONG - multi-word without quotes
if (valid and approved)

Message Length

Keep under 20 chars. Use a comment for context:

// HTML response with embedded ZenUML content
User -> Page: HTML with DSL

Coloring

Color comment goes on the line BEFORE the message:

// (red) Critical path
A -> B.process()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment