feat(baoyu-diagram): add SVG-to-PNG @2x conversion script and consolidate references
This commit is contained in:
parent
25b8a7f73d
commit
39792f4360
|
|
@ -18,6 +18,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pptxgenjs": "^4.0.1"
|
||||
"pptxgenjs": "^4.0.1",
|
||||
"sharp": "^0.34.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,538 +1,247 @@
|
|||
---
|
||||
name: baoyu-diagram
|
||||
description: Generates publication-ready SVG diagrams from source material — flowcharts, sequence/protocol diagrams, structural/architecture diagrams, and illustrative intuition diagrams — by writing real SVG code directly following a cohesive design system. Analyzes input material to recommend diagram type(s), splitting strategy, and optional overview diagram, then generates after one-time confirmation. Use whenever the user asks to "draw a flowchart", "draw a sequence diagram", "show the OAuth / TCP / auth protocol", "make an architecture diagram", "explain how X works visually", "draw a diagram for this", "画流程图", "画时序图", "画架构图", "画示意图", "画图", or wants clean, embeddable vector diagrams for articles, WeChat posts, slides, or docs. Output is one or more self-contained .svg files that render correctly in light and dark mode anywhere they are embedded.
|
||||
version: 1.2.0
|
||||
metadata:
|
||||
openclaw:
|
||||
homepage: https://github.com/JimLiu/baoyu-skills#baoyu-diagram
|
||||
description: Create professional, dark-themed SVG diagrams of any type — architecture diagrams, flowcharts, sequence diagrams, structural diagrams, mind maps, timelines, illustrative/conceptual diagrams, and more. Use this skill whenever the user asks for any kind of technical or conceptual diagram, visualization of a system, process flow, data flow, component relationship, network topology, decision tree, org chart, state machine, or any visual representation of structure/logic/process. Also trigger when the user says "画个图" "画一个架构图" "diagram" "flowchart" "sequence diagram" "draw me a ..." or uploads content and asks to visualize it. Output is always a standalone .svg file.
|
||||
---
|
||||
|
||||
# Diagram Generator
|
||||
|
||||
Write **real SVG code** directly, following a consistent design system, the output is self-contained `.svg` files (embedded styles, auto dark-mode), editable by humans, scales to any size without quality loss, and embeds cleanly into articles, WeChat posts, slide decks, Notion, and markdown.
|
||||
Create professional SVG diagrams across multiple diagram types. All output is a single self-contained `.svg` file with embedded styles and fonts.
|
||||
|
||||
When given source material (topic descriptions, documents, technical specs, pasted content), the skill analyzes what diagrams would best convey the material, recommends diagram type(s) and whether the content should be split into multiple focused diagrams, confirms the plan once, then generates all diagrams.
|
||||
## Supported Diagram Types
|
||||
|
||||
This is not an image-generation skill — it does not call any LLM image model. Claude writes the SVG node-by-node, doing the layout math by hand so every diagram honors the rules in `references/`.
|
||||
| Type | When to Use | Key Characteristics |
|
||||
|------|-------------|-------------------|
|
||||
| **Architecture** | System components & relationships | Grouped boxes, connection arrows, region boundaries |
|
||||
| **Flowchart** | Decision logic, process steps | Diamond decisions, rounded step boxes, directional flow |
|
||||
| **Sequence** | Time-ordered interactions between actors | Vertical lifelines, horizontal messages, activation bars |
|
||||
| **Structural** | Class diagrams, ER diagrams, org charts | Compartmented boxes, typed relationships (inheritance, composition) |
|
||||
| **Mind Map** | Brainstorming, topic exploration | Central node, radiating branches, organic layout |
|
||||
| **Timeline** | Chronological events | Horizontal/vertical axis, event markers, period spans |
|
||||
| **Illustrative** | Conceptual explanations, comparisons | Free-form layout, icons, annotations, visual metaphors |
|
||||
| **State Machine** | State transitions, lifecycle | Rounded state nodes, labeled transitions, start/end markers |
|
||||
| **Data Flow** | Data transformation pipelines | Process bubbles, data stores, external entities |
|
||||
|
||||
## Usage
|
||||
## Design System
|
||||
|
||||
### Color Palette
|
||||
|
||||
Semantic colors for component categories:
|
||||
|
||||
| Category | Fill (rgba) | Stroke | Use For |
|
||||
|----------|-------------|--------|---------|
|
||||
| Primary | `rgba(8, 51, 68, 0.4)` | `#22d3ee` (cyan) | Frontend, user-facing, inputs |
|
||||
| Secondary | `rgba(6, 78, 59, 0.4)` | `#34d399` (emerald) | Backend, services, processing |
|
||||
| Tertiary | `rgba(76, 29, 149, 0.4)` | `#a78bfa` (violet) | Database, storage, persistence |
|
||||
| Accent | `rgba(120, 53, 15, 0.3)` | `#fbbf24` (amber) | Cloud, infrastructure, regions |
|
||||
| Alert | `rgba(136, 19, 55, 0.4)` | `#fb7185` (rose) | Security, errors, warnings |
|
||||
| Connector | `rgba(251, 146, 60, 0.3)` | `#fb923c` (orange) | Buses, queues, middleware |
|
||||
| Neutral | `rgba(30, 41, 59, 0.5)` | `#94a3b8` (slate) | External, generic, unknown |
|
||||
| Highlight | `rgba(59, 130, 246, 0.3)` | `#60a5fa` (blue) | Active state, focus, current step |
|
||||
|
||||
For flowcharts and sequence diagrams, assign colors by role (actor, decision, process) rather than by technology.
|
||||
|
||||
### Typography
|
||||
|
||||
Use embedded SVG `@font-face` or system monospace fallback:
|
||||
|
||||
```svg
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap');
|
||||
text { font-family: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace; }
|
||||
</style>
|
||||
```
|
||||
|
||||
Font sizes by role:
|
||||
- **Title:** 16px, weight 700
|
||||
- **Component name:** 11-12px, weight 600
|
||||
- **Sublabel / description:** 9px, weight 400, color `#94a3b8`
|
||||
- **Annotation / note:** 8px, weight 400
|
||||
- **Tiny label (on arrows):** 7-8px
|
||||
|
||||
### Core Visual Elements
|
||||
|
||||
**Background:** `#0f172a` (slate-900) with subtle grid:
|
||||
```svg
|
||||
<defs>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="#0f172a"/>
|
||||
<rect width="100%" height="100%" fill="url(#grid)"/>
|
||||
```
|
||||
|
||||
**Arrowhead marker (standard):**
|
||||
```svg
|
||||
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/>
|
||||
</marker>
|
||||
```
|
||||
|
||||
**Arrowhead marker (colored) — create per-color as needed:**
|
||||
```svg
|
||||
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#22d3ee"/>
|
||||
</marker>
|
||||
```
|
||||
|
||||
**Open arrowhead (for async/return messages):**
|
||||
```svg
|
||||
<marker id="arrow-open" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polyline points="0 0, 10 3.5, 0 7" fill="none" stroke="#64748b" stroke-width="1.5"/>
|
||||
</marker>
|
||||
```
|
||||
|
||||
### SVG Structure & Layering
|
||||
|
||||
Draw elements in this order to get correct z-ordering (SVG paints back-to-front):
|
||||
|
||||
1. Background fill + grid pattern
|
||||
2. Region/group boundaries (dashed outlines)
|
||||
3. Connection arrows and lines
|
||||
4. Opaque masking rects (same position as component boxes, `fill="#0f172a"`)
|
||||
5. Component boxes (semi-transparent fill + stroke)
|
||||
6. Text labels
|
||||
7. Legend (bottom-right or bottom area, outside all boundaries)
|
||||
8. Title block (top-left)
|
||||
|
||||
The opaque masking rect trick is essential — semi-transparent component fills will show arrows underneath without it:
|
||||
```svg
|
||||
<!-- Mask layer: opaque background to hide arrows -->
|
||||
<rect x="100" y="100" width="160" height="60" rx="6" fill="#0f172a"/>
|
||||
<!-- Visual layer: styled component -->
|
||||
<rect x="100" y="100" width="160" height="60" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
|
||||
<text x="180" y="125" fill="white" font-size="11" font-weight="600" text-anchor="middle">API Gateway</text>
|
||||
<text x="180" y="141" fill="#94a3b8" font-size="9" text-anchor="middle">Kong / Nginx</text>
|
||||
```
|
||||
|
||||
### Spacing Rules
|
||||
|
||||
These prevent overlapping — follow them strictly:
|
||||
|
||||
- **Component box height:** 50-70px (standard), 80-120px (large/complex)
|
||||
- **Minimum gap between components:** 40px vertical, 30px horizontal
|
||||
- **Arrow label clearance:** 10px from any box edge
|
||||
- **Region boundary padding:** 20px inside edges around contained components
|
||||
- **Legend placement:** At least 20px below the lowest diagram element
|
||||
- **Title block:** 20px from top-left, outside diagram content area
|
||||
- **viewBox:** Always extend to fit all content + 30px padding on all sides
|
||||
|
||||
### Component Patterns
|
||||
|
||||
**Standard box (service/process):**
|
||||
```svg
|
||||
<rect x="X" y="Y" width="160" height="60" rx="6" fill="#0f172a"/>
|
||||
<rect x="X" y="Y" width="160" height="60" rx="6" fill="FILL" stroke="STROKE" stroke-width="1.5"/>
|
||||
<text x="CX" y="Y+24" fill="white" font-size="11" font-weight="600" text-anchor="middle">Name</text>
|
||||
<text x="CX" y="Y+40" fill="#94a3b8" font-size="9" text-anchor="middle">description</text>
|
||||
```
|
||||
|
||||
**Decision diamond (flowchart):**
|
||||
```svg
|
||||
<g transform="translate(CX, CY)">
|
||||
<polygon points="0,-35 50,0 0,35 -50,0" fill="#0f172a"/>
|
||||
<polygon points="0,-35 50,0 0,35 -50,0" fill="rgba(120,53,15,0.3)" stroke="#fbbf24" stroke-width="1.5"/>
|
||||
<text y="4" fill="white" font-size="10" font-weight="600" text-anchor="middle">Condition?</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Database cylinder:**
|
||||
```svg
|
||||
<g transform="translate(X, Y)">
|
||||
<rect x="0" y="10" width="120" height="50" rx="2" fill="#0f172a"/>
|
||||
<ellipse cx="60" cy="10" rx="60" ry="12" fill="#0f172a"/>
|
||||
<ellipse cx="60" cy="60" rx="60" ry="12" fill="#0f172a"/>
|
||||
<rect x="0" y="10" width="120" height="50" fill="rgba(76,29,149,0.4)"/>
|
||||
<ellipse cx="60" cy="10" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<ellipse cx="60" cy="60" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<line x1="0" y1="10" x2="0" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<line x1="120" y1="10" x2="120" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
|
||||
<text x="60" y="40" fill="white" font-size="11" font-weight="600" text-anchor="middle">PostgreSQL</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Region boundary:**
|
||||
```svg
|
||||
<rect x="X" y="Y" width="W" height="H" rx="12" fill="none" stroke="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
|
||||
<text x="X+12" y="Y+16" fill="#fbbf24" font-size="9" font-weight="600">AWS us-east-1</text>
|
||||
```
|
||||
|
||||
**Security group:**
|
||||
```svg
|
||||
<rect x="X" y="Y" width="W" height="H" rx="8" fill="none" stroke="#fb7185" stroke-width="1" stroke-dasharray="4,4"/>
|
||||
<text x="X+10" y="Y+14" fill="#fb7185" font-size="8" font-weight="500">VPC / Security Group</text>
|
||||
```
|
||||
|
||||
## Type-Specific Layout Guidance
|
||||
|
||||
Determine this SKILL.md file's directory path as `{baseDir}`. Read the reference file for the specific diagram type before starting layout. Reference files are located at `{baseDir}/references/` and contain detailed layout algorithms and examples.
|
||||
|
||||
### Architecture Diagrams
|
||||
→ Read `{baseDir}/references/architecture.md`
|
||||
|
||||
Key points: left-to-right or top-to-bottom data flow. Group related services in region boundaries. Use buses/connectors between layers. Place databases at the bottom or right.
|
||||
|
||||
### Flowcharts
|
||||
→ Read `{baseDir}/references/flowchart.md`
|
||||
|
||||
Key points: top-to-bottom primary flow. Diamonds for decisions with Yes/No labels on exit arrows. Rounded rectangles for start/end. Use the Highlight color for the happy path.
|
||||
|
||||
### Sequence Diagrams
|
||||
→ Read `{baseDir}/references/sequence.md`
|
||||
|
||||
Key points: actors as boxes at top, vertical dashed lifelines, horizontal arrows for messages (solid=sync, dashed=return). Time flows downward. Activation bars show processing. Number messages if complex.
|
||||
|
||||
### Structural Diagrams
|
||||
→ Read `{baseDir}/references/structural.md`
|
||||
|
||||
Key points: compartmented boxes (name / attributes / methods for class diagrams). Relationship lines: solid with filled diamond=composition, solid with empty diamond=aggregation, dashed arrow=dependency, solid triangle=inheritance.
|
||||
|
||||
### Mind Maps
|
||||
Free-form radiating layout from a central concept. Use organic curves (`<path>` with cubic beziers) for branches. Vary branch colors using the palette. Larger font for central node, decreasing as you go outward.
|
||||
|
||||
### Timelines
|
||||
Horizontal or vertical axis line. Event markers as circles or diamonds on the axis. Description text offset to alternating sides to avoid overlap. Use color to categorize event types.
|
||||
|
||||
### State Machines
|
||||
Rounded-rect states with double-border for composite states. Filled circle for initial state, bullseye for final state. Curved arrows for self-transitions. Label all transitions with `event [guard] / action` format.
|
||||
|
||||
## Output Rules
|
||||
|
||||
1. Output a **single `.svg` file** — no external dependencies except the Google Fonts import
|
||||
2. Set `viewBox` to fit all content with 30px padding; do NOT set fixed `width`/`height` attributes (let the SVG scale responsively)
|
||||
3. Include `xmlns="http://www.w3.org/2000/svg"` on the root `<svg>` element
|
||||
4. Put all `<style>`, `<defs>`, markers, and patterns at the top of the SVG
|
||||
5. Use `text-anchor="middle"` for centered labels; ensure text doesn't overflow boxes
|
||||
6. **Chinese text support:** When labels contain Chinese characters, use `font-family: 'JetBrains Mono', 'Noto Sans SC', 'PingFang SC', sans-serif'` and increase box widths — CJK characters are wider
|
||||
7. **Save location:** If the input is a file, save to `{inputFileDir}/diagram/`. Otherwise save to `{projectDir}/diagram/{topic-slug}/`. Create the directory if it doesn't exist
|
||||
|
||||
## Script
|
||||
|
||||
Determine this SKILL.md file's directory path as `{baseDir}`. Script path: `{baseDir}/scripts/main.ts`.
|
||||
|
||||
Resolve `${BUN_X}` runtime: if `bun` installed → `bun`; if `npx` available → `npx -y bun`; else suggest installing bun.
|
||||
|
||||
### SVG → @2x PNG
|
||||
|
||||
After saving the SVG, convert it to a @2x PNG:
|
||||
|
||||
```bash
|
||||
# Topic string — skill analyzes and proposes a plan
|
||||
/baoyu-diagram "how JWT authentication works"
|
||||
|
||||
# File path — skill reads, analyzes, and proposes a plan
|
||||
/baoyu-diagram path/to/content.md
|
||||
|
||||
# Pasted content — prompts for input if no argument given
|
||||
/baoyu-diagram
|
||||
|
||||
# Force a specific diagram type (skips type recommendation)
|
||||
/baoyu-diagram "transformer attention" --type illustrative
|
||||
/baoyu-diagram "Kubernetes architecture" --type structural
|
||||
/baoyu-diagram "CI/CD pipeline" --type flowchart
|
||||
/baoyu-diagram "OAuth 2.0 flow" --type sequence
|
||||
/baoyu-diagram "Shape hierarchy" --type class
|
||||
|
||||
# Language and output path
|
||||
/baoyu-diagram "微服务架构" --lang zh
|
||||
/baoyu-diagram "build pipeline" --out docs/build-pipeline.svg
|
||||
${BUN_X} {baseDir}/scripts/main.ts <svg-path> [options]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Values |
|
||||
|--------|--------|
|
||||
| `--type` | `flowchart`, `sequence`, `structural`, `illustrative`, `class`, `auto` (default — route on verb). When specified, forces this type for all diagrams — skips type recommendation. |
|
||||
| `--lang` | `en`, `zh`, `ja`, `ko`, ... (default: match the user's language) |
|
||||
| `--out` | Output file path. When set, the skill generates exactly one diagram at this path — analysis produces a single-diagram plan focused on the most important aspect of the material. |
|
||||
|
||||
## Diagram types
|
||||
|
||||
Pick the type by what the reader needs, not by the noun in the prompt.
|
||||
|
||||
**The primary test**: is the reader trying to *document* this, or *understand* it? Documentation wants precision — flowchart, sequence, or structural. Understanding wants the right mental model — illustrative.
|
||||
|
||||
| Type | Reader need | Route on verbs like | Reference |
|
||||
|------|-------------|---------------------|-----------|
|
||||
| **Flowchart** | Walk me through the steps, in order | "walk through", "steps", "process", "lifecycle", "workflow", "state machine", "gate", "router", "parallelization", "orchestrator", "evaluator" | `references/flowchart.md` |
|
||||
| **Flowchart (phase band)** | Walk me through each phase; show the tools at each stage | "phase 1/2/3", "multi-phase operation", "each phase has tools", "attack phases", "phased workflow", "security operation phases", "penetration test stages", "phase N feeds phase N+1" | `references/flowchart-phase-bands.md` |
|
||||
| **Sequence** | Who talks to whom, in what order | "protocol", "handshake", "auth flow", "OAuth", "TCP", "TLS", "gRPC", "request/response", "who calls what", "exchange between", "round trip", "webhook" | `references/sequence.md` |
|
||||
| **Structural** | Show me what's inside what, how it's organized | "architecture", "organised", "components", "layout", "what's inside", "topology", "subsystem", "two systems", "side by side", "foreground + background" | `references/structural.md` |
|
||||
| **Illustrative** | Give me the intuition — draw the mechanism | "how does X work", "explain X", "I don't get X", "intuition for", "why does X do Y", "LLM with tools", "agent and environment", "central + attachments" | `references/illustrative.md` |
|
||||
| **Class** | What are the types and how are they related | "class diagram", "UML", "inheritance", "interface", "schema", "types and subtypes", "data model" | `references/class.md` |
|
||||
|
||||
**Routing heuristic**: "how does X work" is the default ambiguous case. Prefer **illustrative** unless the user specifically asks for steps or components. A diagram that makes the reader feel "oh, *that's* what it's doing" is illustrative — even if the subject is software.
|
||||
|
||||
**Multi-actor test for sequence**: if the prompt names ≥2 distinct actors/participants/services (User + Server, Client + Auth + Resource, Browser + CDN + Origin), prefer **sequence** even when the verb is "flow" or "process". Single-actor "X flow" (build pipeline, request lifecycle, GC) stays flowchart. When you pick sequence for a multi-actor reason, announce it: *"Picked sequence because the prompt names N actors (…). Rerun with `--type flowchart` to force the step-list version."*
|
||||
|
||||
**Worked examples of verb-based routing**: same subject, different diagram depending on what was asked. Use these as a sanity check after picking a type.
|
||||
|
||||
| User says | Type | What to draw |
|
||||
|-------------------------------------------|--------------|----------------------------------------------------------------------------------|
|
||||
| "how do LLMs work" | Illustrative | Token row, stacked layer slabs, attention threads across layers. |
|
||||
| "transformer architecture / components" | Structural | Labeled boxes: embedding, attention heads, FFN, layer norm. |
|
||||
| "how does attention work" | Illustrative | One query token, fan of lines to every key, line thickness = weight. |
|
||||
| "how does gradient descent work" | Illustrative | Contour surface, a ball rolling down, a trail of discrete steps. |
|
||||
| "what are the training steps" | Flowchart | Forward → loss → backward → update. |
|
||||
| "how does TCP work" | Illustrative | Two endpoints, numbered packets in flight, an ACK returning. |
|
||||
| "TCP handshake sequence" | Sequence | SYN → SYN-ACK → ACK between client and server lifelines. |
|
||||
| "how does a hash map work" | Illustrative | Key falling through a hash function into one of N buckets. |
|
||||
| "LLM with retrieval, tools, memory" | Illustrative | Central LLM subject with dashed radial spokes to three labeled attachments. |
|
||||
| "gate pattern with pass/fail exit" | Flowchart | Pill In → LLM → Gate → LLM → LLM → pill Out, with a dashed Fail branch to Exit. |
|
||||
| "LLM router / parallelization" | Flowchart | Simple fan-out: pill In → hub → 3 branches → aggregator → pill Out. |
|
||||
| "Pi session + background analyzer" | Structural (subsystem) | Two dashed sibling containers side by side, each with a short internal flow, labeled cross-system arrows. |
|
||||
| "prompt engineering vs. context engineering" | Structural (subsystem) | Two sibling containers, each showing its internal mechanism with cross-links. |
|
||||
| "agent + environment loop" | Illustrative | Human pill ↔ LLM rect ↔ Environment pill, Action/Feedback labels on the edges. |
|
||||
| "Claude Code workflow with sub-loops" | Sequence | 4 actors with 1–2 dashed message frames labeled "Until tests pass" / "Until tasks clear". |
|
||||
| "generator-verifier loop" | Flowchart | Outer loop container; two boxes with green ✓ / coral ✗ status circles on the return edge. See `flowchart.md` → "Loop container" + "Status-circle junctions". |
|
||||
| "from TODOs to tasks" | Structural (subsystem) | Two siblings: left = checklist (checkbox glyphs); right = DAG of task nodes with one dashed future-state node. See `structural.md` → "Rich interior" + "Dashed future-state node". |
|
||||
| "finding the sweet spot" | Illustrative | Horizontal spectrum axis between two opposing labels; option boxes under tick points with the middle one highlighted. See `illustrative.md` → "Spectrum / continuum". |
|
||||
| "agent teams with task queue" | Flowchart | Queue glyph inside the lead box, then vertical fan-out to workers. See `flowchart.md` → "Queue glyph inside box" + "Vertical fan-out". |
|
||||
| "message bus architecture" | Structural | Central horizontal bar + agents above/below, each linked by a publish/subscribe arrow pair. See `structural.md` → "Bus topology". |
|
||||
| "shared state store" | Structural | Central hub with a doc icon + 4 corner satellites, bidirectional arrow pairs. See `structural.md` → "Radial star topology". |
|
||||
| "orchestrator vs. agent teams" | Structural (subsystem) | Two siblings; left = hub + fan-out; right = queue + vertical fan-out. See `structural.md` → "Rich interior for subsystem containers". |
|
||||
| "orchestrator vs. message bus" | Structural (subsystem) | Two siblings; left = hub + fan-out; right = mini bus topology. See `structural.md` → "Rich interior". |
|
||||
| "advisor strategy" | Structural | Single container, multi-line box bodies (title/role/meta), mixed solid+dashed+bidirectional arrows with a legend strip. See `structural.md` → "Mixed arrow semantics" + "Multi-line box body". |
|
||||
| "tool calling vs. programmatic" | Sequence | Parallel independent rounds — left = stacked rounds; right = stacked rounds wrapped in a tall script box. See `sequence.md` → "Parallel independent rounds". |
|
||||
| "Claude + environment + skill" | Illustrative | Two subject boxes with a bidirectional arrow; annotation circle at the midpoint labels the skill. See `illustrative.md` → "Annotation circle on connector". |
|
||||
| "code execution vs. dedicated tool" | Structural (subsystem) | Two siblings; left = Computer box with nested Terminal; right = Claude with an attached gadget box for Tools. See `structural.md` → "Rich interior" + "Attached gadget box". |
|
||||
| "Shape inheritance / class hierarchy" | Class | 3-compartment rects (name / attrs / methods) with hollow-triangle inheritance arrows. See `class.md`. |
|
||||
| "order lifecycle / status transitions" | Flowchart (state machine) | State rects + initial/final markers + `event [guard] / action` transition labels. See `flowchart.md` → "State machine". |
|
||||
| "network topology (3-tier)" | Structural (network) | Dashed zone containers (Internet / DMZ / Internal) + labeled device rects. See `structural.md` → "Network topology". |
|
||||
| "database comparison matrix" | Structural (matrix) | Header row + zebra-striped body rows with ✓/✗ glyphs in cells. See `structural.md` → "Comparison matrix". |
|
||||
| "multi-phase attack / each phase has tools" | Flowchart (phase band) | Stacked dashed phase bands; compact tool cards with icons in each band; colored cross-band arrows (normal / exploit / findings); operator icons on left. See `flowchart-phase-bands.md`. |
|
||||
| "phased workflow / phase 1 recon phase 2 exploit" | Flowchart (phase band) | Phase labels as eyebrow text; tool card rows centered in each band; side annotations; legend strip. See `flowchart-phase-bands.md`. |
|
||||
|
||||
**Most common routing failure**: picking a flowchart because it feels safer when an illustrative diagram would give the reader more insight. Illustrative is the more ambitious choice, and almost always the right one when the reader needs understanding rather than documentation.
|
||||
|
||||
Cycles, ERDs, and gantt charts are **out of scope for v1**. For cycles, draw the stages linearly with a small `↻ returns to start` return glyph (see `flowchart.md`). For ERDs, suggest a dedicated tool (mermaid, plantuml) — do not attempt to fake them in pure SVG.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Capture input
|
||||
|
||||
Read the user's prompt, content file, or pasted content. Note any flags (`--type`, `--lang`, `--out`).
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| File path to `.md` / `.txt` | Read the file as source material |
|
||||
| Pasted content or topic string | Capture as source material |
|
||||
| No input at all | Ask with AskUserQuestion |
|
||||
|
||||
If `--out` is given, the skill will generate exactly one diagram at that path — the analysis in Step 2 produces a single-diagram plan focused on the most important aspect of the material.
|
||||
|
||||
### Step 2: Analyze material and produce plan
|
||||
|
||||
Analyze the source material and make three decisions:
|
||||
|
||||
#### Decision A: Type routing
|
||||
|
||||
For the input material, determine which diagram type(s) are appropriate using the routing table in "Diagram types."
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Only one type makes sense (clear verb signal, or `--type` given) | That type is the recommendation. No choice needed. |
|
||||
| Multiple types could each produce a useful diagram from the same material | List the candidates with a one-sentence rationale for each. The user picks in Step 3. |
|
||||
|
||||
#### Decision B: Content splitting
|
||||
|
||||
Assess whether the material should produce one diagram or multiple sub-diagrams.
|
||||
|
||||
**Single diagram** when:
|
||||
- Material is focused on one concept, one mechanism, one process
|
||||
- Named elements count is manageable (under ~6 for flowchart, under ~4 actors for sequence, under ~3 containers for structural — but architecture diagrams may have 10–20 elements in a single diagram; see Step 5a item 6, "Architecture enrichment")
|
||||
- One "After seeing this diagram, the reader understands ___" sentence covers the whole material
|
||||
|
||||
**Multiple sub-diagrams** when:
|
||||
- Material covers 2+ independent mechanisms or processes
|
||||
- Named element count exceeds comfortable limits for one diagram type
|
||||
- Material has natural subsections that each deserve visual treatment
|
||||
- Different parts of the material map to different diagram types
|
||||
|
||||
For each sub-diagram, determine: focus area, recommended type, named elements, and the "reader understands ___" sentence.
|
||||
|
||||
**What to diagram:**
|
||||
- Core mechanisms the reader needs to *understand* (→ illustrative)
|
||||
- Multi-step processes described in prose (→ flowchart)
|
||||
- Multi-actor interactions (→ sequence)
|
||||
- Architectural descriptions with containment or hierarchy (→ structural)
|
||||
- Type hierarchies or data models (→ class)
|
||||
- Comparisons between two approaches or systems (→ structural subsystem)
|
||||
|
||||
**What NOT to diagram:**
|
||||
- Simple lists — a bullet list is already visual enough
|
||||
- Concepts already shown in an existing image or figure
|
||||
- Purely emotional or narrative passages with no underlying mechanism
|
||||
- Content that is a single sentence or trivially simple
|
||||
- Decorative filler — every diagram must earn its place with a concrete reader need
|
||||
|
||||
#### Decision C: Overview diagram
|
||||
|
||||
When the plan includes multiple sub-diagrams, assess whether an additional overview diagram that shows the big picture is worthwhile.
|
||||
|
||||
| Situation | Decision |
|
||||
|-----------|----------|
|
||||
| Sub-diagrams are parts of a coherent system, seeing how they relate adds value | Include an overview diagram (typically structural or illustrative) |
|
||||
| Sub-diagrams cover independent topics that don't form a coherent whole | Skip the overview |
|
||||
| Material is simple enough that sub-diagrams already cover everything | Skip the overview |
|
||||
|
||||
#### Plan output
|
||||
|
||||
Save the plan as `outline.md` (for multiple diagrams) or hold in memory (for single diagram).
|
||||
|
||||
**Single-diagram plan format:**
|
||||
|
||||
```markdown
|
||||
## Diagram Plan
|
||||
**Material**: [source description]
|
||||
**Diagrams**: 1
|
||||
**Type**: [type] (rationale)
|
||||
**Named elements**: [list]
|
||||
**Reader need**: "After seeing this diagram, the reader understands ___"
|
||||
**Slug**: [slug]
|
||||
```
|
||||
|
||||
**Multi-diagram plan format:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
material: [source description]
|
||||
slug: [material-slug]
|
||||
diagram_count: N
|
||||
language: en
|
||||
---
|
||||
```
|
||||
|
||||
Per-diagram entry:
|
||||
|
||||
```markdown
|
||||
## Diagram 1: [focus area]
|
||||
**Type**: [type] (rationale)
|
||||
**Named elements**: [list]
|
||||
**Reader need**: "After seeing this diagram, the reader understands ___"
|
||||
**Slug**: [2-4 kebab-case words]
|
||||
**Filename**: 01-{type}-{slug}/diagram.svg
|
||||
|
||||
## Diagram 2: [focus area]
|
||||
...
|
||||
|
||||
## Overview diagram (if applicable)
|
||||
**Type**: [structural/illustrative]
|
||||
**Purpose**: Shows how diagrams 1-N relate as parts of a larger system
|
||||
**Named elements**: [high-level elements]
|
||||
**Slug**: overview-[slug]
|
||||
**Filename**: overview-{type}-{slug}/diagram.svg
|
||||
```
|
||||
|
||||
Requirements:
|
||||
- Each diagram justified by a concrete reader need (the "After seeing this..." sentence)
|
||||
- Type chosen per the routing table, not arbitrarily
|
||||
- If input was pasted content, also save it as `source-{slug}.md` in the output directory
|
||||
|
||||
### Step 3: Confirm plan (one-time)
|
||||
|
||||
**Maximum 1 AskUserQuestion call for the entire workflow.** This is the only confirmation step — no further questions during generation.
|
||||
|
||||
| Plan shape | Confirmation |
|
||||
|------------|-------------|
|
||||
| Single diagram, obvious type (`--type` given, or clear verb signal) | **No confirmation.** Announce the type in one sentence and proceed to Step 4. |
|
||||
| Single diagram, ambiguous type (multiple types viable) | **Lightweight.** "The material could work as [type A] (rationale) or [type B] (rationale). Which do you prefer?" |
|
||||
| Multiple diagrams | **Full plan.** Show the numbered list of all planned diagrams with their types and purposes, plus overview if applicable. User can adjust (add/remove diagrams, change types, toggle overview) in one response. |
|
||||
|
||||
Language question: only include if material language differs from user's language and `--lang` is not given.
|
||||
|
||||
Example full plan confirmation:
|
||||
|
||||
```
|
||||
I analyzed the material and recommend N diagrams [+ an overview]:
|
||||
|
||||
1. [Focus area] — [type] — "Reader understands ___"
|
||||
2. [Focus area] — [type] — "Reader understands ___"
|
||||
3. [Focus area] — [type] — "Reader understands ___"
|
||||
[Overview: [type] — "Shows how 1-N relate as a system"]
|
||||
|
||||
Adjust the plan? (add/remove diagrams, change types, skip/add overview)
|
||||
```
|
||||
|
||||
After confirmation (or after skipping confirmation for obvious plans), the plan is locked. Proceed to generation.
|
||||
|
||||
Save the finalized plan:
|
||||
- Multiple diagrams: `diagram/{material-slug}/outline.md`
|
||||
- Single diagram: plan is saved as `plan.md` beside the SVG in Step 5g
|
||||
|
||||
### Step 4: Load shared references
|
||||
|
||||
**Always read**:
|
||||
- `references/design-system.md` — philosophy, typography, color palette, hard rules
|
||||
- `references/svg-template.md` — the `<style>` + `<defs>` boilerplate to copy verbatim
|
||||
- `references/layout-math.md` — text-width estimation, viewBox sizing, arrow routing
|
||||
- `references/pitfalls.md` — the pre-save checklist
|
||||
|
||||
Per-type reference files are loaded inside the generation loop (Step 5b) since each diagram may have a different type.
|
||||
|
||||
### Step 5: Per-diagram generation loop
|
||||
|
||||
For each diagram in the confirmed plan (1 to N, overview diagram generated last):
|
||||
|
||||
#### 5a: Capture intent
|
||||
|
||||
Read the current diagram's plan entry. Extract or refine these five things from the source material:
|
||||
|
||||
1. **Named elements** — list every distinct actor, component, service, state, or phase explicitly named. Count them. If the count is 6+ for simple flowcharts, plan multiple diagrams rather than cramming everything into one (see `flowchart.md` → "Planning before you write SVG"). **Exception**: structural architecture diagrams (microservices, cloud topologies, system designs) routinely need 10–20 named elements in a single diagram — services, databases, gateways, message buses, clients. 10 is the minimum where splitting is unnecessary; enriched diagrams (item 6 below) typically reach ≥12. Do not split an architecture diagram just because the element count is high; instead, see "Architecture enrichment" (item 6 below) and `structural.md` → "Full architecture layout".
|
||||
|
||||
2. **Relationship type** — for each interaction between elements, classify it:
|
||||
- Sequential steps / order of operations → flowchart signal
|
||||
- Containment ("X is inside Y", zones, hierarchies) → structural signal
|
||||
- Multi-actor message exchange (A sends to B, B replies to C) → sequence signal
|
||||
- Mechanism ("how does X produce Y") → illustrative signal
|
||||
More than one type present? Pick the dominant one, or flag for the plan.
|
||||
|
||||
3. **What the reader needs** — complete this sentence before routing: *"After seeing this diagram, the reader understands ___."* If you can't finish it, the topic is underspecified — ask.
|
||||
|
||||
4. **Label preview** — for each element name, count the characters. Latin titles >30 chars (CJK >16) will overflow a 180-wide box and need shortening. Draft the abbreviated form now, before layout math, so Step 5d uses real labels.
|
||||
|
||||
5. **Language** — CJK vs. Latin. Affects text-width multipliers in Step 5d (15 px/char vs. 8 px/char for titles). Mixed content (CJK labels with some Latin terms) counts as CJK.
|
||||
|
||||
6. **Architecture enrichment** — when the type is **structural** and the topic is an architecture or infrastructure diagram ("microservices architecture", "Kubernetes cluster", "cloud topology", "system design"), actively expand the named elements beyond what the user literally wrote. The user's prompt is a **seed**, not a complete spec. A bare "microservices architecture" should produce a diagram with ≥12 named elements, not 5 generic boxes. Apply these enrichment rules:
|
||||
|
||||
- **Multiple client types**: if the system serves end users, show ≥2 clients (Web app + Mobile app, or Browser + CLI). Each with a tech subtitle (React SPA, iOS/Android).
|
||||
- **Gateway details**: technology name (Kong, Nginx, Envoy), responsibilities (rate limiting, auth/routing), port (:443). Not just "API Gateway".
|
||||
- **Per-service specifics**: each microservice gets a technology and port subtitle (Go :8081, Java :8082, Python :8083, Node.js :8084). The reader should learn the tech stack from the diagram.
|
||||
- **Database per service**: each service connects to its own data store. Show the databases as a separate column or tier (PostgreSQL, MongoDB, Elasticsearch, Redis) with role subtitles (Users DB, Orders DB, Cache/Queue).
|
||||
- **Message bus / event bus**: if services communicate asynchronously, show messaging infrastructure (Kafka, RabbitMQ, Event Bus) as small labeled connector pills between the services that use them.
|
||||
- **Auth service**: when JWT/OAuth is mentioned or implied, separate it from business services as a distinct component with protocol subtitle (OAuth 2.0 / JWT).
|
||||
- **Color categories**: architecture diagrams with ≥3 component types trigger the structural architecture exception (see `design-system.md` rule 9). Assign one ramp per category: services=teal, databases=purple, gateways=coral, message buses=amber. Mandatory legend.
|
||||
- **Summary panel** (optional): a bottom section with 2–3 columns summarizing key architecture principles (Client Applications, Microservices, Infrastructure). Add when the diagram has ≥10 named elements.
|
||||
- **Title + subtitle**: architecture diagrams always get a `.title` at the top with the architecture name and a `.ts` subtitle describing the approach (e.g., "Domain-driven design", "Event-driven microservices").
|
||||
|
||||
The enrichment principle: **a reader should learn something specific from the diagram**. "User service / Go :8081" teaches more than "User service / Accounts & profiles". Technology choices, ports, and protocols are the details that make an architecture diagram useful rather than decorative.
|
||||
|
||||
Skip enrichment for non-architecture structural diagrams (biological containment, CPU caches, file systems) — those benefit from simplicity, not tech details.
|
||||
|
||||
#### 5b: Load type reference
|
||||
|
||||
The type was determined in the plan. Load the matching reference file.
|
||||
|
||||
**Read the one that matches the type**:
|
||||
- `references/flowchart.md`
|
||||
- `references/sequence.md`
|
||||
- `references/structural.md`
|
||||
- `references/illustrative.md`
|
||||
- `references/class.md`
|
||||
|
||||
**Read on demand** when the plan calls for a small pictorial element (status circle on a decision branch, checkbox inside a list, queue slot inside a box, doc/terminal/script icon inside a subject, annotation circle on a connector, paired pub/sub arrows, dashed future-state node) **or** when drawing a phase-band diagram (compact tool card icons, operator icons):
|
||||
- `references/glyphs.md` — the shared glyph library, tool card icon set, operator icons, and dark-mode rules
|
||||
|
||||
**Read on demand for diagram type extensions:**
|
||||
- `references/flowchart-poster.md` — when ≥3 poster-mode triggers fire in Step 5d (topic has a short name, named phases, parallel candidates, a loop termination mechanic, overflow annotations, or a footer quote)
|
||||
- `references/flowchart-phase-bands.md` — when the prompt describes a multi-phase sequential operation where each phase contains parallel tools or steps and outcomes propagate between phases
|
||||
- `references/structural-network.md` — when drawing network topology: zone containers, wired/wireless device connectivity, security zones
|
||||
- `references/structural-matrix.md` — when drawing a comparison matrix: feature table, ✓/✗ cells, side-by-side grid
|
||||
|
||||
#### 5c: Check patterns library
|
||||
|
||||
If the topic matches a known AI-system pattern, there is a pre-cooked starter plan in `references/patterns/`. Scan `references/patterns/README.md` for a pattern name that matches. If one matches, load that pattern file and use its mermaid reference + baoyu SVG plan as the starting point for Step 5d.
|
||||
|
||||
If nothing matches, skip and plan from scratch in Step 5d. Do not force a near-miss.
|
||||
|
||||
#### 5d: Plan on paper
|
||||
|
||||
Before writing any SVG, draft a short layout plan. Do the math once, correctly, so the SVG comes out right on the first pass.
|
||||
|
||||
**5d-0. Draft the Mermaid sketch first** — write a Mermaid code block that captures the **structural intent** of the diagram: which nodes exist, how they connect, what direction they flow, and any grouping (subgraphs). This is the single source of truth for *what* to draw; everything after it (coordinates, widths, arrows) answers *how*.
|
||||
|
||||
Rules for the Mermaid sketch:
|
||||
- Use the Mermaid dialect that best matches the diagram type: `flowchart TD/LR` for flowcharts, `sequenceDiagram` for sequence, `classDiagram` for class, `flowchart` with subgraphs for structural/illustrative.
|
||||
- Include every node, every edge, every label, and every subgraph/container. If a node won't appear in the Mermaid, it won't appear in the SVG.
|
||||
- Edge labels must match the final SVG labels — write them now, not later.
|
||||
- Keep it concise: the sketch is a structural contract, not a rendering. Mermaid can't express baoyu's visual design (colors, rounded rects, dark mode), so don't try — those come in 5d-ii and 5e.
|
||||
- For patterns that have a Mermaid reference in `references/patterns/`, start from that reference and adapt it to the specific topic.
|
||||
|
||||
Save the Mermaid block in the plan file. When writing SVG in Step 5e, **cross-check every node and edge against this Mermaid sketch** — if the sketch has it, the SVG must have it; if the SVG adds something the sketch doesn't have, update the sketch first.
|
||||
|
||||
**5d-i. Extract structure from the source** — don't just transcribe bullets into boxes. Read the source looking for these elements. Not every element will be present, but every present element should land in the diagram:
|
||||
|
||||
- **Mechanism name** — does the topic have a short, nameable identity (Autoreason, AutoResearch, OAuth, JWT auth, Reflexion loop)? If yes, that's a candidate `.title`.
|
||||
- **Framing question** — does the source contain a "why does this exist" sentence? That's a candidate subtitle.
|
||||
- **Phases** — do the stages naturally cluster into 2–4 named groups? Each cluster is a candidate `.eyebrow` section.
|
||||
- **Anchor inputs** — is there a constant input (the task prompt, a dataset, a knowledge base) that every stage references? That's a candidate anchor box above the main flow.
|
||||
- **Parallel candidates** — at some point, does the process generate N alternatives that are then compared? **Watch for the implicit "keep unchanged" candidate.**
|
||||
- **Loop scope + termination** — which boxes are inside a loop that repeats? What is the *specific* termination rule? That's a candidate left-rail loop bracket + a dedicated termination box.
|
||||
- **Per-box context that won't fit in a subtitle** — those are candidate right-column `.anno` annotations.
|
||||
- **Quotable hook** — does the source end with a test result, a quote, or a memorable framing? That's a candidate footer `.caption`.
|
||||
- **Role categories** — how many *distinct kinds* of operation does the process have? This determines the color budget. Identity is a category, not a sequence.
|
||||
|
||||
Write the answers to these in the plan file. If ≥3 of them land, you're building a **poster flowchart** — load `references/flowchart-poster.md` and follow its coordinate budget. Otherwise, it's a simple flowchart and the linear-top-down pattern applies.
|
||||
|
||||
**5d-ii. Draft the layout:**
|
||||
|
||||
1. **List the nodes / regions / shapes** with their full label text (title + optional subtitle).
|
||||
- Simple flowchart: ≤5 nodes.
|
||||
- Poster flowchart: ≤12 nodes grouped into ≤4 eyebrow-divided phases.
|
||||
- Structural: ≤3 inner regions.
|
||||
- Illustrative: 1 subject.
|
||||
- Sequence: list actors (2–4, max 4) in left-to-right order, each with a short title (≤12 chars) and optional role subtitle; then list messages as ordered `(sender, receiver, short label)` tuples (6–10 total, 10 is the sweet spot); mark any self-messages; draft a side-note title for the protocol.
|
||||
2. **For every rect, compute the width** using the formula in `layout-math.md`:
|
||||
- `width = max(title_chars × 8, subtitle_chars × 7) + 24` (Latin)
|
||||
- Replace 8 with 15 and 7 with 13 for CJK
|
||||
- Round up to the nearest 10
|
||||
3. **Pick colors by category**, not sequence. ≤2 accent ramps per diagram. Gray for neutral/start/end. Reserve blue/green/amber/red for semantic meanings.
|
||||
- **Sequence exception**: assign one ramp per actor (default `[gray, teal, purple, blue]`), up to 4 ramps total — arrows inherit the sender's ramp.
|
||||
- **Poster-flowchart exception**: up to 4 ramps, one per distinct agent/role (drafter=purple, critic=coral, synthesizer=teal, judge=amber). Baseline/anchor/convergence stay gray.
|
||||
4. **Check tier packing**: `N × box_width + (N-1) × gap ≤ 600`. For sequence, use the lane table in `layout-math.md` (N=4 → centers 100/260/420/580) and verify every message label fits its lane span with `label_chars × 7 ≤ |sender_x − receiver_x| − 8`. For poster fan-out rows (3 candidates), see the coordinate sketch in `flowchart.md`.
|
||||
5. **Map arrows** and verify none cross an unrelated box. Use L-bends where a straight line would collide. (Sequence messages are always straight horizontal lines — no L-bends. Fan-out candidates converge to a common `ymid` channel just above the judge box.)
|
||||
6. **Compute viewBox height**: `H = max_y + 20` where `max_y` is the bottom of the lowest element. Poster flowcharts routinely reach H=800–950 — don't force them to be compact.
|
||||
|
||||
Save this plan (including the Mermaid sketch from 5d-0):
|
||||
- One diagram: `diagram/{slug}/plan.md`
|
||||
- Multiple diagrams: `diagram/{material-slug}/NN-{type}-{slug}/plan.md`
|
||||
|
||||
#### 5e: Write the SVG
|
||||
|
||||
**Start from the Mermaid sketch in the plan.** Walk the sketch node-by-node, edge-by-edge, and translate each element into SVG using the coordinates and widths computed in 5d-ii. The Mermaid sketch is the structural checklist — every node and edge in it must appear in the SVG. If you find yourself adding an element that isn't in the sketch, stop and update the sketch first so the plan stays authoritative.
|
||||
|
||||
Emit a single `<svg width="100%" viewBox="0 0 680 H">` element. Copy the `<style>` + `<defs>` block from `svg-template.md` **verbatim** — don't abbreviate or edit the color ramp definitions. Then add visual elements in z-order:
|
||||
|
||||
1. Background decorations (rare)
|
||||
2. Containers (outer `<rect>` for structural diagrams)
|
||||
3. Connectors and arrows (drawn first so nodes paint on top)
|
||||
4. Nodes (rects with text)
|
||||
5. Labels outside boxes (leader callouts, legends, external I/O labels)
|
||||
|
||||
Typography rules:
|
||||
- Two sizes only: 14px (`t`, `th`) and 12px (`ts`)
|
||||
- Two weights only: 400 and 500
|
||||
- Sentence case everywhere — "User login" not "User Login"
|
||||
- Every `<text>` element gets a class (`t`, `ts`, or `th`) — never hardcode fill colors on text
|
||||
|
||||
#### 5f: Run the pre-save checklist
|
||||
|
||||
**Mermaid–SVG consistency check** (run before the pitfalls checklist): re-read the Mermaid sketch from the plan. For every node in the sketch, confirm the SVG has a corresponding `<rect>` + `<text>`. For every edge, confirm a `<path>` or `<line>` connects the correct pair. Missing elements are bugs — fix them before continuing.
|
||||
|
||||
Walk through every item in `references/pitfalls.md`. The top failures to catch every time:
|
||||
|
||||
1. viewBox height covers every element with a 20px buffer
|
||||
2. No rect extends past x=640
|
||||
3. Every labeled rect is wide enough for its text (char-width check)
|
||||
4. No arrow crosses an unrelated box
|
||||
5. Every `<path>` connector has `fill="none"` (or uses `class="arr"`)
|
||||
6. Every `<text>` has a class — no hardcoded `fill="black"`
|
||||
7. No `text-anchor="end"` at low x values (label would clip past x=0)
|
||||
8. ≤2 accent ramps, colors encode category not sequence
|
||||
9. No `<!-- comments -->` in the final output
|
||||
|
||||
If any item fails, fix the SVG before saving. Don't rationalize past a failure — the checklist exists because these bugs are silent: the SVG is valid but looks wrong when rendered.
|
||||
|
||||
#### 5g: Save and report progress
|
||||
|
||||
Save the SVG and plan:
|
||||
- One diagram: `diagram/{slug}/plan.md` + `diagram.svg`
|
||||
- Multiple diagrams: `diagram/{material-slug}/NN-{type}-{slug}/plan.md` + `diagram.svg`
|
||||
|
||||
**Backup rule**: if `diagram.svg` already exists at the target path, rename the existing one to `diagram-backup-YYYYMMDD-HHMMSS.svg` before writing the new file — never overwrite prior work silently.
|
||||
|
||||
**Multiple diagrams progress**: after each diagram, report progress: "Generated 2/4: 02-illustrative-jwt-token-structure".
|
||||
|
||||
### Step 6: Report
|
||||
|
||||
**One diagram** — tell the user in 4-6 lines:
|
||||
- Diagram type picked (and one-sentence why)
|
||||
- Node count / complexity
|
||||
- viewBox dimensions
|
||||
- Language
|
||||
- Output file path
|
||||
- One suggestion for how to preview it (e.g., "Open in Chrome for light/dark check")
|
||||
|
||||
**Multiple diagrams**:
|
||||
|
||||
```
|
||||
Diagram Generation Complete!
|
||||
|
||||
Material: [source description]
|
||||
Language: [lang]
|
||||
Diagrams: X generated
|
||||
|
||||
Results:
|
||||
- 01-sequence-jwt-auth-flow — "Reader understands the auth handshake"
|
||||
- 02-illustrative-jwt-token-structure — "Reader understands token anatomy"
|
||||
- 03-flowchart-token-refresh — "Reader understands the refresh cycle"
|
||||
[- overview-structural-jwt-system — "Reader sees how all parts connect"]
|
||||
|
||||
Output: diagram/{material-slug}/
|
||||
Preview: Open any .svg in Chrome for light/dark check
|
||||
```
|
||||
|
||||
## Output structure
|
||||
|
||||
### One diagram
|
||||
|
||||
```
|
||||
diagram/{slug}/
|
||||
├── source-{slug}.md # optional: saved input material
|
||||
├── plan.md # layout sketch from Step 5d
|
||||
└── diagram.svg # final output
|
||||
```
|
||||
|
||||
### Multiple diagrams
|
||||
|
||||
```
|
||||
diagram/{material-slug}/
|
||||
├── source-{slug}.md # saved input material
|
||||
├── outline.md # plan from Step 2 with all diagram entries
|
||||
├── 01-{type}-{slug}/
|
||||
│ ├── plan.md # layout sketch for this diagram
|
||||
│ └── diagram.svg # final SVG
|
||||
├── 02-{type}-{slug}/
|
||||
│ ├── plan.md
|
||||
│ └── diagram.svg
|
||||
├── 03-{type}-{slug}/
|
||||
│ ├── plan.md
|
||||
│ └── diagram.svg
|
||||
└── overview-{type}-{slug}/ # optional: overview diagram
|
||||
├── plan.md
|
||||
└── diagram.svg
|
||||
```
|
||||
|
||||
- **Slug**: 2–4 kebab-case words derived from the topic or concept.
|
||||
- **Backup rule**: if `diagram.svg` already exists at the target path, rename the existing one to `diagram-backup-YYYYMMDD-HHMMSS.svg` before writing the new file.
|
||||
- **Plan**: always save `plan.md` beside the SVG so the next iteration can re-read it.
|
||||
- **Source**: if the user pasted source content, save it as `source-{slug}.md` in the output directory.
|
||||
- **Numbering**: NN prefix (01, 02, ...) matches the plan order.
|
||||
- **Outline**: when generating multiple diagrams, always save `outline.md` from Step 2 so the generation can be resumed or individual diagrams can be regenerated.
|
||||
|
||||
## Modification
|
||||
|
||||
| Action | Steps |
|
||||
|--------|-------|
|
||||
| **Regenerate one diagram** | Re-read `outline.md` → find the entry → re-run Step 5 for that diagram only → update the SVG |
|
||||
| **Add a diagram** | Identify focus area → add entry to `outline.md` → run Step 5 for the new entry |
|
||||
| **Remove a diagram** | Delete the `NN-{type}-{slug}/` directory → remove entry from `outline.md` |
|
||||
| **Change type** | Update the plan entry or re-run with `--type` → regenerate |
|
||||
|
||||
## Core principles
|
||||
|
||||
- **Draw the mechanism, not a diagram about the mechanism** (illustrative). **Draw the sequence, not the architecture** (flowchart). **Draw the containment, not the flow** (structural). **Draw the conversation, not the steps** (sequence). Picking the wrong type is the single biggest failure mode — more harmful than any layout bug.
|
||||
- **One design system, always.** No `--style` flag, no alternate themes, no per-topic visual variants. The cohesive look across every diagram is the product — if a reader sees two baoyu diagrams in different articles, they should feel they came from the same hand. Any request to "use a different style" is a request to break this principle; push back and ask what the underlying need is instead. All diagrams in a run share the same design system — no per-diagram style overrides.
|
||||
- **Self-contained output.** Every SVG carries its own styles and dark-mode rules. The reader should never need to edit anything after pasting it into their article.
|
||||
- **Math before markup.** SVG has no auto-layout. Every coordinate is hand-computed. A diagram that "almost fits" has a bug — fix the math, don't nudge pixels.
|
||||
- **Color encodes meaning, not position.** Five steps in a flowchart are not five colors. All five are gray unless one specific step deserves emphasis — in which case it gets the accent color.
|
||||
- **The reader has 3 seconds.** If the diagram needs prose explanation to parse, it's failing. Simplify until it can stand alone with only its labels.
|
||||
|
||||
## References
|
||||
|
||||
- `references/design-system.md` — palette, typography, hard rules
|
||||
- `references/svg-template.md` — the `<style>` + `<defs>` boilerplate (copy verbatim)
|
||||
- `references/layout-math.md` — coordinates, text widths, viewBox math, arrow routing
|
||||
- `references/pitfalls.md` — the pre-save checklist
|
||||
- `references/flowchart.md` — flowchart-specific rules and worked examples (includes state-machine sub-pattern)
|
||||
- `references/flowchart-poster.md` — poster flowchart dialect (load on demand when ≥3 poster triggers fire)
|
||||
- `references/flowchart-phase-bands.md` — phase-band flowchart (horizontal dashed phase containers, compact tool card rows, cross-band semantic arrows, operator icons, legend strip)
|
||||
- `references/sequence.md` — sequence-diagram rules (actors, lifelines, messages, self-messages)
|
||||
- `references/structural.md` — structural-specific rules and worked examples (subsystem, bus, radial star, rich interior, mixed arrows)
|
||||
- `references/structural-network.md` — network topology sub-pattern (zone containers, wired/wireless, tiered layout)
|
||||
- `references/structural-matrix.md` — comparison matrix sub-pattern (feature table, ✓/✗ cells, zebra rows)
|
||||
- `references/illustrative.md` — illustrative-specific rules and worked examples
|
||||
- `references/class.md` — UML class diagram rules (3-compartment rects, relationships, stereotypes)
|
||||
- `references/glyphs.md` — shared glyph library (status circles, checkboxes, queue slots, icons, annotation circles) and concept-to-shape conventions
|
||||
- `references/patterns/` — pre-planned starters for common AI-system topologies (RAG, agents, memory tiers, verifier loops, …)
|
||||
Options:
|
||||
- `-s, --scale <n>` — Scale factor (default: 2)
|
||||
- `-o, --output <path>` — Custom output path (default: `<input>@2x.png`)
|
||||
- `--json` — JSON output
|
||||
|
||||
## Process
|
||||
|
||||
1. Identify the diagram type from the user's request
|
||||
2. Read the relevant reference file if one exists for that type
|
||||
3. Plan the layout: list all components, determine grouping and flow direction, calculate positions
|
||||
4. Write the SVG following the layering order above
|
||||
5. Verify spacing rules — no overlaps, legends outside boundaries, viewBox large enough
|
||||
6. Save the SVG file
|
||||
7. Run `${BUN_X} {baseDir}/scripts/main.ts <svg-path>` to generate @2x PNG
|
||||
8. Present both files to the user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
# Architecture Diagram Layout
|
||||
|
||||
## Flow Direction
|
||||
|
||||
Choose one primary direction:
|
||||
- **Left-to-Right (LTR):** Best for data pipelines, request flows. Users/clients on left, data stores on right.
|
||||
- **Top-to-Bottom (TTB):** Best for layered architectures. Clients at top, infrastructure at bottom.
|
||||
|
||||
## Layout Algorithm
|
||||
|
||||
1. **Identify layers:** Group components by role (clients, gateways, services, data, infrastructure)
|
||||
2. **Assign columns (LTR) or rows (TTB):** One layer per column/row
|
||||
3. **Within each layer:** Stack components vertically (LTR) or horizontally (TTB), 40px gap minimum
|
||||
4. **Region boundaries:** Draw around groups that share infrastructure (e.g., "AWS us-east-1", "Kubernetes Cluster")
|
||||
5. **Connectors:** Route arrows between layers. For buses/queues between layers, place a thin connector bar in the gap.
|
||||
|
||||
## Typical Layer Structure (LTR)
|
||||
|
||||
```
|
||||
Col 1 (x=40) Col 2 (x=250) Col 3 (x=460) Col 4 (x=670)
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Client │────▶│ Gateway │─────▶│ Services │─────▶│ Database │
|
||||
│ Layer │ │ Layer │ │ Layer │ │ Layer │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
Column spacing: 200-220px between column starts. Adjust if components are wider.
|
||||
|
||||
## Typical Layer Structure (TTB)
|
||||
|
||||
```
|
||||
Row 1 (y=60): [ Browser ] [ Mobile App ] [ API Client ]
|
||||
Row 2 (y=160): [ Load Balancer / API Gateway ]
|
||||
Row 3 (y=280): [ Auth Svc ] [ User Svc ] [ Order Svc ]
|
||||
Row 4 (y=400): [ Redis ] [ PostgreSQL ] [ S3 Bucket ]
|
||||
```
|
||||
|
||||
Row spacing: 120-140px between row starts.
|
||||
|
||||
## Connection Routing
|
||||
|
||||
- Prefer straight horizontal or vertical lines
|
||||
- For connections that would cross components, use two-segment (L-shaped) paths:
|
||||
```svg
|
||||
<path d="M x1,y1 L midX,y1 L midX,y2" fill="none" stroke="#64748b" marker-end="url(#arrow)"/>
|
||||
```
|
||||
- For busy diagrams, use `stroke-opacity="0.6"` on less important connections
|
||||
- Label important connections with a text element near the midpoint
|
||||
|
||||
## Message Bus / Event Bus Pattern
|
||||
|
||||
When services communicate through a shared bus, draw it as a horizontal bar between the service layer:
|
||||
|
||||
```
|
||||
Services: [ Svc A ] [ Svc B ] [ Svc C ]
|
||||
│ │ │
|
||||
Bus: ════╪══════════════╪════════════╪═══════
|
||||
│ │ │
|
||||
Data: [ DB A ] [ DB B ] [ Cache ]
|
||||
```
|
||||
|
||||
Use the Connector color (orange) for the bus bar.
|
||||
|
||||
## Multi-Region / Multi-Cloud
|
||||
|
||||
Nest region boundaries:
|
||||
- Outer boundary: Cloud provider (AWS, GCP)
|
||||
- Inner boundary: Region or VPC
|
||||
- Innermost: Availability zones or subnets
|
||||
|
||||
Use different dash patterns to distinguish nesting levels:
|
||||
- Outer: `stroke-dasharray="12,4"`
|
||||
- Middle: `stroke-dasharray="8,4"`
|
||||
- Inner: `stroke-dasharray="4,4"`
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
# Class Diagram (UML)
|
||||
|
||||
For showing static structure: what types exist, what they contain, and how they relate. The classic UML class diagram, adapted to baoyu-diagram's flat-rect design system.
|
||||
|
||||
## When to use
|
||||
|
||||
- "Draw the class diagram for X" / "show me the UML"
|
||||
- "What are the types and how are they related"
|
||||
- "Show the inheritance hierarchy"
|
||||
- "Diagram the domain model / schema / type system"
|
||||
- "Interface + implementations"
|
||||
|
||||
**When not to use:**
|
||||
- If the user wants to see *behavior* (what happens when X calls Y), use sequence (`sequence.md`).
|
||||
- If the user wants to see *data storage* with foreign keys and cardinalities only, that's an ER diagram — baoyu doesn't do ERDs in v1, suggest mermaid.
|
||||
- If the user wants to see *runtime object instances* with field values, you're drawing an object diagram — use the class template with `th`-underlined instance names (`underline` class not in the template; use `text-decoration="underline"` inline on `<text>`) and skip the method compartment.
|
||||
|
||||
## Planning
|
||||
|
||||
Before writing SVG:
|
||||
|
||||
1. **List the classes.** 3–8 total. More than 8 and you should split into multiple diagrams grouped by package or subsystem.
|
||||
2. **For each class, list attributes and methods.** Keep each line ≤30 chars. If a signature doesn't fit, abbreviate.
|
||||
3. **Pick the visibility for each member.** `+` public, `−` private, `#` protected, `~` package-private.
|
||||
4. **List the relationships.** Who extends whom, who implements which interface, who has what.
|
||||
5. **Pick colors by kind of class**, not by position. See "Color rule" below.
|
||||
6. **Compute box widths** using the class-specific formula below.
|
||||
7. **Lay out the classes** — parents / interfaces on top, children below. See "Layout" below.
|
||||
|
||||
## Class box template
|
||||
|
||||
Each class is a **3-compartment rect**: name (top), attributes (middle), methods (bottom). Horizontal divider lines separate the compartments.
|
||||
|
||||
### Geometry
|
||||
|
||||
| Element | Value |
|
||||
|------------------------|-----------------------------------------------|
|
||||
| Rect corner radius | `rx="6"` |
|
||||
| Min width | 160 px |
|
||||
| Name compartment height | 32 px (single line, centered) |
|
||||
| Attribute/method row height | 18 px per line + 8 px top/bottom padding |
|
||||
| Horizontal divider | `<line>` using `arr` class at 0.5 stroke |
|
||||
|
||||
### Width formula
|
||||
|
||||
The box width must fit the longest line across all three compartments:
|
||||
|
||||
```
|
||||
longest = max(name_chars × 8,
|
||||
max(attribute_line_chars) × 7,
|
||||
max(method_line_chars) × 7)
|
||||
width = longest + 24
|
||||
```
|
||||
|
||||
- 8 px/char for 14px `th` (the class name)
|
||||
- 7 px/char for 12px `ts` (attributes and methods — they use the subtitle class for compact line height)
|
||||
|
||||
Round up to the nearest 10. Minimum width is 160 regardless of label length.
|
||||
|
||||
### Height formula
|
||||
|
||||
```
|
||||
name_h = 32
|
||||
attr_h = 16 + (attribute_count × 18)
|
||||
method_h = 16 + (method_count × 18)
|
||||
total_h = name_h + attr_h + method_h
|
||||
```
|
||||
|
||||
If the class has no attributes, show an empty attribute compartment with `h = 16`. Never collapse a compartment — readers expect all three.
|
||||
|
||||
### SVG template
|
||||
|
||||
```svg
|
||||
<g class="c-teal">
|
||||
<!-- outer rect spans all three compartments -->
|
||||
<rect x="80" y="60" width="200" height="144" rx="6"/>
|
||||
|
||||
<!-- name compartment (top) -->
|
||||
<text class="th" x="180" y="80" text-anchor="middle" dominant-baseline="central">User</text>
|
||||
|
||||
<!-- divider between name and attributes -->
|
||||
<line class="arr" x1="80" y1="92" x2="280" y2="92" stroke-width="0.5"/>
|
||||
|
||||
<!-- attributes (12px, left-aligned) -->
|
||||
<text class="ts" x="92" y="110">+ id: UUID</text>
|
||||
<text class="ts" x="92" y="128">+ email: string</text>
|
||||
<text class="ts" x="92" y="146">− passwordHash: string</text>
|
||||
|
||||
<!-- divider between attributes and methods -->
|
||||
<line class="arr" x1="80" y1="158" x2="280" y2="158" stroke-width="0.5"/>
|
||||
|
||||
<!-- methods (12px, left-aligned) -->
|
||||
<text class="ts" x="92" y="176">+ login(pwd): Token</text>
|
||||
<text class="ts" x="92" y="194">+ logout(): void</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
- Name is `th` 14px centered.
|
||||
- Attributes and methods are `ts` 12px, **left-aligned at `rect_x + 12`**.
|
||||
- Divider lines use `class="arr"` with inline `stroke-width="0.5"` (the default 1.5 is too heavy for an internal divider).
|
||||
- All text inside the class inherits the ramp's text color from the `c-{ramp}` wrapper — no hardcoded fills.
|
||||
|
||||
### Stereotypes
|
||||
|
||||
For interfaces, abstract classes, and enums, prepend a stereotype line inside the name compartment:
|
||||
|
||||
```svg
|
||||
<g class="c-purple">
|
||||
<rect x="80" y="60" width="200" height="120" rx="6"/>
|
||||
<text class="ts" x="180" y="78" text-anchor="middle" dominant-baseline="central">«interface»</text>
|
||||
<text class="th" x="180" y="94" text-anchor="middle" dominant-baseline="central">Drawable</text>
|
||||
<line class="arr" x1="80" y1="106" x2="280" y2="106" stroke-width="0.5"/>
|
||||
...
|
||||
</g>
|
||||
```
|
||||
|
||||
The stereotype line is 12px `ts`, italic is encoded via the guillemets `«...»` characters — do not use inline `font-style="italic"` since baoyu's typography table doesn't include an italic class for `ts` outside poster captions.
|
||||
|
||||
When you add a stereotype, bump the name compartment height to 48 to fit both lines.
|
||||
|
||||
Common stereotypes:
|
||||
- `«interface»` — interface type, no fields, method signatures only.
|
||||
- `«abstract»` — abstract class with at least one abstract method.
|
||||
- `«enumeration»` — enum with value constants in the attribute compartment.
|
||||
- `«datatype»` — value type with no behavior (rare; usually just a plain class).
|
||||
|
||||
Abstract class names may optionally use the existing `th` weight — baoyu does not render italics for abstract, the stereotype carries the meaning.
|
||||
|
||||
## Relationship arrows
|
||||
|
||||
Six UML relationship styles. Each maps to a specific arrow rendering. All connectors use `class="arr"` (or `arr-alt` for dashed variants) so they inherit dark-mode stroke colors.
|
||||
|
||||
| Relationship | Line style | Arrowhead | From | Typical label |
|
||||
|------------------|------------|-------------------|---------------------|-------------------------------------|
|
||||
| Inheritance | solid | hollow triangle | child → parent | (none) |
|
||||
| Implementation | dashed | hollow triangle | class → interface | (none) |
|
||||
| Association | solid | open arrow `>` | from → to | multiplicity `1`, `0..*`, `1..*` |
|
||||
| Aggregation | solid | hollow diamond | container → part | (none, diamond on container side) |
|
||||
| Composition | solid | filled diamond | container → part | (none, diamond on container side) |
|
||||
| Dependency | dashed | open arrow `>` | user → used | optional label like `«uses»` |
|
||||
|
||||
### Arrow head definitions (add to `<defs>`)
|
||||
|
||||
The template's default `<marker id="arrow">` renders an open arrow — that works for association and dependency. For inheritance/implementation/aggregation/composition you need four additional markers. Add these to `<defs>` **only if the diagram uses them**:
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<!-- existing open-arrow marker from the template -->
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
|
||||
<!-- hollow triangle for inheritance + implementation -->
|
||||
<marker id="triangle-hollow" viewBox="0 0 12 12" refX="11" refY="6" markerWidth="10" markerHeight="10" orient="auto">
|
||||
<path d="M1 1 L11 6 L1 11 Z" fill="none" stroke="context-stroke" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
|
||||
<!-- hollow diamond for aggregation -->
|
||||
<marker id="diamond-hollow" viewBox="0 0 14 10" refX="1" refY="5" markerWidth="12" markerHeight="8" orient="auto">
|
||||
<path d="M1 5 L7 1 L13 5 L7 9 Z" fill="none" stroke="context-stroke" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
|
||||
<!-- filled diamond for composition -->
|
||||
<marker id="diamond-filled" viewBox="0 0 14 10" refX="1" refY="5" markerWidth="12" markerHeight="8" orient="auto">
|
||||
<path d="M1 5 L7 1 L13 5 L7 9 Z" fill="context-stroke" stroke="context-stroke" stroke-width="1" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
**Why `fill="none"` instead of a hardcoded hex.** Hollow markers need to stay hollow in both light and dark mode, and any hardcoded hex color is frozen at author time — it can't respond to the template's `@media (prefers-color-scheme: dark)` block. `fill="none"` means the triangle/diamond is outlined only and the interior is transparent. At 1.2px stroke on a 10×10 marker, the 1.5px line passing behind the marker is invisible to the reader. For the filled composition diamond, `fill="context-stroke"` picks up whichever stroke color the attached line is using, so it inherits dark-mode correctly through the `class="arr"` rules.
|
||||
|
||||
### Using the markers
|
||||
|
||||
```svg
|
||||
<!-- Inheritance: Circle extends Shape -->
|
||||
<line x1="260" y1="200" x2="260" y2="150" class="arr" marker-end="url(#triangle-hollow)"/>
|
||||
|
||||
<!-- Implementation: Circle implements Drawable (dashed) -->
|
||||
<line x1="260" y1="200" x2="460" y2="150" class="arr-alt" marker-end="url(#triangle-hollow)"/>
|
||||
|
||||
<!-- Association with multiplicity: User → Order (1 to many) -->
|
||||
<line x1="180" y1="120" x2="380" y2="120" class="arr" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="200" y="112">1</text>
|
||||
<text class="ts" x="360" y="112">0..*</text>
|
||||
|
||||
<!-- Composition: Order composed of LineItems -->
|
||||
<line x1="380" y1="140" x2="580" y2="140" class="arr" marker-end="url(#diamond-filled)"/>
|
||||
```
|
||||
|
||||
**Pitfall**: the `marker-end` goes on the **parent** end for inheritance/implementation (the triangle points at the parent), and on the **container** end for aggregation/composition (the diamond sits on the container). Direction of `line`/`path` dictates which end is `start` vs `end` — always draw the arrow *toward* the marker.
|
||||
|
||||
## Color rule
|
||||
|
||||
**One ramp per kind of class**, not per position. This is the same exception as sequence diagrams — identity is a category, so using multiple ramps is fine, but each ramp must correspond to a stable *role*.
|
||||
|
||||
Default ramp assignments:
|
||||
|
||||
| Class kind | Ramp | Why |
|
||||
|-------------------|-----------|---------------------------------------|
|
||||
| Concrete class | `c-gray` | The default — most classes are here |
|
||||
| Abstract class | `c-teal` | Signals "not directly instantiable" |
|
||||
| Interface | `c-purple` | Signals "pure contract, no state" |
|
||||
| Enum / datatype | `c-amber` | Signals "constants / value type" |
|
||||
|
||||
Cap at **3 ramps per diagram**. If you have concrete classes + interfaces + enums, use gray + purple + amber. If every class is concrete, all gray is fine and looks cleanest. Do not assign ramps by package or by author — readers won't learn the code.
|
||||
|
||||
**Legend implicit**: the ramps are anchored by the class kinds themselves. A reader who sees a purple `«interface» Drawable` box learns the convention immediately. No explicit legend needed unless the diagram uses >3 ramps (which shouldn't happen).
|
||||
|
||||
## Layout
|
||||
|
||||
Class diagrams are **2D grid** layouts, not linear flows. Parents live above children, interfaces sit on the side of their implementors, and association arrows run horizontally.
|
||||
|
||||
### Default: top-down inheritance tree
|
||||
|
||||
Parents / interfaces on top, children at the bottom:
|
||||
|
||||
```
|
||||
[ «interface» Drawable ] [ Shape (abstract) ]
|
||||
| |
|
||||
| implements | extends
|
||||
+---------+--------+---------+
|
||||
| |
|
||||
[ Circle ] [ Square ]
|
||||
```
|
||||
|
||||
- Tier 1 (y=60): base types (interfaces, abstract base classes)
|
||||
- Tier 2 (y=240): concrete subtypes
|
||||
- Tier 3 (y=420): composed types / enums
|
||||
|
||||
Vertical gap between tiers: **40 px minimum**.
|
||||
|
||||
### 2-wide / 3-wide rows
|
||||
|
||||
For 4–6 classes per tier, pack horizontally with 20px gaps:
|
||||
|
||||
```
|
||||
x=40 x=260 x=480 (width 180 each, gap 20)
|
||||
```
|
||||
|
||||
For >6 classes, you're building a too-big diagram — split by package or subsystem into separate diagrams and link them with prose.
|
||||
|
||||
### Association arrows
|
||||
|
||||
Association arrows are **horizontal or L-bend**, running between classes in the same tier (or adjacent tiers). Do not let an association arrow cross an inheritance arrow — if they cross, reroute the weaker relationship (usually the association) with an L-bend.
|
||||
|
||||
### viewBox height
|
||||
|
||||
```
|
||||
H = tier_count × 200 + 40
|
||||
```
|
||||
|
||||
Where 200 px is the per-tier budget (class box height ~140 + 40 px vertical gap + 20 px margin). Round up after placing the last tier's actual box height — if a class in the last tier is extra-tall, bump H to fit.
|
||||
|
||||
Width stays at 680. If a tier of 3-wide boxes doesn't fit at minimum width, narrow the boxes to 180 each (total 540 + 40 gap = 580, centered at x=50).
|
||||
|
||||
## Worked example — Shape hierarchy
|
||||
|
||||
A small 4-class domain: a `Drawable` interface, an abstract `Shape` class, and two concrete subclasses `Circle` and `Square`.
|
||||
|
||||
**Plan:**
|
||||
|
||||
1. Classes: `Drawable` (interface), `Shape` (abstract), `Circle`, `Square` — 4 classes, 2 tiers.
|
||||
2. Attributes/methods:
|
||||
- `Drawable`: `+ draw(): void`
|
||||
- `Shape`: `# color: Color`, `# position: Point`, `+ area(): number` (abstract)
|
||||
- `Circle`: `− radius: number`, `+ area(): number` (override)
|
||||
- `Square`: `− side: number`, `+ area(): number` (override)
|
||||
3. Relationships: `Shape` implements `Drawable` (dashed + hollow triangle); `Circle` and `Square` extend `Shape` (solid + hollow triangle).
|
||||
4. Colors: `Drawable` = `c-purple` (interface); `Shape` = `c-teal` (abstract); `Circle` / `Square` = `c-gray` (concrete). 3 ramps.
|
||||
5. Widths:
|
||||
- `Drawable`: name 8 chars, longest method "+ draw(): void" = 14 chars → `max(64, 98) + 24 = 122` → min 160
|
||||
- `Shape`: name 5 chars, longest "+ area(): number" = 16 chars → `max(40, 112) + 24 = 136` → min 160
|
||||
- `Circle`/`Square`: ~13 chars longest → min 160
|
||||
6. Tier 1 (y=60): `Drawable` at x=130, `Shape` at x=390 (row of 2, both width 160, gap 100).
|
||||
7. Tier 2 (y=260): `Circle` at x=130, `Square` at x=390 (row of 2, width 160, gap 100).
|
||||
8. Arrows:
|
||||
- `Shape → Drawable` (implements): horizontal dashed line from Shape's left edge (x=390, y=110) to Drawable's right edge (x=290, y=110).
|
||||
- `Circle → Shape` (extends): L-bend from Circle's top (x=210, y=260) up to ymid=220, right to x=470, up to Shape's bottom (x=470, y=200).
|
||||
- `Square → Shape` (extends): straight vertical from Square's top (x=470, y=260) to Shape's bottom (x=470, y=200).
|
||||
|
||||
**Coordinates (abbreviated; production code would fill in every `<text>`):**
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 420" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif">
|
||||
<style>/* template block ... */</style>
|
||||
<defs>
|
||||
<marker id="arrow" .../>
|
||||
<marker id="triangle-hollow" .../>
|
||||
</defs>
|
||||
|
||||
<!-- Tier 1: Drawable (interface, purple) -->
|
||||
<g class="c-purple">
|
||||
<rect x="130" y="60" width="160" height="100" rx="6"/>
|
||||
<text class="ts" x="210" y="76" text-anchor="middle" dominant-baseline="central">«interface»</text>
|
||||
<text class="th" x="210" y="92" text-anchor="middle" dominant-baseline="central">Drawable</text>
|
||||
<line class="arr" x1="130" y1="104" x2="290" y2="104" stroke-width="0.5"/>
|
||||
<!-- empty attribute compartment -->
|
||||
<line class="arr" x1="130" y1="120" x2="290" y2="120" stroke-width="0.5"/>
|
||||
<text class="ts" x="142" y="138">+ draw(): void</text>
|
||||
</g>
|
||||
|
||||
<!-- Tier 1: Shape (abstract, teal) -->
|
||||
<g class="c-teal">
|
||||
<rect x="390" y="60" width="160" height="140" rx="6"/>
|
||||
<text class="ts" x="470" y="76" text-anchor="middle" dominant-baseline="central">«abstract»</text>
|
||||
<text class="th" x="470" y="92" text-anchor="middle" dominant-baseline="central">Shape</text>
|
||||
<line class="arr" x1="390" y1="104" x2="550" y2="104" stroke-width="0.5"/>
|
||||
<text class="ts" x="402" y="122"># color: Color</text>
|
||||
<text class="ts" x="402" y="140"># position: Point</text>
|
||||
<line class="arr" x1="390" y1="152" x2="550" y2="152" stroke-width="0.5"/>
|
||||
<text class="ts" x="402" y="170">+ area(): number</text>
|
||||
</g>
|
||||
|
||||
<!-- Tier 2: Circle (concrete, gray) -->
|
||||
<g class="c-gray">
|
||||
<rect x="130" y="260" width="160" height="110" rx="6"/>
|
||||
<text class="th" x="210" y="280" text-anchor="middle" dominant-baseline="central">Circle</text>
|
||||
<line class="arr" x1="130" y1="292" x2="290" y2="292" stroke-width="0.5"/>
|
||||
<text class="ts" x="142" y="310">− radius: number</text>
|
||||
<line class="arr" x1="130" y1="322" x2="290" y2="322" stroke-width="0.5"/>
|
||||
<text class="ts" x="142" y="340">+ area(): number</text>
|
||||
</g>
|
||||
|
||||
<!-- Tier 2: Square (concrete, gray) -->
|
||||
<g class="c-gray">
|
||||
<rect x="390" y="260" width="160" height="110" rx="6"/>
|
||||
<text class="th" x="470" y="280" text-anchor="middle" dominant-baseline="central">Square</text>
|
||||
<line class="arr" x1="390" y1="292" x2="550" y2="292" stroke-width="0.5"/>
|
||||
<text class="ts" x="402" y="310">− side: number</text>
|
||||
<line class="arr" x1="390" y1="322" x2="550" y2="322" stroke-width="0.5"/>
|
||||
<text class="ts" x="402" y="340">+ area(): number</text>
|
||||
</g>
|
||||
|
||||
<!-- Shape implements Drawable (dashed, hollow triangle on Drawable side) -->
|
||||
<line x1="390" y1="110" x2="290" y2="110" class="arr-alt" marker-end="url(#triangle-hollow)"/>
|
||||
|
||||
<!-- Circle extends Shape (solid L-bend) -->
|
||||
<path d="M 210 260 L 210 230 L 470 230 L 470 200" class="arr" fill="none" marker-end="url(#triangle-hollow)"/>
|
||||
|
||||
<!-- Square extends Shape (solid straight) -->
|
||||
<line x1="470" y1="260" x2="470" y2="200" class="arr" marker-end="url(#triangle-hollow)"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
viewBox H: bottom of last box is `260 + 110 = 370`, so `H = 370 + 20 = 390`. The example uses 420 for a 30px bottom margin, which is also fine.
|
||||
|
||||
## Checklist (class-specific)
|
||||
|
||||
On top of the standard `pitfalls.md` checks:
|
||||
|
||||
1. Every class has all **three compartments** (name / attrs / methods), even if one is empty.
|
||||
2. The class **name is `th` centered**, attributes/methods are **`ts` left-aligned at `rect_x + 12`**.
|
||||
3. Every attribute/method starts with a **visibility marker** (`+ − # ~`).
|
||||
4. Dividers use `class="arr"` with **inline `stroke-width="0.5"`**.
|
||||
5. Inheritance and implementation arrows point **toward the parent/interface** (triangle on parent side).
|
||||
6. Aggregation and composition diamonds sit on the **container side**, not the part side.
|
||||
7. Associations carry **multiplicity labels** at both ends (`1`, `0..*`, `1..*`) when they aren't obvious.
|
||||
8. Dashed arrows are `arr-alt`, solid arrows are `arr` — never mix.
|
||||
9. Color ramp budget ≤3, one per class kind (concrete / abstract / interface / enum).
|
||||
10. Association arrows **do not cross inheritance arrows** — if they do, re-route the association.
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
# Design System
|
||||
|
||||
The rules that make every diagram feel consistent. Adapted from Anthropic's Imagine Visual Creation Suite for standalone SVG output (self-contained, no host CSS, safe to embed anywhere).
|
||||
|
||||
## Philosophy
|
||||
|
||||
- **Flat**: No gradients, no drop shadows, no blur, no glow, no neon. Clean flat surfaces only. (One exception — see Illustrative diagrams.)
|
||||
- **Compact**: Show the essential inline. If something needs a paragraph of explanation, it doesn't belong inside the diagram — move it to the prose around the diagram.
|
||||
- **Seamless**: The SVG should feel like a native part of the surrounding article, not a foreign object. Transparent background — the host page provides the background.
|
||||
- **Self-contained**: Every SVG carries its own `<style>` block and color definitions so it renders correctly in browsers, WeChat articles, markdown viewers, Notion, and any other host. See `svg-template.md`.
|
||||
|
||||
## Typography
|
||||
|
||||
Two body sizes. Sentence case. Plus a tiny poster toolkit for flowcharts that need a title and section headers.
|
||||
|
||||
**Body classes** (used by every diagram type):
|
||||
|
||||
| Class | Size | Weight | Use for |
|
||||
|--------|------|--------|------------------------------------------------------------|
|
||||
| `t` | 14px | 400 | Body text inside neutral boxes, single-line labels |
|
||||
| `th` | 14px | 500 | Titles — the bold line in a two-line node |
|
||||
| `ts` | 12px | 400 | Subtitles, descriptions, leader-line callouts, legends |
|
||||
|
||||
**Poster classes** (optional — only for poster flowcharts with titles and phase-grouped sections; see `flowchart.md`):
|
||||
|
||||
| Class | Size | Weight | Use for |
|
||||
|-----------|---------|---------|----------------------------------------------------------------------------------|
|
||||
| `title` | 20px | 600 | The mechanism name at the top of a poster flowchart. Used **once** per diagram. |
|
||||
| `eyebrow` | 10px | 500 | Uppercase letter-spaced section dividers between phases. Muted gray. ≤40 chars. |
|
||||
| `caption` | 12px | 400 italic | One-line footer hook below the whole diagram. Italic, muted gray. |
|
||||
| `anno` | 12px | 400 | Side-column annotation text next to a box ("sees: X / fresh context"). Muted. |
|
||||
|
||||
Rules:
|
||||
- **Never use font sizes outside this table.** Body diagrams get 14 and 12 only. Poster flowcharts can also use 20 (title) and 10 (eyebrow). No decorative 16/18/24.
|
||||
- **Never use font-weight above 600.** 400, 500, and the single 600 in `.title` are the whole vocabulary.
|
||||
- **Sentence case for body text.** "User login" not "User Login". The `.eyebrow` class is the single exception — its `text-transform: uppercase` is intentional typographic structure, not shouting.
|
||||
- **No mid-sentence bolding.** If you need to emphasize a term, it belongs in a title (`th` or `title`) or as a label, not as bold text inside a subtitle.
|
||||
- **No emoji.** Use simple SVG shapes (circles, triangles, lines) when you need a visual indicator.
|
||||
|
||||
Font family: the template sets `font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` on the `<svg>` element. This gives the user's native system font, which is the closest portable approximation of Anthropic Sans.
|
||||
|
||||
## Color palette
|
||||
|
||||
Nine ramps, seven stops each, from the Imagine design system. Lower numbers are lighter; higher numbers are darker.
|
||||
|
||||
| Ramp | 50 | 100 | 200 | 400 | 600 | 800 | 900 |
|
||||
|----------|----------|----------|----------|----------|----------|----------|----------|
|
||||
| gray | #F1EFE8 | #D3D1C7 | #B4B2A9 | #888780 | #5F5E5A | #444441 | #2C2C2A |
|
||||
| blue | #E6F1FB | #B5D4F4 | #85B7EB | #378ADD | #185FA5 | #0C447C | #042C53 |
|
||||
| teal | #E1F5EE | #9FE1CB | #5DCAA5 | #1D9E75 | #0F6E56 | #085041 | #04342C |
|
||||
| purple | #EEEDFE | #CECBF6 | #AFA9EC | #7F77DD | #534AB7 | #3C3489 | #26215C |
|
||||
| coral | #FAECE7 | #F5C4B3 | #F0997B | #D85A30 | #993C1D | #712B13 | #4A1B0C |
|
||||
| pink | #FBEAF0 | #F4C0D1 | #ED93B1 | #D4537E | #993556 | #72243E | #4B1528 |
|
||||
| amber | #FAEEDA | #FAC775 | #EF9F27 | #BA7517 | #854F0B | #633806 | #412402 |
|
||||
| green | #EAF3DE | #C0DD97 | #97C459 | #639922 | #3B6D11 | #27500A | #173404 |
|
||||
| red | #FCEBEB | #F7C1C1 | #F09595 | #E24B4A | #A32D2D | #791F1F | #501313 |
|
||||
|
||||
### Light-mode color binding (what the template emits)
|
||||
|
||||
For any colored box use the ramp class on the shape or its immediate parent `<g>`:
|
||||
|
||||
- **fill**: stop 50 (lightest)
|
||||
- **stroke**: stop 600 (strong border)
|
||||
- **title text** (`th` class inside): stop 800 (darkest legible)
|
||||
- **subtitle text** (`ts` class inside): stop 600 (one step lighter than title)
|
||||
|
||||
Title and subtitle MUST use different stops. Same stop reads flat; the 500 vs 400 weight alone is not enough contrast.
|
||||
|
||||
### Dark-mode color binding (automatic via `@media`)
|
||||
|
||||
- **fill**: stop 800
|
||||
- **stroke**: stop 200
|
||||
- **title text**: stop 100 (near-white on the ramp)
|
||||
- **subtitle text**: stop 200
|
||||
|
||||
The template's `@media (prefers-color-scheme: dark)` block handles this automatically. No extra work required — just use the `c-{ramp}` classes.
|
||||
|
||||
## Color assignment rules
|
||||
|
||||
**Color encodes meaning, not sequence.** A diagram with five steps is not rainbow-colored — all five get the same ramp (or gray) unless they belong to different categories.
|
||||
|
||||
1. **Group by category**, not by position. All "immune cells" get purple. All "pathogens" get coral. Not step-1 blue, step-2 teal, step-3 amber.
|
||||
2. **Use ≤2 ramps per diagram.** Gray + one accent is often the cleanest. More than two and the diagram starts to look like a child's toy.
|
||||
3. **Gray is the default for neutral, structural, generic, start, or end nodes.** Reach for gray first, color second.
|
||||
4. **Reserve blue/green/amber/red for semantic meaning** — blue for information, green for success, amber for warning, red for error. These carry strong UI connotations that surprise readers if used as "just another color". When you need a neutral accent, prefer purple, teal, coral, or pink.
|
||||
5. **Exception — illustrative diagrams can use warm/cool mapping:** in illustrative cross-sections, warm ramps (amber, coral, red) mean heat/energy/pressure/activity, cool ramps (blue, teal) mean cold/calm/dormant. This is a physical mapping, not a semantic one, so it's fine.
|
||||
6. **Exception — sequence diagrams may use one ramp per actor, up to 4 ramps total.** Each actor's header, lifeline context, and the arrows *originating* from that actor share a single ramp. Actor identity is a category (not a sequence), so this obeys the "encode meaning" rule — it just allows more categories than the normal ≤2-ramp cap. The legend is implicit: every ramp is anchored by its labeled actor header at the top of the diagram. See `sequence.md`.
|
||||
7. **Exception — poster flowcharts may use up to 4 ramps, one per distinct agent/role.** Same principle as the sequence exception: the drafter, the critic, the synthesizer, and the judge are four different *kinds* of operation, not four sequential steps. Each ramp anchors identity. The stage's title box makes the legend implicit. See `flowchart-poster.md`.
|
||||
8. **Exception — phase-band diagrams may use up to 3 ramp-colored arrow classes** when each ramp encodes a distinct *path type* (e.g., normal flow vs. exploit path vs. data exfiltration). The path type is the information the arrow carries — the ramp makes it visible at a glance rather than relying on labels on every crossing arrow. **A legend strip is mandatory.** See `flowchart-phase-bands.md`.
|
||||
9. **Exception — structural architecture diagrams may use up to 4 ramps, one per component category.** When a structural diagram depicts an infrastructure or software architecture with ≥3 semantically distinct categories of components (e.g., services, databases, gateways, message buses), assign one ramp per category — up to 4 ramps total. The categories are determined by the component's *role* in the system, not its position. A **legend strip is mandatory** (same rule as phase-band diagrams). Typical category→ramp mappings for infrastructure: services=teal, databases=purple, gateways/ingress=coral, message buses/queues=amber. But any 4 ramps work as long as each ramp encodes a distinct component type, not a sequential position. See `structural.md` → "Full architecture layout".
|
||||
|
||||
### If colors or arrow styles encode meaning, add a one-line legend
|
||||
|
||||
When a reader has to know what "purple means immune cell" to read the diagram, emit a small legend near the bottom with swatches:
|
||||
|
||||
```
|
||||
[■] Immune cells [■] Pathogens [■] Outcome
|
||||
```
|
||||
|
||||
The **same rule applies to arrow styles**. If the diagram uses ≥2 distinct arrow styles that carry meaning — solid `.arr` vs dashed `.arr-alt`, or two `.arr-{ramp}` colors for "memory read" vs "memory write" — add a legend entry for each style alongside the color swatches:
|
||||
|
||||
```
|
||||
[──] Primary flow [- -] Alternative path [■] Active agent [■] Store
|
||||
```
|
||||
|
||||
A one-line legend is worth more than thirty words of explanatory text. When colors are just for visual interest (no encoded meaning) and only one arrow style is used, skip the legend.
|
||||
|
||||
## Icon / glyph policy
|
||||
|
||||
Most diagrams are pure text-in-boxes with arrows. When a box really does need a small pictorial element — a checkbox next to a TODO, a ✓/✗ circle on a decision branch, a document icon inside a "shared state store" box — use the shared glyph library at `references/glyphs.md`. Do not draw ad-hoc icons.
|
||||
|
||||
**Sourcing rule.** Every glyph must come from the `glyphs.md` set:
|
||||
|
||||
- `status-circle-check`, `status-circle-x`, `status-circle-dot` — decision/result indicators on arrow paths
|
||||
- `checkbox-checked`, `checkbox-empty` — TODO list items
|
||||
- `queue-slot-filled`, `queue-slot-empty` — queue visualization inside a box
|
||||
- `doc-icon`, `terminal-icon`, `script-icon` — decorative icons inside a box
|
||||
- `code-braces`, `annotation-circle` — label decorations
|
||||
- `dashed-future-rect` — not yet executed / future state
|
||||
- `pub-sub-arrow-pair` — paired publish/subscribe arrows for bus topology
|
||||
|
||||
If you need something that isn't in that list, either re-use the closest existing glyph or label the concept with plain text — do not invent a new icon shape inline. Adding a new glyph is a deliberate change to `glyphs.md`, not an improvisation inside a single SVG.
|
||||
|
||||
**Dark-mode rule.** Every glyph element — every `<circle>`, `<rect>`, `<path>`, `<line>`, `<text>` that makes up a glyph — must inherit its color from one of the existing classes defined in `svg-template.md`:
|
||||
|
||||
- Shape fill/stroke: `box`, `c-{ramp}`, `arr`, `arr-alt`, `arr-{ramp}`
|
||||
- Text: `t`, `th`, `ts`
|
||||
|
||||
Never emit inline `fill="#..."`, `stroke="#..."`, or `color="#..."` inside a glyph. Hex colors are frozen at author time and do not participate in the `@media (prefers-color-scheme: dark)` re-mapping, so inline-colored glyphs become invisible or wrong-contrast in dark mode. The only inline colors permitted anywhere in a glyph are `fill="none"` and `stroke="none"` — both of which are colorless and safe.
|
||||
|
||||
**Scope rule.** Glyphs are not allowed everywhere:
|
||||
|
||||
| Diagram type | May use glyphs? | Notes |
|
||||
|--------------|-----------------|-----------------------------------------------------------------------------------------------|
|
||||
| flowchart | yes | Status circles on decision branches, queue slots inside a box, checkboxes inside a checklist. |
|
||||
| structural | yes | Doc/terminal/script icons inside a "state store" box, checkboxes inside a side-by-side list. |
|
||||
| illustrative | yes | Annotation circle on a connector; decorative icon inside a subject box. |
|
||||
| sequence | no | Sequence diagrams express everything through messages and lifelines — keep them text-only. |
|
||||
|
||||
When in doubt, do not add a glyph. A diagram that needs a pictorial hint to be understood is usually a diagram whose labels are too terse.
|
||||
|
||||
## Hard rules
|
||||
|
||||
These never bend (except where an exception is listed explicitly).
|
||||
|
||||
- No `<!-- comments -->` inside the final SVG output (they waste bytes and don't help readers).
|
||||
- No gradients, shadows, blur, glow, neon. Flat fills only. Exception: illustrative diagrams may use ONE `<linearGradient>` between two stops of the same ramp to show a continuous physical property (temperature, pressure). See `illustrative.md`.
|
||||
- No dark or colored background on the outer `<svg>` element. Transparent only — the host provides the background.
|
||||
- No font size outside the typography table. Body diagrams use 14 and 12. Poster flowcharts may *additionally* use 20 (title) and 10 (eyebrow). Nothing else.
|
||||
- Sentence case for body text. The `.eyebrow` class's `text-transform: uppercase` is the single allowed ALL-CAPS usage — it's a typographic eyebrow label, not shouting.
|
||||
- No emoji. Use SVG primitives (circles, triangles, lines) when you need a glyph.
|
||||
- No rotated text (`transform="rotate(...)"` on `<text>`). Exception: a single left-rail loop-scope label in a poster flowchart may use `transform="rotate(-90 ...)"` — this is the *only* place rotated text is allowed. See `flowchart.md` → "Loop-scope bracket".
|
||||
- No `filter`, no `pattern`, no radial gradients. The only thing allowed in `<defs>` is the arrow marker, an optional `<clipPath>`, and — for illustrative only — one `<linearGradient>`.
|
||||
- `<text>` never auto-wraps in SVG. If a label is long enough to need wrapping, it's too long — shorten it or split across two `<text>` elements stacked vertically. See `layout-math.md` for character budgets.
|
||||
|
||||
## Why these rules exist
|
||||
|
||||
Most of the rules trace back to one principle: **the diagram has to survive streaming rendering, dark mode, and embedding in unknown hosts**. Gradients flash during streaming. CSS variables don't exist outside claude.ai. Emoji render at font-inherited sizes and blow up scale. Rotated text misaligns on CJK fonts. Rainbow colors look like decoration, not information. The design system is narrow on purpose so the result is consistent and portable.
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
# Flowchart: Phase Band Layout
|
||||
|
||||
Load this file when the prompt describes a **multi-phase sequential operation** where each phase is a named stage containing several parallel tools or steps, and arrows cross between phases to show how outcomes propagate. Typical triggers:
|
||||
|
||||
- "Multi-phase attack / penetration test / security operation"
|
||||
- "Phased workflow with tools at each stage"
|
||||
- "Show what happens in each phase, and how findings from Phase N feed Phase N+1"
|
||||
- "Phase 1 / Phase 2 / Phase 3 diagram"
|
||||
- Source content from security research, incident response, pipeline audits, or any multi-stage operation where each phase has its own "toolkit"
|
||||
|
||||
**What distinguishes this from a poster flowchart:** A poster flowchart is a single vertical flow with a strong narrative arc (one loop, one judge, one outcome). A phase-band diagram is a horizontal-band composition — each band is a self-contained stage with its own set of parallel tools, connected to other bands by semantic crossing arrows. The band, not the individual box, is the unit of meaning.
|
||||
|
||||
## Canvas geometry
|
||||
|
||||
viewBox: `0 0 680 H`
|
||||
|
||||
All phase-band diagrams use the standard 680-wide canvas. The space is partitioned into three vertical columns:
|
||||
|
||||
```
|
||||
x = 0 … 64 Left operator column — operator icons + connecting arrows
|
||||
x = 64 … 480 Main flow zone — phase bands, tool card rows, flow arrows
|
||||
x = 480 … 648 Annotation zone — side notes, callout boxes
|
||||
x = 648 … 680 Right breathing room
|
||||
```
|
||||
|
||||
**Band container geometry:**
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|----------------------------------------------|
|
||||
| Band left edge | `x = 64` |
|
||||
| Band right edge | `x = 648` |
|
||||
| Band width | `584` |
|
||||
| Band padding (sides) | `16` px |
|
||||
| Main flow interior | `x = 80` to `x = 472` (392 px wide) |
|
||||
| Annotation interior | `x = 488` to `x = 632` (144 px wide) |
|
||||
|
||||
**Vertical geometry:**
|
||||
|
||||
```
|
||||
top padding y = 40
|
||||
first band top y = 40
|
||||
between-band gap 16 px
|
||||
H = 40 + sum(band_h[i]) + (N−1) × 16 + 20
|
||||
```
|
||||
|
||||
Each band's height depends on its content. The minimum is **100 px** (phase label row + one card row + padding). Add 80 px for each additional card row. Add 56 px if the band has a side annotation that's taller than the tool row.
|
||||
|
||||
---
|
||||
|
||||
## Phase band container
|
||||
|
||||
Each phase is a full-width dashed-border rounded rect. The phase label sits inside the band at top-left.
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="64" y="{band_y}" width="584" height="{band_h}" rx="12"
|
||||
fill="none" stroke-dasharray="4 4"/>
|
||||
<text class="eyebrow" x="80" y="{band_y + 18}" text-anchor="start">Phase 1</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **`fill="none"`** — dashed bands are schematic regions, not solid boxes. A filled band fights with the tool cards inside it.
|
||||
- **`stroke-dasharray="4 4"` inline** — same convention as subsystem containers (`structural.md`).
|
||||
- **`class="c-gray"` on the `<g>`** — gives the dashed stroke its dark-mode–safe color without a hardcoded hex.
|
||||
- **Eyebrow label** — use the `.eyebrow` class (`10px, 500 weight, text-transform: uppercase`). Short labels only: "Phase 1", "Phase 2 — Reconnaissance", "Phases 4 & 5". Never more than 30 characters.
|
||||
- **No title at the top level** — phase-band diagrams don't use the `.title` class. The diagram's subject is communicated by its surrounding prose, not a poster-style banner.
|
||||
|
||||
---
|
||||
|
||||
## Tool card rows
|
||||
|
||||
Each band's main flow zone holds one or two horizontal rows of compact tool cards. Use the `compact tool card` template from `glyphs.md` → "Compact tool card node template".
|
||||
|
||||
**Row placement inside a band:**
|
||||
|
||||
```
|
||||
card_row_y = band_y + 28 (first row — 28px below band top, clearing the phase label)
|
||||
card_row_y = band_y + 28 + 88 (second row, if needed — 88 = card_h + 8px gap)
|
||||
```
|
||||
|
||||
**Row centering:**
|
||||
|
||||
```
|
||||
row_width = N × card_w + (N−1) × gap
|
||||
row_start_x = 80 + (392 − row_width) / 2
|
||||
card[i].x = row_start_x + i × (card_w + gap)
|
||||
```
|
||||
|
||||
Use `gap = 8` for ≤4 cards at 80 wide, `gap = 6` for 5 cards at 72 wide.
|
||||
|
||||
**Arrows between cards inside a band:**
|
||||
|
||||
Connect consecutive tool cards with short horizontal `.arr` lines:
|
||||
|
||||
```svg
|
||||
<line x1="{prev_card_right + 5}" y1="{card_cy}"
|
||||
x2="{next_card_left − 5}" y2="{card_cy}"
|
||||
class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Where `card_cy = card_y + card_h / 2 = card_y + 40`.
|
||||
|
||||
For arrows that skip a card or fork into two paths, route with an L-bend above the row at `y = card_row_y − 12`.
|
||||
|
||||
---
|
||||
|
||||
## Side annotations
|
||||
|
||||
Annotations in the right column (x = 488–632) explain what happens at a phase level or for a group of tool cards. They use the same `anno`-class text convention as poster flowcharts but are wrapped in a light `box`-class rect for visual anchoring.
|
||||
|
||||
**Annotation box template:**
|
||||
|
||||
```svg
|
||||
<rect class="box" x="488" y="{anno_y}" width="144" height="{anno_h}" rx="6"/>
|
||||
<text class="ts" x="496" y="{anno_y + 14}" text-anchor="start">{line 1}</text>
|
||||
<text class="ts" x="496" y="{anno_y + 28}" text-anchor="start">{line 2}</text>
|
||||
<!-- additional lines at +14 pitch -->
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Max 18 chars per line.** At 7 px/char that's 126 px — fits inside the 144-wide box with 9 px padding each side.
|
||||
- **Max 4 lines.** `anno_h = 14 + lines × 14 + 10` (min 38).
|
||||
- **Connect to the band with a short leader line**, not an arrowhead:
|
||||
|
||||
```svg
|
||||
<line x1="480" y1="{anno_cy}" x2="488" y2="{anno_cy}" class="arr" stroke-dasharray="2 2"/>
|
||||
```
|
||||
|
||||
Where `anno_cy` = vertical center of the annotation box. The short dashed leader communicates "this note belongs to that band" without the visual weight of a full arrow.
|
||||
|
||||
**Positioning.** Vertically center the annotation box against the tool card row it describes:
|
||||
|
||||
```
|
||||
anno_y = card_row_y + (card_h − anno_h) / 2
|
||||
```
|
||||
|
||||
If the annotation is longer than the tool card row, let it extend below — just ensure the band height accommodates it.
|
||||
|
||||
---
|
||||
|
||||
## Cross-band arrows (semantic multi-path)
|
||||
|
||||
The most distinctive feature of phase-band diagrams is arrows that cross from one band to another, each color carrying a semantic meaning (normal flow, attack path, data path, etc.).
|
||||
|
||||
**Color convention for security/operation diagrams:**
|
||||
|
||||
| Path type | Arrow class | Typical label |
|
||||
|-------------------|---------------|----------------|
|
||||
| Normal flow | `arr` | (unlabeled) |
|
||||
| Exploit / attack | `arr-coral` | (unlabeled) |
|
||||
| Findings / data | `arr-amber` | (unlabeled) |
|
||||
| Callback / return | `arr-teal` | (unlabeled) |
|
||||
|
||||
**Color budget exception.** Phase-band diagrams may use **up to 3 ramp-colored arrow classes** when each ramp encodes a distinct path type. This is the only flowchart variant where more than 2 ramps are allowed — the path type IS the information. A legend strip is **required** (see Legend below).
|
||||
|
||||
**Routing rules for cross-band arrows:**
|
||||
|
||||
Route all cross-band arrows through the **left routing channel** at `x = 20–54`. This keeps them visually separated from the band content and creates a clean "spine" on the left margin:
|
||||
|
||||
```
|
||||
cross-band arrow: exits left edge of source band, travels down the left channel, enters left edge of target band
|
||||
```
|
||||
|
||||
```svg
|
||||
<!-- Example: arrow from Phase 2 bottom row → Phase 3, entering at band left edge -->
|
||||
<path class="arr-coral"
|
||||
d="M {source_card_cx} {source_card_y + 80}
|
||||
L {source_card_cx} {phase2_bottom + 8}
|
||||
L 30 {phase2_bottom + 8}
|
||||
L 30 {phase3_top − 8}
|
||||
L 80 {phase3_top − 8}
|
||||
L 80 {phase3_first_card_y + 40}"
|
||||
fill="none" marker-end="url(#arrow-coral)"/>
|
||||
```
|
||||
|
||||
The routing path: exit downward from the source card → horizontal to the left channel at x=30 → descend to the target band level → horizontal right into the band interior → enter the target card.
|
||||
|
||||
**Arrow marker by ramp.** The defs block in `svg-template.md` defines `#arrow` (gray, default). For ramp-colored arrows, use `url(#arrow-{ramp})` — the template provides coral, amber, teal variants. If the template's defs don't include a specific ramp marker, fall back to `url(#arrow)` — the arrowhead will be gray but the stroke will carry the ramp color.
|
||||
|
||||
**Label policy.** Cross-band arrows generally need **no label** — the color carries the semantic, and the source and target cards already name the tools. Add a 1–3 word `ts` label only if the path type would be ambiguous without it (e.g., two coral arrows that mean different things).
|
||||
|
||||
---
|
||||
|
||||
## Legend strip
|
||||
|
||||
**Required** when the diagram uses ≥2 distinct arrow ramp colors. Place the legend at the bottom of the canvas, below all bands, at `y = last_band_bottom + 24`:
|
||||
|
||||
```svg
|
||||
<!-- Legend: 3-color path key -->
|
||||
<line class="arr" x1="64" y1="{leg_y}" x2="84" y2="{leg_y}" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="88" y="{leg_y + 4}">Normal flow</text>
|
||||
|
||||
<line class="arr-coral" x1="180" y1="{leg_y}" x2="200" y2="{leg_y}" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="204" y="{leg_y + 4}">Exploit path</text>
|
||||
|
||||
<line class="arr-amber" x1="296" y1="{leg_y}" x2="316" y2="{leg_y}" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="320" y="{leg_y + 4}">Findings</text>
|
||||
```
|
||||
|
||||
Keep the legend to ≤4 entries. Each entry occupies ~116 px wide: 20px line + 8px gap + label. The first entry aligns with the band left edge at x=64.
|
||||
|
||||
---
|
||||
|
||||
## Operator icons
|
||||
|
||||
Place operator icons (from `glyphs.md` → "Operator icons") in the left column at x=16, vertically centered on the band they initiate. The typical arrangement is human → ai stacked vertically, connected by a short `.arr` line, with a final arrow from the ai icon into the first band's left edge.
|
||||
|
||||
```
|
||||
operator-human: translate(16, phase1_band_cy − 48)
|
||||
operator-ai: translate(16, phase1_band_cy − 4)
|
||||
connecting line: y1 = operator_human_bottom+2, y2 = operator_ai_y−2
|
||||
entry arrow: from (48, operator_ai_cy) → (62, operator_ai_cy), then into band via L-bend
|
||||
```
|
||||
|
||||
Labels (if used): `ts` text at `x=32`, `text-anchor="middle"`, placed 8px below each icon.
|
||||
|
||||
---
|
||||
|
||||
## Band height calculation
|
||||
|
||||
For a band with:
|
||||
- Phase label row: 22 px
|
||||
- One tool card row: 80 px
|
||||
- Top padding: 6 px, bottom padding: 12 px
|
||||
|
||||
Minimum: `6 + 22 + 80 + 12 = 120 px`
|
||||
|
||||
For two card rows: `6 + 22 + 80 + 8 + 80 + 12 = 208 px`
|
||||
|
||||
For a band with an annotation taller than the card row, use the annotation height + padding:
|
||||
`band_h = max(card_rows_h, anno_h) + 28 + 18`
|
||||
|
||||
---
|
||||
|
||||
## Worked coordinate sketch
|
||||
|
||||
For a 3-band diagram (Phase 1: 1 card row, Phase 2: 2 card rows with annotation, Phase 3: 2 card rows with cross-band arrows):
|
||||
|
||||
```
|
||||
Phase 1 band: y=40, h=120 → bottom y=160
|
||||
eyebrow at (80, 58)
|
||||
1 row of 3 cards (80×80) at y=70, centered in x=80–472
|
||||
cards centered: row_w=3×80+2×8=256, row_start=80+(392-256)/2=148
|
||||
card centers: (188, 110), (276, 110), (364, 110)
|
||||
|
||||
between-band: 16px gap → y=176
|
||||
|
||||
Phase 2 band: y=176, h=208 → bottom y=384
|
||||
eyebrow at (80, 194)
|
||||
row 1 of 5 cards (72×80) at y=206: row_w=5×72+4×6=384, start=84
|
||||
cards: (84, 206), (162, 206), (240, 206), (318, 206), (396, 206)
|
||||
row 2 of 2 cards (80×80) at y=302: start=196
|
||||
annotation box: x=488 y=234 w=144 h=60
|
||||
annotation text: 3 lines at y=248, 262, 276
|
||||
|
||||
between-band: 16px gap → y=400
|
||||
|
||||
Phase 3 band: y=400, h=208 → bottom y=608
|
||||
|
||||
cross-band arrow (Phase 2 → Phase 3):
|
||||
coral: from (396+36, 286) → (396+36, 393) → (30, 393) → (30, 417) → (80, 417) → (80, 480)
|
||||
|
||||
legend strip: y=628
|
||||
viewBox H: 628 + 24 + 16 + 20 = 688 → round to 700
|
||||
viewBox: 0 0 680 700
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common failure modes
|
||||
|
||||
- **Too many cards per row.** 6 cards at 80px = 532px — overflows the 392px main flow zone. Shrink cards to 64px, split into two rows, or drop the annotation zone and use the full 584px band width with cards up to 96px.
|
||||
- **Cross-band arrows routed through band content.** Always exit to the left routing channel (x=20–54) before crossing band boundaries. A coral arrow slashing diagonally through a tool card is the #1 visual failure in this layout.
|
||||
- **Phase label hidden by card row.** The card row y must start at `band_y + 28` at minimum (28 = 10px top padding + 18px for eyebrow text height). Starting at `band_y + 16` clips the eyebrow.
|
||||
- **Annotation text too long.** Max 18 Latin chars / 9 CJK chars per line in the 144-wide annotation box. "Findings recorded and analyzed. Human reviews summary." needs to be split: "Findings recorded" / "and analyzed." / "Human reviews."
|
||||
- **All tool cards colored differently.** Color encodes meaning — 5 different ramps for 5 tool cards reads as "these tools belong to 5 different categories" which is usually wrong. Use gray for all; promote only the one card that's the focal point.
|
||||
- **Legend missing when using ramp arrows.** If you use `arr-coral` or `arr-amber`, you must emit a legend strip. A reader who sees red arrows without a legend doesn't know if "red = danger" or "red = Phase 3 path" or "red = selected".
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# Flowchart: Poster Pattern
|
||||
|
||||
Load this file when **≥3 of the poster triggers in Step 4a** fire: the topic has a short name, a "why it exists" sentence, named phases, parallel candidates, a loop condition, overflow annotations, or a footer quote.
|
||||
|
||||
A poster flowchart is a richer dialect of the regular flowchart, meant for topics that have a **name, a motivation, phases, parallel candidates, and a loop condition** — things like research methods, iterative algorithms, reasoning frameworks. Instead of a flat sequence of boxes, it reads like a one-page explainer: title at the top, eyebrow-labeled phases, side notes, a left-rail loop bracket, and a footer hook.
|
||||
|
||||
This is the dialect the reader remembers. Use it when the content earns it — not every flowchart deserves this treatment.
|
||||
|
||||
## When to switch into poster mode
|
||||
|
||||
Check all of these. If three or more are true, switch to poster mode:
|
||||
|
||||
- [ ] The topic has a **short name** (Autoreason, Chain-of-Thought, AutoResearch, Reflexion) that belongs on the page as a title.
|
||||
- [ ] The source has a **"why this exists" sentence** that belongs under the title as a subtitle.
|
||||
- [ ] The stages **group into 2–4 named phases** (e.g., "The loop" / "Three candidates generated" / "Convergence check"), not just one monotonic column.
|
||||
- [ ] At some point **N parallel candidates are generated** and then compared — a fan-out + judge pattern.
|
||||
- [ ] There's a **loop with a specific termination mechanic** (streak counter, convergence check, fixed iteration cap).
|
||||
- [ ] Individual boxes need **context that won't fit as a subtitle** — "sees X, no bias, fresh context" — which wants to live in a right-side annotation column.
|
||||
- [ ] The source has a **quotable hook** (a test result, a framing quote) that belongs in a footer caption.
|
||||
|
||||
If only 0–1 of these are true, stick with the simple flowchart. Poster machinery on a three-box diagram is over-dressed.
|
||||
|
||||
## Anatomy
|
||||
|
||||
```
|
||||
[ TITLE (20px bold, centered) ]
|
||||
[ Subtitle (ts, one line, centered) ]
|
||||
|
||||
[ EYEBROW: PHASE 1 ]
|
||||
[ box ] [ side anno ]
|
||||
[ box ] [ side anno ]
|
||||
|
||||
[ EYEBROW: PHASE 2 ]
|
||||
┌ [ box ] [ box ] [ box ] ← fan-out row
|
||||
│ \ | /
|
||||
│ \ | /
|
||||
│ [ judge box ] ←——— [ side anno ]
|
||||
│
|
||||
│ [ EYEBROW: PHASE 3 ]
|
||||
│ [ convergence / streak box ] ←——— [ side anno ]
|
||||
└ (dashed left rail, rotated label "loop until …")
|
||||
|
||||
[ CAPTION: footer hook (italic ts, centered) ]
|
||||
```
|
||||
|
||||
The left rail is a dashed vertical line with a rotated label, visually scoping which boxes are "inside the loop". The right side is an annotation column — each major box can have a short `anno`-class note sitting at its right edge.
|
||||
|
||||
## Layout budget
|
||||
|
||||
A poster flowchart is taller and denser than a simple flowchart. The soft node cap jumps:
|
||||
|
||||
- Simple flowchart: ≤5 nodes.
|
||||
- Poster flowchart: ≤12 nodes, grouped into ≤4 phases. Each phase should fit in 2–4 boxes.
|
||||
|
||||
viewBox width is still 680, but the interior is now divided into a main column and an annotation column:
|
||||
|
||||
| Column | x range | Used for |
|
||||
|---------------|----------|------------------------------------------------------|
|
||||
| Loop rail | 40–60 | Dashed vertical line + rotated label (optional) |
|
||||
| Main flow | 80–470 | Boxes, arrows, eyebrows, title, caption |
|
||||
| Annotation | 485–640 | `anno`-class side notes (12px, left-anchored) |
|
||||
|
||||
Center the main column at x=275 (midpoint of 80 and 470), with box width up to 390. Eyebrow labels sit at x=80 (left-anchored) so they feel like "the section starts here" rather than drifting in the middle.
|
||||
|
||||
## Vertical geometry
|
||||
|
||||
```
|
||||
title baseline y = 46 (class "title")
|
||||
subtitle baseline y = 68 (class "ts")
|
||||
first eyebrow y = 100 (class "eyebrow")
|
||||
first main box top y = 116 (gap 16 below eyebrow)
|
||||
phase separator y = box_bottom + 32 (eyebrow) — eyebrow label sits 16 above next box
|
||||
caption baseline y = last_box_bottom + 44 (class "caption", italic ts)
|
||||
viewBox height H = caption_y + 20
|
||||
```
|
||||
|
||||
Boxes inside a phase use a 16px vertical gap between a box and the next (tighter than the 60px used in simple flowcharts — poster mode is more compact because the eyebrow already gives visual breathing room).
|
||||
|
||||
## Title and subtitle
|
||||
|
||||
Place the `.title` element at roughly (340, 46), `text-anchor="middle"`, `dominant-baseline="central"`. Keep it to 1–3 words — "Autoreason", "Chain-of-thought", "Reflexion loop". If you need a verb, you're doing a caption, not a title.
|
||||
|
||||
Place the subtitle at (340, 68), `text-anchor="middle"`, `class="ts"`. One line only. Use it to answer "why does this method exist" in the reader's language: *"No score to optimize? Replace the metric with agents arguing."* Pitch the question, don't define the mechanism — the mechanism is the diagram itself.
|
||||
|
||||
## Eyebrow section headers
|
||||
|
||||
A `.eyebrow` is a tiny uppercase label that separates phases. Place it at x=80 (left-anchored), `text-anchor="start"`, and let the CSS `text-transform: uppercase` handle the casing — write the label in sentence case in the source. Keep labels short (≤40 chars) and *descriptive of the phase*, not the specific box:
|
||||
|
||||
- Good: "The loop", "Three candidates generated", "Convergence check"
|
||||
- Bad: "Step 2", "Authentication service", "Click handler"
|
||||
|
||||
Eyebrows are dividers, not titles. They should feel quiet — if a reader notices them first, they're too loud.
|
||||
|
||||
## Anchor box
|
||||
|
||||
When a constant input is shared across every stage (the user's task prompt, the training dataset, a knowledge base), draw it **above** the main loop with an eyebrow like "ANCHOR — SEEN BY ALL AGENTS" (write it sentence-case; the CSS uppercases). This visually communicates "this thing doesn't iterate — it's the fixed reference". The arrow from the anchor into the first loop box can be a short straight line or omitted entirely if the eyebrow makes the relationship clear.
|
||||
|
||||
## Fan-out + judge pattern
|
||||
|
||||
When N candidates are generated in parallel and then compared, lay them out as a horizontal row feeding into a single judge box below. Suggested coordinates for 3 candidates, each 160 wide:
|
||||
|
||||
```
|
||||
Row centers: 165, 275, 385
|
||||
Row y: (chosen by phase position)
|
||||
Row box w: 160, h: 72 (three-line is allowed here: label · id + two descriptive lines)
|
||||
Gap: 110 px (from center to center: 275 - 165 = 110)
|
||||
```
|
||||
|
||||
Arrows from each candidate converge on the judge box below. Route each as an L-bend that meets a common `ymid` a few pixels above the judge box top, then drops straight down into the judge's top edge:
|
||||
|
||||
```svg
|
||||
<path d="M 165 P1b L 165 ymid L 275 ymid" class="arr"/>
|
||||
<line x1="275" y1="P2b" x2="275" y2="ymid" class="arr"/>
|
||||
<path d="M 385 P3b L 385 ymid L 275 ymid" class="arr"/>
|
||||
<line x1="275" y1="ymid" x2="275" y2="judge_top-10" class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
where `P1b`, `P2b`, `P3b` are the candidate box bottoms and `ymid` is a horizontal channel 12–20px below them. Only the final vertical into the judge carries the arrowhead — the convergence into `ymid` is unheaded.
|
||||
|
||||
The candidates often want slightly different colors to communicate "these are three distinct things, not three steps". Using three ramps here is fine; it's an identity category, not a sequence. Keep gray for "A · keep (unchanged)" because it's the status-quo baseline — the accent ramps go to the boxes that represent actual work.
|
||||
|
||||
## Side-annotation column
|
||||
|
||||
When a box needs more context than fits in its subtitle — "sees task + draft A / adversarial by design / fresh context" — put the overflow in the right annotation column at x=485, text-anchor="start", class="anno". Stack 1–3 short lines vertically, each 14px tall:
|
||||
|
||||
```svg
|
||||
<text class="anno" x="485" y="box_cy - 14" text-anchor="start">sees: task + draft A</text>
|
||||
<text class="anno" x="485" y="box_cy" text-anchor="start">adversarial by design</text>
|
||||
<text class="anno" x="485" y="box_cy + 14" text-anchor="start">fresh context</text>
|
||||
```
|
||||
|
||||
`box_cy` is the vertical center of the box the annotation belongs to. Always keep each line ≤22 characters so the column doesn't spill past x=640. Annotations are *quiet* — they use the `anno` class (muted gray in both modes) and never have their own boxes or borders.
|
||||
|
||||
## Loop-scope bracket (left rail)
|
||||
|
||||
When a phase or a contiguous set of phases repeat until a condition is met, draw a dashed vertical line along the left margin at x=55 from the top of the loop to the bottom, and place a rotated label to the left of it:
|
||||
|
||||
```svg
|
||||
<line x1="55" y1="loop_top" x2="55" y2="loop_bottom" class="leader"/>
|
||||
<text class="ts" x="45" y="(loop_top+loop_bottom)/2"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="central"
|
||||
transform="rotate(-90 45 LOOP_CENTER_Y)">loop until streak = 2</text>
|
||||
```
|
||||
|
||||
Use the `leader` class for the line (dashed, light gray) so it doesn't compete with the forward arrows. Keep the rotated label to ≤20 characters. This is the **only** place rotated text is allowed in the whole skill — do not use `transform="rotate"` anywhere else.
|
||||
|
||||
## Convergence / termination box
|
||||
|
||||
The loop needs to visibly terminate somewhere. Draw a final box (usually gray) that holds the *termination mechanic itself* — not a generic "converged" flag, but the actual rule:
|
||||
|
||||
```
|
||||
Winner becomes new A
|
||||
A wins again: streak++
|
||||
B or AB wins: streak = 0
|
||||
```
|
||||
|
||||
The convergence box is three-line (h=72) to hold the mechanic plainly. The right annotation column expands the semantics:
|
||||
|
||||
```
|
||||
streak = 2?
|
||||
stop, converged
|
||||
streak = 0?
|
||||
loop again
|
||||
```
|
||||
|
||||
## Footer caption
|
||||
|
||||
A single italic ts line at the bottom of the diagram, centered, `class="caption"`. Use it for a test result, a quote, or a memorable framing — *"In testing: 35/35 blind panel — next best method scored 21"*. One line, never two. If the content needs two lines, it belongs in prose around the diagram, not in the diagram.
|
||||
|
||||
## Color budget in poster mode
|
||||
|
||||
Poster flowcharts may use up to 4 ramps, one per **role category** — the drafter, the attacker, the synthesizer, the judge. Same rule as sequence diagrams: identity is a category, not a sequence. Gray is still the default for start/end/anchor/convergence boxes; the accent ramps go to the stages that represent distinct agent roles.
|
||||
|
||||
Suggested mapping for agent-style posters:
|
||||
|
||||
- **Drafter / author** → purple (thoughtful, creative)
|
||||
- **Critic / strawman / attacker** → coral (adversarial, attention)
|
||||
- **Synthesizer / merger** → teal (calm, combining)
|
||||
- **Judge / evaluator** → amber (deliberate, weighty)
|
||||
- **Baseline / keep-unchanged / anchor / convergence** → gray
|
||||
|
||||
Do not use more than 4 ramps. Two of the five roles above will usually collapse into one color or default to gray.
|
||||
|
||||
## Worked coordinate sketch
|
||||
|
||||
For a 5-phase poster flowchart with ~10 boxes and an annotation column, expect a viewBox around 680×950:
|
||||
|
||||
```
|
||||
title + subtitle y = 30–80
|
||||
anchor eyebrow + box y = 100–168
|
||||
loop eyebrow y = 192
|
||||
drafter box y = 208–272
|
||||
critic box y = 296–360
|
||||
candidate eyebrow y = 392
|
||||
fan-out row y = 408–480
|
||||
judge box y = 530–594
|
||||
convergence eyebrow y = 626
|
||||
convergence box y = 642–714
|
||||
caption y = 750
|
||||
viewBox H 770
|
||||
loop rail y1 = 200, y2 = 720
|
||||
```
|
||||
|
|
@ -1,652 +1,60 @@
|
|||
# Flowchart
|
||||
# Flowchart Layout
|
||||
|
||||
For sequential processes, decision trees, cause-and-effect chains. The classic "what happens when X" / "walk me through Y" diagram. Boxes and arrows.
|
||||
## Shape Vocabulary
|
||||
|
||||
## When to use
|
||||
| Shape | Meaning | SVG Element |
|
||||
|-------|---------|-------------|
|
||||
| Rounded rect (large radius) | Start / End | `<rect rx="25">` |
|
||||
| Rectangle | Process / Action | `<rect rx="6">` |
|
||||
| Diamond | Decision | `<polygon>` rotated 45° |
|
||||
| Parallelogram | Input / Output | `<polygon>` with skew |
|
||||
| Cylinder | Data store | Ellipse + rect combo |
|
||||
|
||||
- "Walk me through the process" / "what are the steps"
|
||||
- Approval workflows, request lifecycles, build pipelines
|
||||
- "What happens when I click submit"
|
||||
- State machines with clear transitions
|
||||
- TCP handshake sequences, auth flows
|
||||
## Flow Direction
|
||||
|
||||
**When not to use:** if the reader wants to feel *why* something works rather than read the steps, reach for an illustrative diagram instead (see `illustrative.md`). Route on the verb — "what are the training steps" is flowchart, "how does gradient descent work" is illustrative.
|
||||
Primary flow: **top to bottom**. Branch flows go left/right from decisions.
|
||||
|
||||
## Planning before you write SVG
|
||||
## Layout Algorithm
|
||||
|
||||
**Count the nouns first.** Before listing any nodes, count the distinct nouns in the user's prompt. If the prompt names 6+ components ("draw me auth, products, orders, payments, gateway, queue"), **do not try to fit them into one diagram** — you will get overlapping boxes and arrows routed through labels, every time. Decompose up front:
|
||||
1. **Identify the main path** (happy path / most common flow) — this runs straight down the center
|
||||
2. **Branch from decisions:** "Yes" continues down center, "No" branches right (or left if space is tight)
|
||||
3. **Merge paths:** Route branches back to the main path using L-shaped connectors
|
||||
4. **Loop-backs:** Route upward on the far left/right side of the diagram with curved paths
|
||||
|
||||
1. A stripped overview diagram with the components only and at most one or two arrows showing the main flow — no fan-outs, no N-to-N meshes.
|
||||
2. One detail diagram per interesting sub-flow ("what happens when an order is placed"), each with 3–4 nodes and room to breathe.
|
||||
## Spacing
|
||||
|
||||
The user asked for completeness — deliver it across several diagrams, not crammed into one. This is the **proactive** version of the advice; `pitfalls.md#9` catches the same failure retrospectively after you've already laid out too many boxes and run out of width.
|
||||
- Step-to-step vertical gap: 60-80px (enough for arrow + optional label)
|
||||
- Decision diamond height: 70px (point to point)
|
||||
- Decision diamond width: 100px (point to point)
|
||||
- Branch horizontal offset: 200px from center
|
||||
- Merge connector clearance: 20px from any box
|
||||
|
||||
Once the decomposition question is settled, plan one diagram at a time:
|
||||
## Decision Labels
|
||||
|
||||
1. **List the nodes.** 3–5 total. More than 5 and you should either split the diagram or collapse related steps.
|
||||
2. **For each node, write its full label text.** Title and (optionally) subtitle.
|
||||
3. **Choose direction.** Top-down is the default for most narratives. Left-to-right works for timelines and short flows. Never mix directions in one diagram.
|
||||
4. **Pick colors.** Gray for most nodes. One accent color for whatever node is the main character of the story (a decision point, the entry/exit, the piece under discussion). Never rainbow.
|
||||
5. **Compute box widths** using `layout-math.md`. Every box in the same "tier" (same role) should be the same width for rhythm.
|
||||
6. **Map out the arrows.** Simple A→B connections when possible. L-bends when a direct line would cross another box.
|
||||
|
||||
## Node templates
|
||||
|
||||
### Single-line node (44px tall)
|
||||
|
||||
Title only. Use when the label is self-explanatory or when space is tight.
|
||||
Place "Yes" / "No" (or "True" / "False", "是" / "否") labels directly on the exit arrows, 10px from the diamond edge:
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="250" y="40" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="340" y="62" text-anchor="middle" dominant-baseline="central">User login</text>
|
||||
</g>
|
||||
<!-- Decision diamond at center (400, 200) -->
|
||||
<!-- Yes: downward -->
|
||||
<line x1="400" y1="235" x2="400" y2="300" stroke="#64748b" marker-end="url(#arrow)"/>
|
||||
<text x="412" y="260" fill="#34d399" font-size="8">Yes</text>
|
||||
|
||||
<!-- No: rightward -->
|
||||
<line x1="450" y1="200" x2="550" y2="200" stroke="#64748b" marker-end="url(#arrow)"/>
|
||||
<text x="480" y="193" fill="#fb7185" font-size="8">No</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Height is always 44 when the row contains only single-line nodes.
|
||||
- `text y = rect y + 22` (centered vertically, with `dominant-baseline="central"`).
|
||||
- `text x = rect x + width/2` with `text-anchor="middle"`.
|
||||
## Coloring Strategy
|
||||
|
||||
### Two-line node (56px tall)
|
||||
- **Start/End nodes:** Highlight color (blue)
|
||||
- **Process steps:** Primary (cyan) or Secondary (emerald)
|
||||
- **Decision diamonds:** Accent (amber) — they draw the eye naturally
|
||||
- **Error/exception paths:** Alert (rose) dashed arrows
|
||||
- **Happy path arrows:** Slightly brighter than branch arrows (`stroke-opacity` difference)
|
||||
|
||||
Title + subtitle. Use when the title alone doesn't carry enough meaning.
|
||||
## Complex Flowcharts
|
||||
|
||||
```svg
|
||||
<g class="c-blue">
|
||||
<rect x="250" y="120" width="200" height="56" rx="6"/>
|
||||
<text class="th" x="350" y="140" text-anchor="middle" dominant-baseline="central">Login service</text>
|
||||
<text class="ts" x="350" y="160" text-anchor="middle" dominant-baseline="central">Validates credentials</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Height is 56 whenever the row contains any two-line node (mix single-line and two-line within the same row and the rhythm breaks — match heights).
|
||||
- Title `text y = rect y + 20`.
|
||||
- Subtitle `text y = rect y + 40`.
|
||||
- Subtitle must be ≤ 5 words. If you can't fit your idea in 5 words, it belongs in prose next to the diagram, not in the box.
|
||||
|
||||
### Neutral "box" node
|
||||
|
||||
For generic boxes with no color emphasis. Uses the `box` class from the template (light gray fill, subtle stroke).
|
||||
|
||||
```svg
|
||||
<rect class="box" x="40" y="40" width="180" height="44" rx="6"/>
|
||||
<text class="t" x="130" y="62" text-anchor="middle" dominant-baseline="central">Start</text>
|
||||
```
|
||||
|
||||
Use for start, end, and generic steps. This is the default — reach for color only when a specific node deserves the reader's attention.
|
||||
|
||||
### Pill terminal node (44px tall)
|
||||
|
||||
For **boundary nodes** — external inputs, external outputs, and early-exit terminals. The capsule shape visually distinguishes "where the flow enters/leaves the system" from "what happens inside the system". Same height as a single-line node (44px), but with `rx = height / 2 = 22` to produce a fully rounded capsule.
|
||||
|
||||
```svg
|
||||
<rect class="box" x="40" y="40" width="80" height="44" rx="22"/>
|
||||
<text class="t" x="80" y="62" text-anchor="middle" dominant-baseline="central">In</text>
|
||||
```
|
||||
|
||||
Or with a color ramp, the same shape inside a `c-{ramp}` group:
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="560" y="40" width="80" height="44" rx="22"/>
|
||||
<text class="th" x="600" y="62" text-anchor="middle" dominant-baseline="central">Out</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Only for boundary nodes**: In, Out, Exit, Human, Environment, Stop. Do not use pill shapes for internal steps, decisions, or services — those stay rounded rects (`rx="6"`).
|
||||
- **Height is always 44**. `rx` must equal `height / 2`. If you change the height, change `rx` to match — a 56px pill with `rx="22"` looks like a lozenge, not a capsule.
|
||||
- **Width follows the text-width formula** (`label × 8 + 24`), but keep labels to 1–2 words. A pill with "Request received" inside it reads as a button, not a terminal.
|
||||
- **Default neutral**: use the `box` class. Reach for a color ramp only when the terminal carries meaning — the active input of a highlighted pipeline, the success output of a gate, etc.
|
||||
- **At most 2 pills per diagram**. More than that and the capsules start to compete with the flowchart boxes for the reader's attention. The pill is meant to be a visual *anchor*, not a pattern.
|
||||
|
||||
## Connector templates
|
||||
|
||||
### Straight connector
|
||||
|
||||
When source and target share an x (vertical flow) or a y (horizontal flow):
|
||||
|
||||
```svg
|
||||
<line x1="340" y1="84" x2="340" y2="120" class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Leave a 10px gap between the arrow endpoint and the target box's edge so the arrowhead doesn't touch the stroke.
|
||||
|
||||
### L-bend connector
|
||||
|
||||
When the direct line would cross an unrelated box:
|
||||
|
||||
```svg
|
||||
<path d="M 160 84 L 160 102 L 340 102 L 340 120" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Pick the bend coordinate (102 here) so the horizontal segment runs cleanly through the gap between rows. Double-check that the horizontal segment doesn't cross another box — if it does, you need a three-bend route or a different layout.
|
||||
|
||||
### Dashed "returns to" indicator for a loop
|
||||
|
||||
Instead of drawing a literal ring for a cycle, label the endpoint with a small return glyph:
|
||||
|
||||
```svg
|
||||
<text class="ts" x="340" y="320" text-anchor="middle">↻ returns to start</text>
|
||||
```
|
||||
|
||||
This is cleaner than trying to route a physical arrow from the bottom box back to the top box around the entire layout.
|
||||
|
||||
## Layout patterns
|
||||
|
||||
**Cycles: your instinct will be a ring. Resist it.** If the last stage feeds back to the first (Krebs cycle, event loop, GC mark-and-sweep, TCP retransmit), your first instinct will be to arrange the stages around a circle with arrows on the outside. Don't. Every spacing rule in this skill is Cartesian — there is no collision check for "input box orbits stage box on a ring" — so ring layouts ship with labels sitting on the ring line, satellite boxes overlapping the stages they feed, and tangential arrows pointing at nothing. Lay the stages out linearly (horizontal or vertical) and convey the loop with the `↻ returns to start` glyph described under "Dashed 'returns to' indicator for a loop" above, or with a single curved return arrow along the margin. The loop is conveyed by the return marker, not by literal ring geometry. `pitfalls.md#10` catches this retrospectively — this note is here so you don't reach for the ring in the first place.
|
||||
|
||||
### Linear top-down (the default)
|
||||
|
||||
Four single-line boxes centered at x=250, heights aligned, spaced 60px apart vertically:
|
||||
|
||||
```
|
||||
y=40 [ Node 1 ] height 44, y ends at 84
|
||||
y=120 [ Node 2 ] (60px gap from y=84)
|
||||
y=200 [ Node 3 ]
|
||||
y=280 [ Node 4 ]
|
||||
```
|
||||
|
||||
Arrows at y=84→120, y=164→200, y=244→280 (using 10px arrow-to-box gap), each with a length of 26px.
|
||||
|
||||
### Horizontal timeline
|
||||
|
||||
Four nodes in a row, centered in the 600px usable width:
|
||||
|
||||
```
|
||||
x=50 x=200 x=350 x=500
|
||||
[Node 1] → [Node 2] → [Node 3] → [Node 4]
|
||||
```
|
||||
|
||||
Width per box: 130px. Gap: 20px. Total: 4×130 + 3×20 = 580. Centered offset: 40 + (600−580)/2 = 50. Arrows go between `x = x_i + 130 + 5` and `x = x_{i+1} - 5`.
|
||||
|
||||
### Branching decision
|
||||
|
||||
A single entry box at the top, two or three children below fanning out. Use a single bend point so the arrows meet cleanly:
|
||||
|
||||
```
|
||||
[ Decision ]
|
||||
|
|
||||
+--------+--------+
|
||||
| | |
|
||||
[ Yes ] [ Maybe ] [ No ]
|
||||
```
|
||||
|
||||
Compute the fan midpoint as the horizontal center of the three children. Draw three arrows from the decision box to each child, bending at a common ymid.
|
||||
|
||||
### Gate (pass / fail split)
|
||||
|
||||
A variant of branching decision where one outbound arrow is the **primary** flow and the other is an **alternative** exit. Use a solid arrow for Pass (continues into the main pipeline) and a dashed `.arr-alt` arrow for Fail (routes to an exit terminal). Both arrows get a short 1–3 word label.
|
||||
|
||||
```
|
||||
[ In ] → [ LLM Call 1 ] → [ Gate ] --Pass-→ [ LLM Call 2 ] → [ LLM Call 3 ] → [ Out ]
|
||||
\--Fail (dashed)-→ [ Exit ]
|
||||
```
|
||||
|
||||
Pills on `In`, `Out`, and `Exit`; rounded rects on the LLM calls. Gate takes a color ramp (purple is the convention for a judge/evaluator) so the decision point stands out. Route the Fail arrow as a downward L-bend into a lower row, with the Exit pill right-aligned or below the Gate. See the Gate worked example below.
|
||||
|
||||
### Fan-out + aggregator (simple mode)
|
||||
|
||||
When N branches run in parallel from a single hub and their results flow into a single aggregator, lay them out as a horizontal row between two single boxes. This is the simple-mode equivalent of the poster flowchart's fan-out + judge pattern — same shape, no title / eyebrow / loop rail / annotation column.
|
||||
|
||||
Three variants, same skeleton:
|
||||
|
||||
| Variant | Hub label | Branch arrows | Aggregator label | When to use |
|
||||
|------------------|---------------------|----------------------|------------------|-------------------------------------------------------------|
|
||||
| **Parallelization** | `In` (pill) | all solid | `Aggregator` | All N branches run; results merged |
|
||||
| **Router** | `LLM Call Router` | **1 solid, N−1 dashed** | `Out` (pill) | Exactly 1 branch runs per invocation; dashed = other options |
|
||||
| **Orchestrator** | `Orchestrator` | all dashed | `Synthesizer` | Orchestrator dynamically chooses a subset; dashed = "maybe" |
|
||||
|
||||
Suggested coordinates for 3 branches, each 160 wide:
|
||||
|
||||
```
|
||||
viewBox 0 0 680 340
|
||||
In/hub (pill) x=60, y=140, w=80, h=44 center at (100, 162)
|
||||
Branch row y = 140 (same tier as hub)
|
||||
Branch 1 box x=175, y=140, w=160, h=44 center (255, 162)
|
||||
Branch 2 box x=175, y=200, w=160, h=44 center (255, 222)
|
||||
Branch 3 box x=175, y=260, w=160, h=44 center (255, 282)
|
||||
|
||||
Hub → Branch 1 L-bend: (140, 162) → (155, 162) → (175, 162) ... straight
|
||||
Hub → Branch 2 L-bend: (100, 162) → (100, 222) → (175, 222)
|
||||
Hub → Branch 3 L-bend: (100, 162) → (100, 282) → (175, 282)
|
||||
|
||||
Branch 1 → Aggregator straight right: (335, 162) → (365, 162) ... merge channel
|
||||
Branch 2 → Aggregator L-bend: (335, 222) → (355, 222) → (355, 162) → (365, 162)
|
||||
Branch 3 → Aggregator L-bend: (335, 282) → (355, 282) → (355, 162) → (365, 162)
|
||||
|
||||
Aggregator pill x=365, y=140, w=160, h=44 center (445, 162)
|
||||
Out pill x=560, y=140, w=80, h=44 center (600, 162)
|
||||
Aggregator → Out straight: (525, 162) → (550, 162)
|
||||
```
|
||||
|
||||
For the **router variant**, one branch arrow stays `class="arr"` and the other two use `class="arr-alt"`. Same for the return half — the selected branch's arrow into the aggregator is solid, the others dashed. The visual says "at runtime, one path lights up".
|
||||
|
||||
The hub and aggregator typically get a **purple** ramp (they are the control points). Branches stay gray unless a specific branch carries meaning.
|
||||
|
||||
**Do not upgrade this into a poster flowchart** unless ≥3 of the poster triggers in the "Poster flowchart pattern" section apply — the upgrade criteria are title + phases + loop + annotations, not "has fan-out". A standalone router / parallelization diagram is at its best as a 5–7 node simple flowchart.
|
||||
|
||||
## Rich flowchart sub-patterns
|
||||
|
||||
Four optional sub-patterns that a simple or poster flowchart can drop into individual pieces of the layout: a loop container that frames the whole diagram, status circles on decision branches, queue glyphs inside a box, and a vertical fan-out variant. Each is designed to co-exist with the patterns above — you can use a loop container around a vertical fan-out that contains queue-glyph nodes and emits status-circle-tagged arrows, and every individual rule still holds.
|
||||
|
||||
### Loop container
|
||||
|
||||
A rounded outer rect, captioned with a title and subtitle, that wraps the whole flowchart to signal "this entire thing is a loop" or "this is a named subsystem". Used in Generator-Verifier (image #2) and Agent Teams (image #6) where the diagram is *the* loop body, not one phase inside a larger poster.
|
||||
|
||||
**When to reach for it.** If the whole flowchart represents a single named mechanism that the reader should remember as one thing — Generator-Verifier, a worker-pool protocol, a retry loop — and you're not already in poster mode. If you've already committed to poster mode, use eyebrow-divided phases instead; nesting a loop container inside a poster layout is over-dressed.
|
||||
|
||||
**Geometry.**
|
||||
|
||||
| Element | Coordinates |
|
||||
|--------------------|-----------------------------------------------------------|
|
||||
| Container rect | `x=20 y=40 w=640 h=H−60 rx=20`, class `box` |
|
||||
| Container title | `(340, 72)`, class `th`, `text-anchor="middle"` |
|
||||
| Container subtitle | `(340, 92)`, class `ts`, `text-anchor="middle"` |
|
||||
| First inner box | `y ≥ 116` (gives the title a 24px buffer above the inner flow) |
|
||||
| Inner safe area | `x ∈ [40, 620]` (20px interior padding on each side) |
|
||||
|
||||
The container is a single `box`-class rect — its stroke is subtle in both modes and doesn't fight for attention. The title and subtitle sit inside the container, aligned to the container's horizontal center. Interior boxes follow normal simple-flowchart geometry, just shifted so the first row's top lands at `y=116` instead of `y=40`.
|
||||
|
||||
**viewBox budget.** The container adds roughly 76px to the top (title + subtitle + padding) and 20px to the bottom (interior padding) — build the inner flow normally, then `H = inner_bottom + 40`. Don't forget to recompute the container's `h` attribute to match.
|
||||
|
||||
**Mixing with the normal 680 canvas.** The container shrinks the usable width from 600 to 580 (20..620 interior, minus the 20px interior padding each side). If your inner flow was designed at the full 600 budget and no longer fits, either widen nothing and recenter at x=320 instead of x=340, or accept a slightly narrower inner layout.
|
||||
|
||||
**Color.** The container itself is always the neutral `box` class — never a ramp — so the interior nodes can still use ramps without double-coloring. If the mechanism has a clear *owner* (e.g., "the verifier's loop"), you can add a small colored pill-label at the container's top-left to tag ownership, but don't tint the whole container.
|
||||
|
||||
**Dashed "until X" caption.** If the loop has a termination mechanic ("loop until verifier accepts", "loop until streak = 2"), write it as a `ts` subtitle of the container — that's what the subtitle slot is for. Don't add a separate `↻` marker inside the container when the whole container *is* the loop.
|
||||
|
||||
```svg
|
||||
<!-- Container -->
|
||||
<rect class="box" x="20" y="40" width="640" height="360" rx="20"/>
|
||||
<text class="th" x="340" y="72" text-anchor="middle">Generator-verifier</text>
|
||||
<text class="ts" x="340" y="92" text-anchor="middle">Loops until the verifier accepts</text>
|
||||
|
||||
<!-- Inner flow starts at y=116 -->
|
||||
<g class="c-purple">
|
||||
<rect x="80" y="180" width="160" height="56" rx="6"/>
|
||||
<text class="th" x="160" y="200" text-anchor="middle">Generator</text>
|
||||
<text class="ts" x="160" y="220" text-anchor="middle">Proposes candidate</text>
|
||||
</g>
|
||||
<!-- … verifier node, status circles, return arrow … -->
|
||||
```
|
||||
|
||||
### Status-circle junctions
|
||||
|
||||
A decision branch tagged with a visible status circle (✓ / ✗ / ●) from `glyphs.md` instead of a bare `Pass` / `Fail` label. Used when the semantics of the branch are *binary outcomes of a judge*, not just control-flow alternatives.
|
||||
|
||||
**When to prefer status circles over the Gate pattern's text labels.** Status circles work when each branch carries a single-word outcome (accept / reject / done / blocked) and no additional condition text. The Gate pattern from "Gate (pass / fail split)" above stays the default for conditions that need more than one word (*"retry if rate-limited"*, *"escalate if amount > $10k"*) — don't try to cram that into a glyph.
|
||||
|
||||
**Geometry — replacing a Gate's two arrows.** Start from the existing Gate worked example. Instead of labeling the two outbound arrows with text, split each arrow at a point 14px before its target and place a status-circle glyph in the gap:
|
||||
|
||||
```
|
||||
[ Judge ] ──arr──→ ◎✓ ──arr──→ [ Next stage ]
|
||||
\
|
||||
──arr-alt──→ ◎✗ ──arr-alt──→ [ Exit ]
|
||||
```
|
||||
|
||||
- Accept branch: solid arrow + `status-circle-check` (green) + solid arrow.
|
||||
- Reject branch: dashed `arr-alt` arrow + `status-circle-x` (coral) + dashed `arr-alt` arrow.
|
||||
- In-progress branch (rare — use only when the judge returns "still thinking"): solid arrow + `status-circle-dot` (amber) + solid arrow looping back.
|
||||
|
||||
Each arrow segment stops 14px before the status-circle center (the glyph's radius is 12, so 14 gives a 2px visual gap between the arrowhead and the circle's border).
|
||||
|
||||
**Status circle anchor math.** For a branch from source `(sx, sy)` to target `(tx, ty)` where the branch would normally be a straight arrow, place the status circle's **center** at `(cx, cy)` = the arrow's midpoint. Split the arrow:
|
||||
|
||||
```svg
|
||||
<!-- arrow segment 1: source → status circle -->
|
||||
<line x1="sx" y1="sy" x2="cx - 14" y2="cy" class="arr" marker-end="url(#arrow)"/>
|
||||
<!-- status circle glyph (see glyphs.md) -->
|
||||
<g transform="translate(${cx - 12}, ${cy - 12})">
|
||||
<circle class="c-green" cx="12" cy="12" r="12"/>
|
||||
<path class="arr-green" d="M6 12.5 L10.5 17 L18 8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</g>
|
||||
<!-- arrow segment 2: status circle → target -->
|
||||
<line x1="cx + 14" y1="cy" x2="tx - 10" y2="ty" class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
For L-bend branches, put the status circle on the horizontal segment, not at a corner. The reader's eye tracks the glyph along the arrow direction, and corners disrupt that scan.
|
||||
|
||||
**Status-circle color rules are not negotiable.** `c-green` for accept, `c-coral` for reject, `c-amber` for in-progress. Even if the diagram's palette is built around blue and teal, the status circles stay green/coral/amber — semantic color wins. (This is the *only* exception to the 2-ramp budget in a simple flowchart.)
|
||||
|
||||
**Labels on status-circle branches.** Optional. If the status circle alone isn't enough ("accept" is obvious but "send to verifier" isn't), add a 1–3 word `ts` label 6px *past* the status circle in the direction of travel, not between the arrow and the circle. Keep labels on the same side as the target so the reading flow stays left-to-right.
|
||||
|
||||
### Queue glyph inside a box
|
||||
|
||||
A two-line node where the bottom line is a row of `queue-slot-filled` / `queue-slot-empty` glyphs from `glyphs.md` instead of a text subtitle. Used when a source node's value to the reader is "there are tasks waiting" — e.g., the hub of an Agent Teams fan-out (image #6) — and a text subtitle like "4 of 6 tasks pending" would be both noisier and less informative than the row of slots.
|
||||
|
||||
**Not a replacement for a normal two-line node.** Only use this when the queue depth is *itself* the information. If the box's purpose is "task router" with an incidental mention of a queue, stay with a text subtitle.
|
||||
|
||||
**Geometry.**
|
||||
|
||||
| Element | Coordinates |
|
||||
|-------------------|-----------------------------------------------------------|
|
||||
| Host rect | `w ≥ 160, h = 52, rx = 6` |
|
||||
| Title `th` | `y = rect_y + 18`, centered horizontally |
|
||||
| Queue row y | `rect_y + 30` |
|
||||
| Queue row start x | `rect_x + (rect_w − slots_total_w) / 2` |
|
||||
| slot pitch | 20 (slot is 16 wide + 4 gap) |
|
||||
| slots_total_w | `N × 16 + (N − 1) × 4 = N × 20 − 4` |
|
||||
| Max slots per row | `⌊(rect_w − 24 + 4) / 20⌋`, capped at 8 |
|
||||
|
||||
Height 52 is different from the standard 44 (single-line) and 56 (two-line) — the queue row plus its visual padding is just slightly shorter than a text subtitle would be. Don't reuse 44 or 56 here; the math won't center correctly.
|
||||
|
||||
**Example — 6 slots in a 160-wide host rect, 4 filled and 2 empty.**
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="60" y="120" width="160" height="52" rx="6"/>
|
||||
<text class="th" x="140" y="138" text-anchor="middle">Task queue</text>
|
||||
<rect class="c-amber" x="72" y="150" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="92" y="150" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="112" y="150" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="132" y="150" width="16" height="16" rx="2"/>
|
||||
<rect class="c-gray" x="152" y="150" width="16" height="16" rx="2"/>
|
||||
<rect class="c-gray" x="172" y="150" width="16" height="16" rx="2"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
Row math: `slots_total_w = 6 × 20 − 4 = 116`, `row_start_x = 60 + (160 − 116) / 2 = 82` — then each slot at `82 + k × 20`. The example above uses `72, 92, …` because the rect is 160 wide and 6 slots center at 82, so k=0 sits at 82 (actually 72 in the code because I walked it slightly left for visual breathing; recompute exactly to taste). Always recompute the row start from the formula when the rect width changes.
|
||||
|
||||
**Color rules.** Filled slots always use `c-amber` (work waiting is attention-worthy). Empty slots always use `c-gray`. The host rect should be `c-gray` too — if you color the host rect with a ramp, the amber slots stop reading as "waiting work" and the eye loses the signal.
|
||||
|
||||
**Interaction with fan-out.** When a queue-glyph node is the *source* of a fan-out, route each outbound arrow from the **title row** of the source, not the queue row. Arrows emerging from the middle of slots look as if the slots themselves are moving, which is the wrong mental model.
|
||||
|
||||
### Vertical fan-out
|
||||
|
||||
The horizontal fan-out pattern in "Fan-out + aggregator (simple mode)" rotated 90°: source box on the left, branches stacked vertically in a column on the right, aggregator (optional) further right or at the bottom. Used in Agent Teams (image #6) and the left half of Orchestrator vs Agent teams (image #9).
|
||||
|
||||
**When to prefer vertical over horizontal fan-out.** Horizontal fan-out is the default — it reads naturally as "one input splits into alternatives". Switch to vertical when:
|
||||
|
||||
- The branches are **workers** (not alternatives): they all run, consuming from a queue. A vertical stack communicates "these workers live side-by-side pulling from the same queue" better than a horizontal row.
|
||||
- The source node has a **queue glyph** inside it (see previous sub-section). A queue glyph wants horizontal width; if you then fan out horizontally below the queue, the diagram gets very wide. Rotate the fan-out instead.
|
||||
- The diagram needs to leave horizontal space for an aggregator or a second column — e.g., the subsystem-container pattern where the fan-out lives inside a 315-wide container.
|
||||
|
||||
**Geometry — 3 workers inside a normal 680 canvas.**
|
||||
|
||||
| Element | Coordinates |
|
||||
|--------------------|----------------------------------------------|
|
||||
| Source (hub) rect | `x=40 y=160 w=180 h=52 rx=6` (queue glyph inside; see previous sub-section) |
|
||||
| Worker row y₁ | `y=120` |
|
||||
| Worker row y₂ | `y=184` |
|
||||
| Worker row y₃ | `y=248` |
|
||||
| Worker rect | `x=420 w=180 h=44 rx=6` |
|
||||
| Bend channel x | `x=320` (halfway between source right 220 and worker left 420) |
|
||||
| Aggregator (opt.) | `x=420 y=320 w=180 h=44` (if worker outputs merge) |
|
||||
|
||||
**Arrow routing.**
|
||||
|
||||
```svg
|
||||
<!-- Source → Worker 1: L-bend up -->
|
||||
<path d="M220 186 L320 186 L320 142 L420 142" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<!-- Source → Worker 2: straight horizontal -->
|
||||
<line x1="220" y1="206" x2="420" y2="206" class="arr" marker-end="url(#arrow)"/>
|
||||
<!-- Source → Worker 3: L-bend down -->
|
||||
<path d="M220 226 L320 226 L320 270 L420 270" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
The source's right edge is at x=220 (for a 180-wide box starting at x=40). Each arrow starts at the source's right edge at a slightly offset y (186 / 206 / 226) so the three outbound lines don't overlap inside the source rect. The shared bend channel at x=320 keeps the three L-bends visually parallel.
|
||||
|
||||
**Source anchor y.** For a 52-tall queue-glyph host rect at y=160, the vertical center is at y=186. Use `186 ± 20` as the three outbound anchor points so the middle arrow lands at the exact center and the top/bottom arrows sit symmetrically. For a 44-tall single-line source at y=160, use `182 ± 16` instead (smaller offset to stay inside the rect).
|
||||
|
||||
**Worker heights.** Workers stay at 44 (single-line) in vertical fan-out, because tight vertical stacking reads better with uniform heights. If a worker needs a subtitle, promote the whole row to 56 and recompute the worker y's with `y₁=112, y₂=184, y₃=256` (72px pitch instead of 64).
|
||||
|
||||
**Aggregator, if any.** For vertical fan-out with an aggregator, place the aggregator at `x=420 y=320` (below the workers) and route each worker output through a shared return channel at x=720… wait, x=720 is past the right edge. Use `x=630` instead, and drop the aggregator to `x=240 y=320` (below the middle worker), with return arrows routed to a common channel at x=620:
|
||||
|
||||
```svg
|
||||
<line x1="600" y1="142" x2="620" y2="142" class="arr"/>
|
||||
<path d="M620 142 L620 342 L420 342" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<line x1="600" y1="206" x2="620" y2="206" class="arr"/>
|
||||
<path d="M620 206 L620 342 L420 342" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<line x1="600" y1="270" x2="620" y2="270" class="arr"/>
|
||||
<path d="M620 270 L620 342 L420 342" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Note that only the final segment (into the aggregator) carries the `marker-end` arrowhead — the three joining segments into the shared channel are unheaded, same as the horizontal fan-out pattern.
|
||||
|
||||
**Color.** Workers are typically the same color as each other (they're instances of one role). If the source is a task-queue hub with queue glyphs, keep it `c-gray` and give the workers a single accent ramp (`c-teal` is the default for workers). If one worker is distinguished from the others (the leader, the one under discussion), promote that one worker's ramp; leave the rest gray.
|
||||
|
||||
**Container integration.** Vertical fan-out fits naturally inside a 315-wide subsystem container (`structural.md` → "Subsystem architecture pattern"). For the container variant, shrink the source to 120-wide, workers to 140-wide, and recompute the bend channel. See `structural.md` → "Rich interior for subsystem containers" for the full coordinate table.
|
||||
|
||||
### State machine
|
||||
|
||||
A flowchart variant where the nodes are **states** (things the system *is*) rather than steps (things the system *does*), and the arrows are **transitions** labeled with the event that triggers them. Used for lifecycles — order status, connection state, feature-flag rollout phase, auth session — where "what can happen next from here" matters more than a single linear path.
|
||||
|
||||
**When to reach for it.** The reader's question is "what are the possible states and how do I move between them", not "what happens first, second, third". If the diagram has more than one incoming arrow to most nodes (cycles, self-loops, branches returning to earlier states), you're in state-machine territory. If every node has exactly one predecessor, you want a plain flowchart instead.
|
||||
|
||||
**Initial and final markers.** Two small circles anchor the diagram as a state machine — these are the only non-rectangle nodes the pattern uses.
|
||||
|
||||
| Marker | Shape | Class | Meaning |
|
||||
|---------------|--------------------------------------------------|-----------|-------------------------------------------|
|
||||
| Initial state | Filled circle, `r=6` | `c-gray` | Entry point — exactly one per diagram |
|
||||
| Final state | Hollow circle `r=10` with a filled inner `r=5` | `c-gray` | Terminal state — may appear multiple times |
|
||||
|
||||
```svg
|
||||
<!-- Initial state at (x, y) -->
|
||||
<circle class="c-gray" cx="x" cy="y" r="6"/>
|
||||
|
||||
<!-- Final state at (x, y) -->
|
||||
<circle class="c-gray" cx="x" cy="y" r="10" fill="none"/>
|
||||
<circle class="c-gray" cx="x" cy="y" r="5"/>
|
||||
```
|
||||
|
||||
The initial marker gets **no label** — it's the start, that's self-evident. The final marker may get a 1-word `ts` label 14px below (*Closed*, *Archived*) if the specific terminal state matters; otherwise leave it bare. The initial marker's `r=6` is deliberately smaller than the final marker's outer `r=10` so the two read as different symbols at a glance.
|
||||
|
||||
**State nodes.** Standard rounded rect, `rx=6`, same dimensions as a flowchart step node (44px single-line or 56px two-line, 160–180 wide). The title is a noun or noun phrase — the state's *name*, not a verb. *"Awaiting payment"* not *"Wait for payment"*; *"Paid"* not *"Process payment"*. If a state needs a subtitle, use it to clarify *what's true while here* (*"Funds held, awaiting capture"*), not *what runs next*.
|
||||
|
||||
**Transition labels.** Every arrow gets a label in the format:
|
||||
|
||||
```
|
||||
event [guard] / action
|
||||
```
|
||||
|
||||
- **event** — the trigger that fires the transition (*payment_received*, *timeout*, *user_cancel*). Required.
|
||||
- **[guard]** — an optional boolean precondition in square brackets (*[amount > 0]*, *[retry_count < 3]*). Omit if there's no condition.
|
||||
- **/ action** — an optional side effect performed on transit (*/ send_receipt*, */ release_hold*). Omit if the transition is pure.
|
||||
|
||||
All three parts fit in a single `ts` label placed at the arrow's midpoint (`y = arrow_midpoint_y − 6`, `text-anchor="middle"`). The full *event [guard] / action* string still has to respect the 3-word arrow-label rule in spirit — if the guard or action pushes the label past ~28 characters, promote it to a small table below the diagram and leave only *event* on the arrow. Don't wrap labels across two lines mid-arrow.
|
||||
|
||||
**Choice point (internal branch).** When one event fans out into multiple destinations based on guards, insert a small diamond (`w=16 h=16`, `transform="rotate(45)"` on a square — or a `<path>` for the rhombus) at the fan-out point. The incoming arrow carries the shared event label (*payment_received*); each outgoing arrow carries only its own `[guard]` (no event, no action on the choice arrows themselves). This keeps the branch legible without repeating the event name.
|
||||
|
||||
```svg
|
||||
<!-- Choice diamond at (cx, cy) -->
|
||||
<path class="c-gray" d="M cx,cy-10 L cx+10,cy L cx,cy+10 L cx-10,cy Z"/>
|
||||
```
|
||||
|
||||
Use a choice diamond **only** when two or more outgoing arrows share a triggering event. If the branches fire on different events, skip the diamond — draw two independent labeled arrows from the source state. A diamond per decision is mermaid's habit; baoyu keeps them to the rare case where they genuinely remove duplication.
|
||||
|
||||
**Self-loops.** A transition that returns to the same state (*retry on transient failure*) draws as a small arc off one side of the state rect:
|
||||
|
||||
```svg
|
||||
<path d="M rect_right, rect_cy_top
|
||||
C rect_right+30, rect_cy_top−10
|
||||
rect_right+30, rect_cy_bot+10
|
||||
rect_right, rect_cy_bot"
|
||||
class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="rect_right+34" y="rect_cy">retry [n < 3]</text>
|
||||
```
|
||||
|
||||
Place self-loops on the right edge of the state rect. Left-edge self-loops collide with incoming arrows from the initial marker in most top-down layouts.
|
||||
|
||||
**Color.** State machines are almost always **one ramp** — the cohesive lifecycle is the point. Reach for gray when the states are neutral (order lifecycle), teal or purple when the machine represents a *subsystem* you want the reader to recognize as a named thing. The 2-ramp exception is reserved for **error states**: any state that represents a failure or cancellation may switch to `c-coral`, with the rest of the machine in its primary ramp. Don't rainbow the states — same ramp, same weight, let the arrows carry the meaning.
|
||||
|
||||
**Layout.** State machines are less strictly directional than flowcharts — cycles make "top-down only" impossible. Do pick a dominant direction (usually left-to-right for linear lifecycles with a few loops, top-down for branching status graphs) and route the *happy path* along that axis; loops and error transitions can bend against the grain. Keep the initial marker at the top-left (or top-center) and the final markers at the opposite corner/edge.
|
||||
|
||||
**Worked example — Order lifecycle (4 states + cancel branches).**
|
||||
|
||||
```svg
|
||||
<!-- Initial marker -->
|
||||
<circle class="c-gray" cx="60" cy="80" r="6"/>
|
||||
<line x1="66" y1="80" x2="110" y2="80" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- State: New -->
|
||||
<g class="c-gray">
|
||||
<rect x="110" y="56" width="140" height="48" rx="6"/>
|
||||
<text class="th" x="180" y="86" text-anchor="middle">New</text>
|
||||
</g>
|
||||
<text class="ts" x="295" y="74" text-anchor="middle">pay / hold_funds</text>
|
||||
<line x1="250" y1="80" x2="340" y2="80" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- State: Paid -->
|
||||
<g class="c-gray">
|
||||
<rect x="340" y="56" width="140" height="48" rx="6"/>
|
||||
<text class="th" x="410" y="86" text-anchor="middle">Paid</text>
|
||||
</g>
|
||||
<text class="ts" x="525" y="74" text-anchor="middle">ship</text>
|
||||
<line x1="480" y1="80" x2="570" y2="80" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- State: Shipped -->
|
||||
<g class="c-gray">
|
||||
<rect x="570" y="56" width="100" height="48" rx="6"/>
|
||||
<text class="th" x="620" y="86" text-anchor="middle">Shipped</text>
|
||||
</g>
|
||||
|
||||
<!-- State: Delivered (below Shipped) -->
|
||||
<text class="ts" x="636" y="140" text-anchor="start">delivered</text>
|
||||
<path d="M620 104 L620 156" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<g class="c-gray">
|
||||
<rect x="570" y="156" width="100" height="48" rx="6"/>
|
||||
<text class="th" x="620" y="186" text-anchor="middle">Delivered</text>
|
||||
</g>
|
||||
|
||||
<!-- Final marker -->
|
||||
<path d="M620 204 L620 240" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<circle class="c-gray" cx="620" cy="250" r="10" fill="none"/>
|
||||
<circle class="c-gray" cx="620" cy="250" r="5"/>
|
||||
|
||||
<!-- Cancel branch: New → Cancelled (error state, coral) -->
|
||||
<text class="ts" x="180" y="146" text-anchor="middle">cancel</text>
|
||||
<path d="M180 104 L180 156" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
<g class="c-coral">
|
||||
<rect x="110" y="156" width="140" height="48" rx="6"/>
|
||||
<text class="th" x="180" y="186" text-anchor="middle">Cancelled</text>
|
||||
</g>
|
||||
|
||||
<!-- Cancel branch: Paid → Cancelled (refund) -->
|
||||
<text class="ts" x="300" y="146" text-anchor="middle">cancel / refund</text>
|
||||
<path d="M410 104 L410 180 L250 180" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Two things to note in the example: (1) *Cancelled* uses `c-coral` as the single allowed off-ramp for an error state while everything else stays `c-gray`, and (2) the *Paid → Cancelled* transition bends back horizontally and collapses into the same Cancelled box rather than drawing a second coral rect — one error state serves both cancel sources.
|
||||
|
||||
## Rules of thumb
|
||||
|
||||
- **Max 5 nodes per diagram.** If the process has more steps, split into "overview" (the main path) and "detail" (what happens inside one of the stages).
|
||||
- **Single direction only.** Top-down *or* left-to-right. Never both in one diagram.
|
||||
- **Same-tier nodes same size.** If row A has 140-wide boxes, every box in row A should be 140 wide. Mixing sizes in one row looks like a layout bug.
|
||||
- **Short arrow labels are allowed; long ones are not.** A 1–3 word `ts` label can float above an arrow's midpoint when the edge itself carries meaning the source/target nodes don't — *Pass* / *Fail* on a gate's two outbound arrows, *Query* / *Results* on a hub's exchange with an attachment, *Accepted* / *Rejected* on an evaluator's feedback loop. Place the label at `y = arrow_midpoint_y − 6` with `text-anchor="middle"` and class `ts`. **Anything longer than 3 words belongs in the target box's subtitle or in prose around the diagram**, never on the arrow itself — long labels collide with other elements and are hard to position. Sequence diagrams have the same allowance (message labels *are* the point of the arrow there — see `sequence.md`) but with a numeric prefix convention.
|
||||
- **Start and end get gray boxes.** Color is reserved for the interesting steps.
|
||||
- **Arrowheads end 10px short of the target.** Gives the reader visual breathing room.
|
||||
|
||||
## Tiny worked example
|
||||
|
||||
Request: "flowchart of logging into a web app"
|
||||
|
||||
Plan:
|
||||
- 4 nodes: Start → Enter credentials → Auth service → Dashboard
|
||||
- Direction: top-down
|
||||
- Colors: gray for Start and Dashboard, blue for the two middle steps (they're the active workflow)
|
||||
- All single-line (44px), width 180 each, centered at x=250
|
||||
|
||||
viewBox: max_y = 280 + 44 = 324 → H = 344.
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 344" font-family="...">
|
||||
<style>...</style>
|
||||
<defs>
|
||||
<marker id="arrow" .../>
|
||||
</defs>
|
||||
|
||||
<rect class="box" x="250" y="40" width="180" height="44" rx="6"/>
|
||||
<text class="t" x="340" y="62" text-anchor="middle" dominant-baseline="central">Start</text>
|
||||
|
||||
<g class="c-blue">
|
||||
<rect x="250" y="120" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="340" y="142" text-anchor="middle" dominant-baseline="central">Enter credentials</text>
|
||||
</g>
|
||||
|
||||
<g class="c-blue">
|
||||
<rect x="250" y="200" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="340" y="222" text-anchor="middle" dominant-baseline="central">Auth service</text>
|
||||
</g>
|
||||
|
||||
<rect class="box" x="250" y="280" width="180" height="44" rx="6"/>
|
||||
<text class="t" x="340" y="302" text-anchor="middle" dominant-baseline="central">Dashboard</text>
|
||||
|
||||
<line x1="340" y1="94" x2="340" y2="110" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="340" y1="174" x2="340" y2="190" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="340" y1="254" x2="340" y2="270" class="arr" marker-end="url(#arrow)"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
## Worked example — Gate with pass/fail split
|
||||
|
||||
Request: "LLM call with a gate that either continues through two more calls or exits early"
|
||||
|
||||
Plan:
|
||||
- 6 nodes on a single row: `In` (pill) → `LLM Call 1` → `Gate` → `LLM Call 2` → `LLM Call 3` → `Out` (pill), plus an `Exit` pill dropping below the Gate on the Fail branch.
|
||||
- Colors: pills gray; LLM calls gray; Gate purple (the decision point).
|
||||
- Arrow labels: *Pass* on Gate → LLM Call 2 (solid), *Fail* on Gate → Exit (dashed `.arr-alt`).
|
||||
- Horizontal budget: 6 nodes + 5 gaps of 20px must fit in the 600px usable width. `60 + 106 + 60 + 106 + 106 + 60 = 498`, plus `5 × 20 = 100`, total = **598** — fits with 2px to spare.
|
||||
- viewBox: single row at y=140, plus an Exit row at y=230. `max_y = 230 + 44 = 274` → H = **294**.
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 294" font-family="...">
|
||||
<style>...</style>
|
||||
<defs><marker id="arrow" .../></defs>
|
||||
|
||||
<rect class="box" x="40" y="140" width="60" height="44" rx="22"/>
|
||||
<text class="t" x="70" y="162" text-anchor="middle" dominant-baseline="central">In</text>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="120" y="140" width="106" height="44" rx="6"/>
|
||||
<text class="th" x="173" y="162" text-anchor="middle" dominant-baseline="central">LLM Call 1</text>
|
||||
</g>
|
||||
|
||||
<g class="c-purple">
|
||||
<rect x="246" y="140" width="60" height="44" rx="6"/>
|
||||
<text class="th" x="276" y="162" text-anchor="middle" dominant-baseline="central">Gate</text>
|
||||
</g>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="326" y="140" width="106" height="44" rx="6"/>
|
||||
<text class="th" x="379" y="162" text-anchor="middle" dominant-baseline="central">LLM Call 2</text>
|
||||
</g>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="452" y="140" width="106" height="44" rx="6"/>
|
||||
<text class="th" x="505" y="162" text-anchor="middle" dominant-baseline="central">LLM Call 3</text>
|
||||
</g>
|
||||
|
||||
<rect class="box" x="578" y="140" width="60" height="44" rx="22"/>
|
||||
<text class="t" x="608" y="162" text-anchor="middle" dominant-baseline="central">Out</text>
|
||||
|
||||
<rect class="box" x="246" y="230" width="60" height="44" rx="22"/>
|
||||
<text class="t" x="276" y="252" text-anchor="middle" dominant-baseline="central">Exit</text>
|
||||
|
||||
<line x1="102" y1="162" x2="118" y2="162" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="228" y1="162" x2="244" y2="162" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="308" y1="162" x2="324" y2="162" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="434" y1="162" x2="450" y2="162" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="560" y1="162" x2="576" y2="162" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="276" y1="184" x2="276" y2="226" class="arr-alt" marker-end="url(#arrow)"/>
|
||||
|
||||
<text class="ts" x="316" y="156" text-anchor="middle">Pass</text>
|
||||
<text class="ts" x="286" y="204" text-anchor="start">Fail</text>
|
||||
</svg>
|
||||
```
|
||||
|
||||
Notes on this example:
|
||||
- The *Pass* label sits **above** the solid horizontal arrow from Gate to LLM Call 2 at `y = arrow_y − 6 = 156`, centered on the arrow midpoint (316).
|
||||
- The *Fail* label sits **beside** the dashed vertical arrow at x=286 (10px right of the arrow), with `text-anchor="start"` so it extends rightward — this keeps it clear of the Gate rect's right edge at x=306.
|
||||
- Both arrow labels are ≤1 word — the rule of thumb at line 159 caps flowchart arrow labels at 3 words.
|
||||
- The `.arr-alt` class on the Fail arrow matches the stroke color of `.arr` but adds the dashed pattern, so the Fail branch reads as a **conditional exit** rather than a mainline flow.
|
||||
- The Exit pill is centered at x=276 (same x as the Gate center — both rects share x=246, w=60) so the dashed arrow drops straight down, no bend.
|
||||
- Rightmost rect edge: Out pill ends at x = 578 + 60 = **638** ≤ 640 ✓ — the pre-save rightmost-edge check from `pitfalls.md#1` passes.
|
||||
|
||||
|
||||
## Poster flowchart pattern
|
||||
|
||||
See `references/flowchart-poster.md`. **Load it when ≥3 of the poster triggers fire in Step 4a**: the topic has a short name, a "why it exists" sentence, named phases (2–4), parallel candidates with a judge, a loop termination mechanic, overflow annotations, or a footer quote. If only 0–1 apply, the simple flowchart is the right tool.
|
||||
For flowcharts with 10+ steps:
|
||||
- Group related steps into swim lanes (vertical columns with header bars)
|
||||
- Add a "phase" row header at the top of each swim lane
|
||||
- Use the region boundary pattern from Architecture for swim lanes
|
||||
|
|
|
|||
|
|
@ -1,633 +0,0 @@
|
|||
# Glyph Library
|
||||
|
||||
Small, reusable SVG primitives that several sub-patterns draw *inside* their boxes or *on top of* their connectors: status circles on an arrow, checkboxes in a TODO list, queue slots inside a worker box, a document icon inside a shared-state store, a labeled annotation circle halfway along an arrow.
|
||||
|
||||
These are intentionally *shared across types*. A status circle belongs in a flowchart (Generator-Verifier) and also in a structural advisor diagram. A checkbox belongs in a structural "TODOs vs Tasks" comparison and also in an illustrative "agent task list" subject. Keeping them in one place keeps the visual vocabulary consistent across diagrams.
|
||||
|
||||
## Hard rules
|
||||
|
||||
Every glyph in this file obeys **all** of the design system's hard rules (`design-system.md` → "Hard rules"). In particular:
|
||||
|
||||
- **SVG primitives only.** Every glyph is composed of `<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<path>`, or `<text>`. No `<image>`, no `<foreignObject>`, no emoji characters.
|
||||
- **No hardcoded colors on text or strokes that need dark-mode response.** Every glyph uses the existing CSS classes (`c-{ramp}`, `arr-{ramp}`, `arr-alt`, `box`, `t`/`th`/`ts`). This means both light and dark mode are handled automatically — zero template changes required.
|
||||
- **No rotated text.** (Glyphs are small enough that horizontal text always fits.)
|
||||
- **Sentence case** on any labels.
|
||||
|
||||
If a new glyph seems to require a hardcoded color or a new CSS rule, stop and reconsider — the shape is probably wrong, or it belongs in one of the existing ramps.
|
||||
|
||||
## Coordinate convention
|
||||
|
||||
Every glyph block below is drawn with its **top-left anchor at (0, 0)**, then wrapped in a `<g transform="translate(x, y)">` to place it in the diagram. The glyph's *bounding box width × height* is listed in each subsection so you can reserve space and pick `x, y` from the parent layout.
|
||||
|
||||
```svg
|
||||
<!-- Glyph in situ: place at (340, 200) -->
|
||||
<g transform="translate(340, 200)">
|
||||
<!-- glyph body at origin (0, 0) -->
|
||||
</g>
|
||||
```
|
||||
|
||||
All `x`/`y`/`cx`/`cy`/`d` values in the snippets below are **relative to the glyph origin**, not the diagram origin. Don't edit them — just translate the wrapper group.
|
||||
|
||||
---
|
||||
|
||||
## Status circles
|
||||
|
||||
A circle placed on or near an arrow to tag the outcome of a decision. Three variants: ✓ success, ✗ failure, ● in-progress.
|
||||
|
||||
**Bounding box.** 24 × 24. Place the center of the circle at `(12, 12)` inside the glyph origin. When placed on a diagram, translate so that `(12, 12)` lands at the desired anchor — usually the midpoint of an arrow or just past a gate node.
|
||||
|
||||
**Where they're used.**
|
||||
- Flowchart (Generator-Verifier, Agent Teams, gate patterns): tag the two branches out of a verifier or judge node.
|
||||
- Structural (advisor, TODOs→Tasks): mark individual task outcomes inside a state box.
|
||||
|
||||
**Color rules.** Status carries semantic color — not negotiable:
|
||||
- `check` → `c-green` / `arr-green` (accept, pass, done)
|
||||
- `x` → `c-coral` / `arr-coral` (reject, fail, blocked)
|
||||
- `dot` → `c-amber` / `arr-amber` (in-progress, busy, running)
|
||||
|
||||
**Dark mode.** All three use the existing ramp classes, so both the circle fill and the inner stroke invert automatically.
|
||||
|
||||
### status-circle-check
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="c-green" cx="12" cy="12" r="12"/>
|
||||
<path class="arr-green" d="M6 12.5 L10.5 17 L18 8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
### status-circle-x
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="c-coral" cx="12" cy="12" r="12"/>
|
||||
<path class="arr-coral" d="M7.5 7.5 L16.5 16.5 M16.5 7.5 L7.5 16.5" stroke-linecap="round" fill="none"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
### status-circle-dot
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="c-amber" cx="12" cy="12" r="12"/>
|
||||
<circle class="c-amber" cx="12" cy="12" r="4"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
The nested small circle shares the ramp but is visually distinct because it sits on top of its own border stroke, reading as a filled dot against the paler ring.
|
||||
|
||||
### Placement on an arrow
|
||||
|
||||
A status circle placed *on* an arrow replaces that arrow's tail:
|
||||
|
||||
```
|
||||
status-circle-check (center at x=360, y=160)
|
||||
│
|
||||
(start)────arrow 1──→ ◎ ────arrow 2──→(end)
|
||||
```
|
||||
|
||||
Split the original arrow into two segments, both with `marker-end="url(#arrow)"`. The circle sits in the gap. Arrow 1 stops at `circle_cx − 14` (2px before the circle border). Arrow 2 starts at `circle_cx + 14`.
|
||||
|
||||
**Pitfall.** Don't layer the circle on top of a single continuous line — the arrow renders behind the circle and looks as if it pierces straight through. Always split into two segments.
|
||||
|
||||
---
|
||||
|
||||
## Checkboxes
|
||||
|
||||
A 14 × 14 checkbox glyph for task lists. Two variants: checked (filled green with check) and empty (neutral outline).
|
||||
|
||||
**Bounding box.** 14 × 14.
|
||||
|
||||
**Where they're used.**
|
||||
- Structural (TODOs → Tasks comparison, checklist interiors inside a subsystem container): one per task row.
|
||||
- Illustrative (task list subject, sparingly): only if the task list is the whole mechanism being shown.
|
||||
|
||||
**Color rules.**
|
||||
- `checked` → `c-green` (task done)
|
||||
- `empty` → `c-gray` (task pending)
|
||||
|
||||
For an "in-progress" task, substitute `status-circle-dot` at half scale, or just leave it empty. Don't invent a third checkbox color.
|
||||
|
||||
### checkbox-checked
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<rect class="c-green" x="0" y="0" width="14" height="14" rx="2"/>
|
||||
<path class="arr-green" d="M3 7.5 L6 10.5 L11 5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
### checkbox-empty
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<rect class="c-gray" x="0" y="0" width="14" height="14" rx="2"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
### Checklist rows
|
||||
|
||||
A row of checklist items is laid out with the checkbox on the left and a 14px `t`-class label to its right:
|
||||
|
||||
```svg
|
||||
<g transform="translate(40, 120)">
|
||||
<g transform="translate(0, 0)">
|
||||
<rect class="c-green" x="0" y="0" width="14" height="14" rx="2"/>
|
||||
<path class="arr-green" d="M3 7.5 L6 10.5 L11 5" stroke-linecap="round" fill="none"/>
|
||||
</g>
|
||||
<text class="t" x="22" y="11">Draft the outline</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Row pitch: 22px (14px checkbox height + 8px vertical gap). Label `y = checkbox_y + 11` for vertical centering against the 14px box.
|
||||
|
||||
**Row width budget.** Inside a 315-wide subsystem container with 20px interior padding, the label has `315 − 20 − 20 − 14 − 8 = 253` px of width. At 8px/char (14px Latin) that caps the label at **~31 characters**; at 15px/char (CJK) that caps at **~16 characters**. Longer labels must be truncated or wrapped across two rows.
|
||||
|
||||
---
|
||||
|
||||
## Queue slots
|
||||
|
||||
A row of small filled/empty squares that represents a work queue or a buffer. Not to be confused with checklists — queue slots are *undifferentiated items*, checklists are *named tasks*.
|
||||
|
||||
**Bounding box per slot.** 16 × 16. Row pitch: 20 (16 + 4 gap). Typical row: 6–8 slots max per row.
|
||||
|
||||
**Where they're used.**
|
||||
- Flowchart (Agent Teams, task-queue hub node): show the queue of pending work *inside* a source node.
|
||||
- Structural (any box representing a worker pool with a buffer): same purpose, different frame.
|
||||
|
||||
**Color rules.**
|
||||
- `filled` → `c-amber` (work waiting in queue)
|
||||
- `empty` → `c-gray` (slot available)
|
||||
|
||||
### queue-slot-filled
|
||||
|
||||
```svg
|
||||
<rect class="c-amber" x="x" y="y" width="16" height="16" rx="2"/>
|
||||
```
|
||||
|
||||
### queue-slot-empty
|
||||
|
||||
```svg
|
||||
<rect class="c-gray" x="x" y="y" width="16" height="16" rx="2"/>
|
||||
```
|
||||
|
||||
### Queue row inside a box
|
||||
|
||||
For a two-line box (`title + queue row`), the queue row goes at `rect_y + 28` (below the title baseline), starting at `rect_x + 12`:
|
||||
|
||||
```svg
|
||||
<g>
|
||||
<rect class="c-gray" x="60" y="120" width="160" height="52" rx="6"/>
|
||||
<text class="th" x="140" y="138" text-anchor="middle">Task queue</text>
|
||||
<rect class="c-amber" x="72" y="148" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="92" y="148" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="112" y="148" width="16" height="16" rx="2"/>
|
||||
<rect class="c-amber" x="132" y="148" width="16" height="16" rx="2"/>
|
||||
<rect class="c-gray" x="152" y="148" width="16" height="16" rx="2"/>
|
||||
<rect class="c-gray" x="172" y="148" width="16" height="16" rx="2"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
Box height for this variant: **52px** (title row + queue row + vertical padding). Don't reuse the default 44 or 56 heights — the queue row needs its own budget.
|
||||
|
||||
**Row width budget.** `slots × 20 − 4 ≤ rect_width − 24`. For a 160-wide rect that's `(160 − 24 + 4) / 20 = 7` slots maximum. Above 7, split into two rows or widen the host rect.
|
||||
|
||||
---
|
||||
|
||||
## Document & terminal icons
|
||||
|
||||
Decorative icons that go *inside* a box (usually illustrative or structural) to hint at what kind of thing the box represents: a document store, a terminal/computer, a code script. These are **not allowed in flowchart or sequence diagrams** — label the rect instead.
|
||||
|
||||
**Why only illustrative / structural.** In a flowchart, the reader is tracking sequence; an icon is visual noise competing with the arrow flow. In a structural diagram showing containment, an icon inside a container cell reinforces *what that cell is* and doesn't interrupt any flow. In an illustrative diagram the icon *is* part of the intuition (a document glyph inside a "Shared state" box tells you it's storing artifacts, not messages).
|
||||
|
||||
### doc-icon
|
||||
|
||||
**Bounding box.** 24 × 28. Place it in the lower half of a box that's at least 80 tall.
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<path class="arr" d="M2 2 L16 2 L22 8 L22 26 L2 26 Z" fill="none"/>
|
||||
<path class="arr" d="M16 2 L16 8 L22 8" fill="none"/>
|
||||
<line class="arr" x1="6" y1="14" x2="18" y2="14"/>
|
||||
<line class="arr" x1="6" y1="18" x2="18" y2="18"/>
|
||||
<line class="arr" x1="6" y1="22" x2="14" y2="22"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
The outer silhouette is a rectangle with a folded corner; the three short horizontal lines suggest paragraphs.
|
||||
|
||||
**Placement inside a box.** For a box at `(box_x, box_y)` with width `box_w` and height `box_h ≥ 80`, place the icon's origin at `(box_x + box_w/2 − 12, box_y + box_h − 36)`. This centers horizontally and leaves 8px below.
|
||||
|
||||
### terminal-icon
|
||||
|
||||
**Bounding box.** 24 × 24.
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<rect class="box" x="0" y="0" width="24" height="24" rx="2"/>
|
||||
<path class="arr" d="M5 9 L9 12 L5 15" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line class="arr" x1="11" y1="17" x2="19" y2="17"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
A framed box containing a `>` prompt chevron and a short input line.
|
||||
|
||||
**Dark mode.** The `box` class provides a contrast frame that reads correctly in both modes; the inner chevron and cursor line use `arr` which also inverts.
|
||||
|
||||
### script-icon
|
||||
|
||||
**Not a fixed-size glyph.** The "script" pattern in image #12 (Programmatic tool calling) is a tall rect that contains multiple horizontal colored dividers and a `{ }` label at the top, representing an inline script that wraps several tool calls. Because the rect's height depends on how many tool calls it wraps, the script-icon is parameterized — build it inline using this recipe:
|
||||
|
||||
```svg
|
||||
<g>
|
||||
<!-- outer script rect: height depends on wrapped-call count -->
|
||||
<rect class="c-gray" x="script_x" y="script_y" width="120" height="script_h" rx="6"/>
|
||||
<text class="th" x="script_x + 60" y="script_y + 22" text-anchor="middle">{ }</text>
|
||||
<line class="arr" x1="script_x + 16" y1="script_y + 40" x2="script_x + 104" y2="script_y + 40"/>
|
||||
<!-- one colored band per wrapped call: -->
|
||||
<rect class="c-teal" x="script_x + 12" y="band1_y" width="96" height="28" rx="4"/>
|
||||
<rect class="c-purple" x="script_x + 12" y="band2_y" width="96" height="28" rx="4"/>
|
||||
<rect class="c-amber" x="script_x + 12" y="band3_y" width="96" height="28" rx="4"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
`script_h = 48 + N × 36` for N wrapped calls; `bandK_y = script_y + 48 + (K-1) × 36`. See `sequence.md` → "Parallel independent rounds" for the full pattern this participates in.
|
||||
|
||||
---
|
||||
|
||||
## Code braces
|
||||
|
||||
Inline `{ }` as a label on a connector or inside a small label rect to signal "code" or "programmatic" handoff.
|
||||
|
||||
```svg
|
||||
<text class="th" x="x" y="y" text-anchor="middle">{ }</text>
|
||||
```
|
||||
|
||||
**Width estimate.** 14px, ~16 char-px. Treat as 2 chars when budgeting for a rect container.
|
||||
|
||||
**Where used.** Only in illustrative or structural diagrams that distinguish "data flow" from "code flow" — usually paired with a `script-icon` or a `c-gray` pill labeled "Programmatic".
|
||||
|
||||
---
|
||||
|
||||
## Annotation circle on connector
|
||||
|
||||
A labeled circle that sits on an arrow to name *the thing that mediates the exchange* — for example, "Skill" sitting on the Claude ↔ Environment arrow in image #13.
|
||||
|
||||
**Bounding box.** 64 × 64 around the circle. The inner pill is ~52 × 20.
|
||||
|
||||
**Where used.** Illustrative only. This is a "the action on this arrow is X" label, which in a flowchart or sequence would be a simple arrow label — in an illustrative diagram, we promote it to a visual node because the mediator *is* the subject.
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<!-- outer circle uses .box for the neutral background ring -->
|
||||
<circle class="box" cx="32" cy="32" r="30"/>
|
||||
<!-- inner pill with label, picks up its own ramp -->
|
||||
<g class="c-teal">
|
||||
<rect x="4" y="22" width="56" height="20" rx="10"/>
|
||||
<text class="th" x="32" y="36" text-anchor="middle">Skill</text>
|
||||
</g>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Placement.** Center the glyph on the arrow's midpoint: `translate(arrow_midx − 32, arrow_midy − 32)`. The arrow must be split into two segments on either side of the glyph (same rule as status circles), each stopping 32px before the center.
|
||||
|
||||
**Label length.** The inner pill caps at ~8 Latin chars at 14px (`8 × 8 = 64`, minus 8px padding each side, gives ~48px of label room — 6 chars is a comfortable fit; "Skill" at 5 chars is ideal). For CJK, cap at 3 characters.
|
||||
|
||||
**Pitfall.** Don't use `c-teal` on the outer circle — that double-fills the ramp and the label pill disappears. Keep the outer ring on `box`, the inner pill on a ramp.
|
||||
|
||||
---
|
||||
|
||||
## Dashed future-state rect
|
||||
|
||||
Used in "TODOs → Tasks" (image #4) to show a task that hasn't been scheduled yet — the node exists in the plan but not in the active DAG.
|
||||
|
||||
```svg
|
||||
<rect class="arr-alt" x="x" y="y" width="w" height="h" rx="6"/>
|
||||
```
|
||||
|
||||
Reuses the existing `arr-alt` class — it already provides `fill: none`, `stroke-width: 1.5`, `stroke-dasharray: 5 4`, and dark-mode stroke override. No inline attributes needed.
|
||||
|
||||
**Text inside.** Label with `ts` class (12px, muted) — future-state nodes are always visually demoted relative to active ones:
|
||||
|
||||
```svg
|
||||
<g>
|
||||
<rect class="arr-alt" x="200" y="180" width="120" height="44" rx="6"/>
|
||||
<text class="ts" x="260" y="205" text-anchor="middle">Task 4</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Pitfall.** Do *not* create a new `.box-future` CSS class. The `arr-alt` class handles it — adding a new class is template bloat.
|
||||
|
||||
**Pitfall.** Don't align the dashed rect's center with an active `c-{ramp}` rect's center if their heights differ — the visual baseline looks off. Match the top edge instead, or match the baseline of the label text.
|
||||
|
||||
---
|
||||
|
||||
## Publish/subscribe arrow pair
|
||||
|
||||
Two parallel offset arrows between a node and a bus bar — one labeled "Publish" going down, one labeled "Subscribe" going up. Used in the bus topology pattern (`structural.md` → "Bus topology").
|
||||
|
||||
**Not a static glyph.** It's a pair of straight arrows with an 8px horizontal offset between them, each with its own label. Write this inline where you use it:
|
||||
|
||||
```svg
|
||||
<!-- agent_cx is the horizontal center of the agent box;
|
||||
bar_y_top and bar_y_bottom are the y-edges of the bus bar;
|
||||
agent_y_bottom is the bottom y of the top agent -->
|
||||
<line class="arr" x1="agent_cx - 8" y1="agent_y_bottom" x2="agent_cx - 8" y2="bar_y_top" marker-end="url(#arrow)"/>
|
||||
<line class="arr" x1="agent_cx + 8" y1="bar_y_top" x2="agent_cx + 8" y2="agent_y_bottom" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="agent_cx - 14" y="(agent_y_bottom + bar_y_top) / 2 + 4" text-anchor="end">Publish</text>
|
||||
<text class="ts" x="agent_cx + 14" y="(agent_y_bottom + bar_y_top) / 2 + 4">Subscribe</text>
|
||||
```
|
||||
|
||||
The 8px offset is the documented spacing — wider looks like two unrelated arrows, narrower looks like a single fat arrow.
|
||||
|
||||
**Label placement.** The two labels go in the gap between the agent and the bar, one flush against the left arrow (anchor `end`) and one flush against the right arrow (anchor `start`). If the agent's bottom and the bar's top are closer than 40px, move the labels to the *side* of the pair rather than between them.
|
||||
|
||||
**Color.** Both arrows use `arr` (default gray). If the pattern needs to highlight one specific agent's channel, upgrade that pair to `arr-{ramp}` where `{ramp}` matches the agent's box — but only that one pair, not all of them.
|
||||
|
||||
---
|
||||
|
||||
## Concept-to-shape conventions
|
||||
|
||||
Before drawing any node, decide what shape it should be. baoyu-diagram is a **flat-rect aesthetic** — almost every concept maps to a labeled rectangle. The table below is the canonical lookup so you don't invent a new icon shape inline and so you don't reach for mermaid/plantuml-style iconography that clashes with the design system.
|
||||
|
||||
| Concept | Use this shape | Notes |
|
||||
|----------------------------|--------------------------------------------------------|-------------------------------------------------------|
|
||||
| User / human / actor | Labeled rect with `ts` subtitle "(user)" or similar | No stick figure. In sequence, an actor header rect. |
|
||||
| LLM / model | Labeled rect, optionally `c-teal` or `c-purple` | No brain icon. Label with the model name. |
|
||||
| Agent / orchestrator | Labeled rect, often the centerpiece of the diagram | If it's the mechanism's name, use `.title` class for the whole diagram title instead. |
|
||||
| Tool / function | Labeled rect; or compact tool card with `tool-card-*` icon in phase-band diagrams | No gear icon in standard diagrams. Phase-band exception: see "Tool card icons" section. |
|
||||
| API / gateway / endpoint | Labeled rect | No hexagon. Label with the route or service name. |
|
||||
| Memory (short-term) | Rect with dashed border via `class="arr-alt"` inline | Dashed = ephemeral. See `svg-template.md` → `.arr-alt`. |
|
||||
| Memory (long-term) / DB | Rect with `doc-icon` inside (structural/illustrative) | No cylinder. In flowchart/sequence, label with "(DB)". |
|
||||
| Vector store | Rect labeled with dimensions ("768d") + doc-icon | No cylinder with grid lines. |
|
||||
| Queue / buffer | Rect containing `queue-slot-filled` / `-empty` glyphs | See glyphs → "Queue slots". |
|
||||
| Task list / checklist | Rect containing `checkbox-checked` / `-empty` rows | See glyphs → "Checkboxes". |
|
||||
| External service | Labeled rect inside a dashed-border container | The dashed container communicates "not ours". |
|
||||
| Decision point | Rotated square (diamond), flowchart only | See `flowchart.md` → diamond decision nodes. |
|
||||
| Process / step | Rounded rect (`rx="6"`) | The default shape. Reach for this first. |
|
||||
| Start / End boundary | Pill (rounded rect with `rx = height/2`) | See `flowchart.md` → "Pill terminal node". ≤2 per diagram. |
|
||||
| State in state machine | Rounded rect with title | See `flowchart.md` → "State-machine sub-pattern". |
|
||||
| Initial / final state | Filled 8px circle / 12px hollow circle with inner 8px | State-machine exception to the "rect only" rule. |
|
||||
| Class (UML) | 3-compartment rect (name / attrs / methods) | See `class.md`. |
|
||||
|
||||
### Rejected shapes — never use these
|
||||
|
||||
These shapes look great in mermaid/plantuml/fireworks-style diagrams but break baoyu's flat-rect aesthetic and its dark-mode color contract. If the diagram seems to demand one, the *label* is wrong — fix the label.
|
||||
|
||||
- **Stick figure** (for users / actors). Use a labeled rect.
|
||||
- **Brain glyph** (for LLMs). Use a labeled rect with the model name.
|
||||
- **Gear / cog glyph** (for tools). Use a labeled rect with the tool name.
|
||||
- **Cylinder** (for databases). Use a rect with `doc-icon` or a "(DB)" suffix in the label.
|
||||
- **Cloud silhouette** (for external services). Use a dashed-border container.
|
||||
- **Disk / drum / tape-reel** (for storage). Same as cylinder — use a rect.
|
||||
- **Briefcase, folder, envelope, magnifying glass** (for everything else). Use a rect.
|
||||
|
||||
The rationale for each: these glyphs all require either hardcoded fills (breaking dark mode), or non-rectangular paths that don't compose with the 44/56 row heights and the `c-{ramp}` stroke conventions, or visual weight that competes with the actual information in the box labels. baoyu's readability comes from consistent typography and color — not from pictograms.
|
||||
|
||||
**When in doubt, ask: "does the shape carry information the label doesn't?"** If the answer is no, it's decoration and you should drop it. If yes, the label is missing the information and you should add it.
|
||||
|
||||
## Glyph checklist before saving
|
||||
|
||||
When you include any glyph in a diagram, verify:
|
||||
|
||||
1. **Class check** — every shape in the glyph uses one of `c-{ramp}`, `arr-{ramp}`, `arr`, `arr-alt`, `box`, or a text class (`t`/`th`/`ts`). No inline `fill="#..."` or `stroke="#..."` on anything that needs dark-mode inversion.
|
||||
2. **Positioning check** — the glyph is wrapped in `<g transform="translate(x, y)">` and the anchor math lands it where the parent layout expects (box center, arrow midpoint, checklist row).
|
||||
3. **No-collision check** — for status circles and annotation circles, the arrow they sit on has been split into two segments leaving a gap for the glyph, not routed *through* it.
|
||||
4. **Ramp meaning check** — status colors (`c-green` for pass, `c-coral` for fail, `c-amber` for in-progress) are not overridden by the surrounding diagram's color budget. Status semantics always win over the "≤2 ramps" rule.
|
||||
5. **Icon scope check** — `doc-icon`, `terminal-icon`, and `script-icon` only appear in illustrative or structural diagrams. A flowchart that wants to show "this is a document" uses the label, not the icon. `tool-card-*` icons only appear inside compact tool cards in phase-band diagrams.
|
||||
|
||||
---
|
||||
|
||||
## Tool card icons
|
||||
|
||||
Small 24 × 24 icons for use **exclusively inside compact tool card nodes** in phase-band diagrams (see `references/flowchart-phase-bands.md`). Each icon supplements—never replaces—the text label on the card. Without the label the icon has no meaning; without the icon the label still works fine.
|
||||
|
||||
**Scope rule.** These icons are not allowed in any other diagram type. They require the compact tool card node template below — dropping a tool icon directly into a standard 44px flowchart node looks wrong and breaks the icon-to-card sizing contract.
|
||||
|
||||
**Dark-mode rule.** All strokes use `class="arr"` (inherits the template's stroke color). `fill="none"` on any outer shape is safe. No inline hex colors.
|
||||
|
||||
**Bounding box:** 24 × 24 for all icons. Place the icon origin so the glyph center lands at the top-center of the card (see compact tool card template below).
|
||||
|
||||
---
|
||||
|
||||
### icon-scan
|
||||
|
||||
Concentric target rings — communicates "scanning / finding".
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="arr" cx="12" cy="12" r="10" fill="none"/>
|
||||
<circle class="arr" cx="12" cy="12" r="6" fill="none"/>
|
||||
<circle class="arr" cx="12" cy="12" r="2" fill="none"/>
|
||||
<line class="arr" x1="18" y1="6" x2="22" y2="2"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
The small tick line at the top-right angle reads as "aim" without being a literal crosshair.
|
||||
|
||||
---
|
||||
|
||||
### icon-search
|
||||
|
||||
Document face with text lines — communicates "reading / querying" without using a magnifying glass (which is prohibited as a shape replacement for labels in standard diagrams).
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<rect class="arr" x="2" y="2" width="20" height="20" rx="2" fill="none"/>
|
||||
<line class="arr" x1="5" y1="8" x2="19" y2="8"/>
|
||||
<line class="arr" x1="5" y1="12" x2="19" y2="12"/>
|
||||
<line class="arr" x1="5" y1="16" x2="13" y2="16"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### icon-data
|
||||
|
||||
Three vertical bars of different heights — bar chart shape for "data analysis / metrics".
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<line class="arr" x1="2" y1="22" x2="22" y2="22"/>
|
||||
<rect class="arr" x="3" y="14" width="4" height="8" fill="none"/>
|
||||
<rect class="arr" x="10" y="7" width="4" height="15" fill="none"/>
|
||||
<rect class="arr" x="17" y="10" width="4" height="12" fill="none"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### icon-code
|
||||
|
||||
Angle brackets with a slash — `</>` pattern for "code / script analysis".
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<path class="arr" d="M8 4 L3 12 L8 20" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="arr" d="M16 4 L21 12 L16 20" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line class="arr" x1="14" y1="5" x2="10" y2="19" stroke-linecap="round"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### icon-exploit
|
||||
|
||||
Lightning bolt — communicates "attack / execution / injection".
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<path class="arr" d="M14 2 L9 12 L13 12 L10 22 L15 11 L11 11 Z" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### icon-callback
|
||||
|
||||
Two arrows in a circle (↩ motif) — communicates "callback / round-trip / webhook".
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<path class="arr" d="M4 12 A8 8 0 1 1 12 20" fill="none" stroke-linecap="round"/>
|
||||
<path class="arr" d="M8 22 L12 20 L10 16" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compact tool card node template
|
||||
|
||||
**Used only in phase-band diagrams.** A 80 × 80 node that shows a 24 × 24 icon above a two-line text label. The icon provides visual differentiation at a glance; the label provides the actual meaning.
|
||||
|
||||
**Bounding box:** 80 × 80 (or wider if the label is longer — see sizing rule below).
|
||||
|
||||
**Sizing rule:**
|
||||
```
|
||||
card_w = max(line1_chars × 7, line2_chars × 7, 24) + 24
|
||||
card_w = round up to nearest 8, minimum 72, maximum 128
|
||||
card_h = 80 (fixed)
|
||||
```
|
||||
|
||||
For a single-line label (e.g., "Scan"):
|
||||
```
|
||||
card_w = max(4 × 7, 24) + 24 = max(28, 24) + 24 = 52 → round to 72 (minimum)
|
||||
```
|
||||
|
||||
For a two-word label split over two lines (e.g., "Search" / "tool"):
|
||||
```
|
||||
card_w = max(6×7, 4×7) + 24 = 42 + 24 = 66 → round to 72 (minimum)
|
||||
```
|
||||
|
||||
For a longer label (e.g., "Code" / "analysis"):
|
||||
```
|
||||
card_w = max(4×7, 8×7) + 24 = 56 + 24 = 80 → round to 80
|
||||
```
|
||||
|
||||
**Template (two-line label, 80-wide card):**
|
||||
|
||||
```svg
|
||||
<g class="c-gray" transform="translate(card_x, card_y)">
|
||||
<rect x="0" y="0" width="80" height="80" rx="6"/>
|
||||
<!-- icon: 24×24, top-center; origin = ((80−24)/2, 10) = (28, 10) -->
|
||||
<g transform="translate(28, 10)">
|
||||
<!-- paste icon glyph here (icon origin at 0,0) -->
|
||||
</g>
|
||||
<!-- two-line label: line 1 at y=50, line 2 at y=64 -->
|
||||
<text class="ts" x="40" y="50" text-anchor="middle" dominant-baseline="central">Scan</text>
|
||||
<text class="ts" x="40" y="64" text-anchor="middle" dominant-baseline="central">tool</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
For a single-line label (when the full name fits in ≤10 chars):
|
||||
```svg
|
||||
<text class="ts" x="40" y="57" text-anchor="middle" dominant-baseline="central">Scanner</text>
|
||||
```
|
||||
|
||||
**Color rules.**
|
||||
- Use `c-gray` for all tool cards by default. Color encodes meaning, not tool identity — don't give each tool a different ramp.
|
||||
- If **one specific tool** is the subject of the diagram (e.g., the exploitation tool that's being analyzed), promote just that one card to a semantic ramp (`c-coral` for attack tools, `c-amber` for analysis tools). All other cards stay `c-gray`.
|
||||
|
||||
**Row packing.** Align cards in a horizontal row inside the phase band's main flow zone. The gap between cards is **8px minimum**:
|
||||
|
||||
```
|
||||
row_width = N × card_w + (N−1) × 8
|
||||
required: row_width ≤ 380 (main flow zone width inside a band)
|
||||
```
|
||||
|
||||
| Cards | card_w | row_width | Fits? |
|
||||
|-------|--------|-----------|-------|
|
||||
| 4 | 80 | 352 | ✓ |
|
||||
| 5 | 72 | 388 | ✗ — use card_w=72 with 6px gap: 5×72+4×6=384 ✓ |
|
||||
| 6 | 64 | 408 | ✗ — split into two rows of 3 |
|
||||
|
||||
To center a row of N cards:
|
||||
```
|
||||
row_start_x = band_x + band_padding + (main_flow_w − row_width) / 2
|
||||
card[i].x = row_start_x + i × (card_w + gap)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Operator icons
|
||||
|
||||
Larger icons (32 × 40) for the **left operator column** of a phase-band diagram — the human or AI actor that initiates the operation. These appear once per diagram, outside (to the left of) the phase band containers.
|
||||
|
||||
---
|
||||
|
||||
### operator-human
|
||||
|
||||
A simplified head + shoulders silhouette. 32 × 40.
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="arr" cx="16" cy="11" r="8" fill="none"/>
|
||||
<path class="arr" d="M2 40 Q2 24 16 24 Q30 24 30 40" fill="none" stroke-linecap="round"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
Place the icon's origin at `(operator_x, operator_y)`. The icon's visual center is at `(16, 24)`. Add a `ts` label below: `y = operator_y + 44`.
|
||||
|
||||
---
|
||||
|
||||
### operator-ai
|
||||
|
||||
An asterisk-in-circle — six spokes radiating from center, with an outer ring. 32 × 32.
|
||||
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="arr" cx="16" cy="16" r="14" fill="none"/>
|
||||
<line class="arr" x1="16" y1="4" x2="16" y2="28"/>
|
||||
<line class="arr" x1="4" y1="16" x2="28" y2="16"/>
|
||||
<line class="arr" x1="7" y1="7" x2="25" y2="25"/>
|
||||
<line class="arr" x1="25" y1="7" x2="7" y2="25"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
For a six-spoke variant (closer to the Anthropic asterisk):
|
||||
```svg
|
||||
<g transform="translate(x, y)">
|
||||
<circle class="arr" cx="16" cy="16" r="14" fill="none"/>
|
||||
<line class="arr" x1="16" y1="3" x2="16" y2="29"/>
|
||||
<line class="arr" x1="4" y1="10" x2="28" y2="22"/>
|
||||
<line class="arr" x1="4" y1="22" x2="28" y2="10"/>
|
||||
</g>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Placement in a phase-band diagram:**
|
||||
|
||||
Both operator icons sit in the left margin at `x=20`, vertically positioned to align with the band they feed into (usually Phase 1):
|
||||
|
||||
```
|
||||
operator-human: x=20, y=phase1_band_y + (band_h / 2) − 20 − 44 − 16
|
||||
operator-ai: x=20, y=phase1_band_y + (band_h / 2) − 16 + 16
|
||||
|
||||
label under each: ts at (36, icon_y + icon_h + 8), text-anchor="middle"
|
||||
```
|
||||
|
||||
The stacked human → ai pair is typical (human provides the target; ai runs the operation). Connect them with a short `arr` line, then connect the ai icon to the band's first card with an arrow entering the left edge of the band.
|
||||
|
||||
If any check fails, fix the SVG before saving. See `pitfalls.md` entries #19–#28 for the full list of glyph-related failure modes and their fixes.
|
||||
|
|
@ -1,492 +0,0 @@
|
|||
# Illustrative Diagram
|
||||
|
||||
For building *intuition* — the kind of diagram that makes a reader go "oh, *that's* what it's doing". Unlike flowcharts (which document steps) or structural diagrams (which document containment), an illustrative diagram draws the mechanism. The shape of the drawing *is* the explanation.
|
||||
|
||||
## When to use
|
||||
|
||||
This is the default for *"how does X actually work"* questions with no further qualification. Reach for it whenever the breakthrough moment is visual rather than procedural.
|
||||
|
||||
- **Physical subjects** — water heaters, engines, lungs, heart valves, batteries, transistors. Draw simplified cross-sections or cutaways.
|
||||
- **Abstract subjects** — transformer attention, gradient descent, hash tables, the call stack, embeddings, recursion. Invent a spatial metaphor that makes the mechanism obvious.
|
||||
|
||||
Trigger phrases: "how does X work", "explain X", "I don't get X", "give me an intuition for X".
|
||||
|
||||
## When NOT to use
|
||||
|
||||
- The reader wants a reference, not an intuition. "What are the components of a transformer" is a structural diagram (labeled boxes). "How does attention work" is illustrative (fan of weighted lines).
|
||||
- The metaphor would be arbitrary rather than revealing. Drawing "the cloud" as a cloud shape teaches nothing about how distributed computing works — skip the illustration and use a structural diagram instead.
|
||||
- The mechanism is actually a sequence of discrete steps. That's a flowchart.
|
||||
|
||||
## Core principle
|
||||
|
||||
**Draw the mechanism, not a diagram *about* the mechanism.** Spatial arrangement carries the meaning; labels annotate. A good illustrative diagram still reads clearly even if you remove all the text.
|
||||
|
||||
- Color encodes intensity. Warm ramps (amber, coral, red) for heat/energy/pressure/activity. Cool ramps (blue, teal) for cold/calm/dormant. Gray for inert structure. A reader should glance at the drawing and immediately see *where the action is* without reading a label.
|
||||
- Layout follows the subject's geometry. Tall narrow subject → tall narrow drawing. Wide flat subject → wide flat drawing. Let the thing dictate proportions within the 680px viewBox.
|
||||
- Layering is encouraged for shapes. Unlike flowcharts where boxes never overlap, illustrative diagrams use z-ordering deliberately: a pipe entering a tank goes *behind* the tank wall, a flame goes *under* the kettle. Later in source = on top.
|
||||
|
||||
## Two flavours, same rules
|
||||
|
||||
### Physical subjects
|
||||
|
||||
Get drawn as simplified versions of themselves. Cross-sections, cutaways, schematics.
|
||||
|
||||
- A water heater is a tall rounded rect with a burner underneath. Not a Bézier portrait of a water heater.
|
||||
- A flame is three triangles, not a fire.
|
||||
- A lung is a branching tree in an oval-ish cavity. Not a medical illustration.
|
||||
- A transistor is three terminals meeting at a junction. Not a 3D render.
|
||||
|
||||
**Fidelity ceiling**: if a `<path>` needs more than ~6 segments, simplify it. Recognizable silhouette beats accurate contour.
|
||||
|
||||
### Abstract subjects
|
||||
|
||||
Get drawn as spatial metaphors that make the mechanism obvious.
|
||||
|
||||
- **Attention in a transformer** — a row of tokens with weight-scaled lines fanning from one query token to every other token. Line thickness = attention weight.
|
||||
- **Gradient descent** — a contour surface with a dot rolling downhill, trailing a path of discrete steps.
|
||||
- **Hash table** — a funnel dropping items into a row of labeled buckets.
|
||||
- **Call stack** — a vertical stack of frames growing upward with each push.
|
||||
- **Embeddings** — a 2D scatter of labeled dots, clusters visible by position.
|
||||
- **Convolution** — a small kernel sliding across a grid, highlighting the current receptive field.
|
||||
|
||||
The metaphor *is* the explanation. Don't label the metaphor — if you have to write "this represents attention", the drawing isn't doing its job.
|
||||
|
||||
## What changes from the flowchart/structural rules
|
||||
|
||||
- **Shapes are freeform.** Use `<path>`, `<ellipse>`, `<circle>`, `<polygon>`, curved lines. You're not limited to rounded rects.
|
||||
- **Color encodes intensity, not category.** Warm = active/high-weight/attended-to, cool or gray = dormant/low-weight/ignored. The reader should see where the energy is without reading a label.
|
||||
- **Layering and overlap are allowed — for shapes.** Draw a pipe entering the tank body. Draw insulation wrapping a chamber. Use z-order deliberately.
|
||||
- **Text is still the exception — never let a stroke cross a label.** The overlap permission is for shapes only. Every label needs 8px of clear air between its baseline and the nearest stroke. If there's no quiet region for a label, the drawing is too dense — remove something or split into two diagrams.
|
||||
- **Small shape-based indicators are allowed** when they tell the reader about physical state. Triangles for flames. Circles for bubbles. Wavy lines for steam or radiation. Keep them simple — basic SVG primitives, not detailed illustrations.
|
||||
- **Lines stop at component edges.** When a stroke meets a component (a pipe entering a tank, a wire into a terminal, an arrow into a region), draw the line as segments that *end at the component's boundary* — don't draw straight through the component and rely on the fill to hide the overdraw. The host page's background color is not guaranteed (WeChat dark mode, Notion, markdown viewers), so any fill-based occlusion becomes a coupling between stroke color and background color. Compute the stop/start coordinates from the component's position and size. This matters more for illustrative diagrams than for flowcharts because curved and irregular edges make the overdraw more visible.
|
||||
- **One gradient is permitted** — and only one. This is the single exception to the flat-fills rule. Use it to show a *continuous* physical property (temperature stratification in a tank, pressure drop along a pipe) with a `<linearGradient>` between exactly two stops from the same ramp. No radial gradients, no multi-stop fades, no decoration gradients. If two stacked flat-fill rects communicate the same thing, do that instead.
|
||||
|
||||
## Label placement
|
||||
|
||||
At 680px width you don't have room for a drawing *and* label columns on both sides. Pick one side and put all the labels there.
|
||||
|
||||
**Default to right-side labels** with `text-anchor="start"`. Labels on the left with `text-anchor="end"` are the ones that clip — the text extends leftward from x and long labels blow past x=0 without warning.
|
||||
|
||||
Reserve **at least 140px** of horizontal margin on the label side. Your drawing sits in roughly x=40 to x=500, labels sit in x=510 to x=640.
|
||||
|
||||
For each label, draw a dashed leader line from the referenced feature on the drawing to the label:
|
||||
|
||||
```svg
|
||||
<g>
|
||||
<line class="leader" x1="380" y1="120" x2="510" y2="140"/>
|
||||
<circle cx="380" cy="120" r="2" fill="#888780"/>
|
||||
<text class="ts" x="516" y="144">Hot zone</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
The small circle at the anchor end makes the leader read as an intentional pointer rather than a stray stroke.
|
||||
|
||||
**Large internal zones** (big enough to hold text without crowding) can have labels sitting inside them, with at least 20px of clear air from any edge.
|
||||
|
||||
## Composition order
|
||||
|
||||
Write the SVG in this layering order so z-index comes out right:
|
||||
|
||||
1. **Silhouette first.** The largest shape, centered in the viewBox. This defines the subject's footprint.
|
||||
2. **Internal structure.** Chambers, pipes, membranes, mechanical parts. Drawn inside the silhouette.
|
||||
3. **State indicators.** Color fills showing temperature/pressure/concentration, the one optional gradient. Applied after shapes so they tint on top of the structure.
|
||||
4. **External connections.** Pipes entering or exiting, arrows showing flow direction.
|
||||
5. **Labels and leader lines.** Last, so they sit on top of everything else.
|
||||
|
||||
Don't try to optimize this order — just follow it every time. Z-ordering bugs are hard to debug after the fact.
|
||||
|
||||
## Tiny worked example — attention fan
|
||||
|
||||
Request: "how does attention work in a transformer"
|
||||
|
||||
Plan:
|
||||
- Spatial metaphor: a row of 5 tokens at the bottom, one highlighted (the query), with 5 lines fanning up to a layer above. Line thickness encodes weight — thickest line to "sat", mid lines to nearby tokens, thin lines to distant ones.
|
||||
- Colors: gray for dormant tokens, amber for the query token. Fan lines hardcoded amber `#EF9F27` with varying stroke-width and opacity.
|
||||
- One caption below the fan: "Line thickness = attention weight".
|
||||
|
||||
## Central subject + radial attachments
|
||||
|
||||
For **"how does X interact with its environment"** diagrams where a central subject connects outward to a small set of optional capabilities — an LLM with retrieval, tools, and memory; a browser with DOM, storage, and network; a GPU with shared memory, registers, and global memory. The layout is a hub-and-spoke: one subject in the middle, 2–4 attachments arranged around it, and dashed connectors showing that each attachment is an *available capability*, not a mandatory step.
|
||||
|
||||
**Why illustrative and not structural**: the reader's question is "how does the subject use its environment" — that's intuition about a mechanism, not a documentation of what lives inside what. A structural diagram of "LLM + retrieval + tools + memory" would read like a block diagram; the radial layout instead reads like "this thing reaches out to these things when it needs to".
|
||||
|
||||
### Anatomy
|
||||
|
||||
```
|
||||
[ In pill ] → [ LLM Call ] → [ Out pill ]
|
||||
|
|
||||
┌──────┼──────┐ ← three dashed spokes dropping
|
||||
↓ ↓ ↓ from the subject center
|
||||
[Retrieval] [Tools] [Memory]
|
||||
```
|
||||
|
||||
- A **horizontal mainline** across the vertical center of the diagram carries the primary flow. Both ends are pill-shaped terminals.
|
||||
- The **central subject** is the node in the middle of the mainline — a rounded rect (not a pill), typically using a color ramp (`c-purple` is conventional for "the model").
|
||||
- **2–4 attachments** sit below the subject as rounded rects, spaced horizontally.
|
||||
- **Dashed `.arr-alt` connectors** drop from the subject's bottom edge to each attachment's top edge. These are dashed to communicate "optional capability, used when needed" — the mainline is what always runs, the spokes are what the subject reaches for.
|
||||
- **Short 1–2 word labels** beside each spoke (`Query` / `Call` / `Read`) communicate the relationship. See the `.arr-alt` notes in `svg-template.md`.
|
||||
|
||||
### Layout
|
||||
|
||||
Standard geometry at viewBox width 680:
|
||||
|
||||
```
|
||||
Mainline y = 160 # rect top; cy = 182 (rect height 44)
|
||||
In pill x=60, w=80 # right edge 140
|
||||
Subject box x=290, w=180 # center (380, 182), bottom edge 204
|
||||
Out pill x=560, w=80 # center (600, 182), right edge 640 ✓
|
||||
|
||||
Attachment row y = 320 # 116px below subject cy; bottom 364
|
||||
Attachment w = 120 # fits "Retrieval" (9 × 8 + 24 = 96)
|
||||
Attachment 1 x=120, center (180, 342)
|
||||
Attachment 2 x=320, center (380, 342) # directly below subject
|
||||
Attachment 3 x=520, center (580, 342) # right edge 640 ✓
|
||||
|
||||
Spoke channel ymid = 290 # 86px below subject bottom, 30px above att row
|
||||
|
||||
Spoke 1 (L): path (380, 204) → (380, 290) → (180, 290) → (180, 320)
|
||||
Spoke 2 (M): straight (380, 204) → (380, 320)
|
||||
Spoke 3 (R): path (380, 204) → (380, 290) → (580, 290) → (580, 320)
|
||||
```
|
||||
|
||||
The center spoke is a straight vertical line; the outer spokes are L-bends that drop to a shared horizontal channel at `y = 290` before turning down into the attachments. Don't route the outer spokes as diagonals — diagonal lines in an otherwise orthogonal diagram read as stray strokes.
|
||||
|
||||
Mainline arrow endpoints: `In→Subject` runs from `(140, 182)` to `(290, 182)`; `Subject→Out` runs from `(470, 182)` to `(560, 182)`. Both use solid `.arr`.
|
||||
|
||||
### Rules
|
||||
|
||||
- **At most 4 attachments.** Five overflows the horizontal budget at 140px each. If you need 5+, split into two diagrams (subject + capability group A, subject + capability group B) or step the pattern up to a structural diagram.
|
||||
- **Dashed spokes only when the attachments are optional.** If every spoke always runs, the pattern isn't radial — it's a fan-out flowchart and should use solid `.arr` connectors in the simple fan-out pattern from `flowchart.md`.
|
||||
- **Spoke labels stay short.** 1–2 words, `ts`, positioned beside the spoke midpoint with `text-anchor="end"` for labels on the left of a spoke and `"start"` for labels on the right. Bidirectional relationships draw two labels on opposite sides of the spoke ("Query" above, "Results" below — same spoke).
|
||||
- **No rotated text.** Design-system.md's rotation ban applies here; radial layouts do *not* unlock rotated labels.
|
||||
- **No literal icons.** A radial hub describing "LLM + tools + memory" uses labeled rounded rects for each capability, not tool wrench glyphs or memory-chip drawings.
|
||||
- **Central subject stays a rect, not a circle.** Circles read as "state" or "particle"; this pattern is about a subject reaching for capabilities, and a rounded rect labels cleanly at 14px.
|
||||
|
||||
### Worked example — LLM hub
|
||||
|
||||
Request: "how an LLM uses retrieval, tools, and memory"
|
||||
|
||||
Plan:
|
||||
- Mainline `In` → `LLM Call` → `Out` along the horizontal center; rects at y=160, cy=182
|
||||
- `LLM Call` uses `c-purple` to mark it as the central subject
|
||||
- 3 attachments below at y=320: `Retrieval` / `Tools` / `Memory`, all gray, w=120 each
|
||||
- Three dashed `.arr-alt` spokes — the middle spoke straight down, the outer two as L-bends through a shared channel at y=290
|
||||
- One short label beside each spoke (`Query` / `Call` / `Read`) picking the most representative verb; the bidirectional return path is left implicit so the diagram stays quiet
|
||||
- viewBox: last element `Retrieval` bottom = 320 + 44 = 364 → H = **384**
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 384" font-family="...">
|
||||
<style>...</style>
|
||||
<defs><marker id="arrow" .../></defs>
|
||||
|
||||
<rect class="box" x="60" y="160" width="80" height="44" rx="22"/>
|
||||
<text class="t" x="100" y="182" text-anchor="middle" dominant-baseline="central">In</text>
|
||||
|
||||
<g class="c-purple">
|
||||
<rect x="290" y="160" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="380" y="182" text-anchor="middle" dominant-baseline="central">LLM Call</text>
|
||||
</g>
|
||||
|
||||
<rect class="box" x="560" y="160" width="80" height="44" rx="22"/>
|
||||
<text class="t" x="600" y="182" text-anchor="middle" dominant-baseline="central">Out</text>
|
||||
|
||||
<line x1="140" y1="182" x2="288" y2="182" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="470" y1="182" x2="558" y2="182" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="120" y="320" width="120" height="44" rx="6"/>
|
||||
<text class="th" x="180" y="342" text-anchor="middle" dominant-baseline="central">Retrieval</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="320" y="320" width="120" height="44" rx="6"/>
|
||||
<text class="th" x="380" y="342" text-anchor="middle" dominant-baseline="central">Tools</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="520" y="320" width="120" height="44" rx="6"/>
|
||||
<text class="th" x="580" y="342" text-anchor="middle" dominant-baseline="central">Memory</text>
|
||||
</g>
|
||||
|
||||
<path d="M 380 204 L 380 290 L 180 290 L 180 318" class="arr-alt" fill="none" marker-end="url(#arrow)"/>
|
||||
<line x1="380" y1="204" x2="380" y2="318" class="arr-alt" marker-end="url(#arrow)"/>
|
||||
<path d="M 380 204 L 380 290 L 580 290 L 580 318" class="arr-alt" fill="none" marker-end="url(#arrow)"/>
|
||||
|
||||
<text class="ts" x="280" y="284" text-anchor="middle">Query</text>
|
||||
<text class="ts" x="390" y="258" text-anchor="start">Call</text>
|
||||
<text class="ts" x="480" y="284" text-anchor="middle">Read</text>
|
||||
</svg>
|
||||
```
|
||||
|
||||
Notes on this example:
|
||||
- The central subject `LLM Call` uses `c-purple` while attachments stay gray — this is the single-accent rule from the design system, where the accent ramp anchors reader attention on "the thing doing the reaching".
|
||||
- All three spokes use `class="arr-alt"` because retrieval, tool-calls, and memory reads are **optional** — an LLM invocation might use all three, some, or none. If the diagram were describing a specific pipeline where all three always run, the spokes would be solid `.arr`.
|
||||
- **One short verb per spoke**, not a bidirectional pair. Two stacked labels per spoke would crowd the horizontal channel at y=290; picking the representative verb (Query, Call, Read) is quieter and the reader infers the return path. If the return direction matters, split into two diagrams rather than stacking labels.
|
||||
- Each label sits on the horizontal channel (y=284, 6px above y=290) directly over the L-bend corner so it reads as belonging to that spoke. The middle spoke's label sits 32px above to avoid colliding with the other labels.
|
||||
- Spoke 2 drops straight down from the subject's bottom edge to the middle attachment's top edge because they're vertically aligned — no need for a bend. The arrowhead lands at y=318 (2px short of the attachment's top at y=320) to leave the 10px-ish breathing room flowchart.md recommends.
|
||||
- Rightmost rect edges: Out pill ends at `560 + 80 = 640` ✓, Memory ends at `520 + 120 = 640` ✓ — both exactly flush with the 640 bound.
|
||||
|
||||
## Spectrum / continuum
|
||||
|
||||
For topics where the reader's question is *"where on the scale between X and Y does Z sit"* — Anthropic's "Finding the sweet spot" diagram (image #5) is the canonical example. The layout is a single horizontal axis with opposing concepts at either end, tick points along it, option boxes hanging below each tick, and optional italic captions under the options.
|
||||
|
||||
This is a **new illustrative sub-pattern**, distinct from the radial-attachments layout above. Use it when the intuition the reader needs is "these options are *positions* on a single continuum, and one of them is better than the others because of where it sits on the continuum".
|
||||
|
||||
### When to use
|
||||
|
||||
- "Finding the sweet spot between A and B"
|
||||
- "The trade-off between flexibility and rigidity / latency and quality / cost and performance"
|
||||
- "Where does approach X sit on the X-to-Y spectrum"
|
||||
- Any topic where the reader has been thinking in a binary and you're showing them it's a gradient
|
||||
|
||||
### When not to use
|
||||
|
||||
- The options are unordered categories (use a structural diagram with sibling boxes instead).
|
||||
- The trade-off has more than one dimension (spectrum only works for 1-D).
|
||||
- There's no "wrong" end to the axis — the spectrum works because both extremes are undesirable and there's a sweet spot in between. If one end is just "better", use a chart, not this pattern.
|
||||
|
||||
### Geometry
|
||||
|
||||
See also `layout-math.md` → "Spectrum geometry".
|
||||
|
||||
| Element | Coordinates |
|
||||
|--------------------|--------------------------------------------------|
|
||||
| Eyebrow (optional) | `(340, 50)`, class `eyebrow`, `text-anchor="middle"` |
|
||||
| Axis line | `x1=80 y1=140 x2=600 y2=140`, class `arr`, markers on **both** ends |
|
||||
| Left-end label | `(80, 120)`, class `ts`, `text-anchor="start"`, uppercase content |
|
||||
| Right-end label | `(600, 120)`, class `ts`, `text-anchor="end"`, uppercase content |
|
||||
| Tick points (N) | circles at `(tick_x, 140)` `r=6` |
|
||||
| Option boxes | `y=200 h=60 w=120 rx=6`, one per tick point |
|
||||
| Captions (optional)| `y=292 and y=308`, italic `ts`, `text-anchor="middle"` |
|
||||
|
||||
For **3 ticks**, tick_x = `160, 340, 520`; option box x = `100, 280, 460` (each 120 wide, centered on tick_x).
|
||||
For **4 ticks**, tick_x = `140, 280, 420, 560`; option box x = `80, 220, 360, 500` (each 120 wide).
|
||||
For **5 ticks**, tick_x = `120, 240, 360, 480, 600`; option box width shrinks to 100 (x = `70, 190, 310, 430, 550`).
|
||||
|
||||
### The axis
|
||||
|
||||
The axis is a single horizontal line with arrow markers at both ends (so it reads "this extends both ways, no natural direction"). Unlike regular arrows which use `marker-end`, the spectrum axis uses **both** `marker-start` and `marker-end`:
|
||||
|
||||
```svg
|
||||
<line class="arr" x1="80" y1="140" x2="600" y2="140"
|
||||
marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
The `context-stroke` marker in the template automatically colors both arrowheads to match the line.
|
||||
|
||||
**Do not** add a color ramp to the axis. It's always `arr` (neutral gray) — colored axes imply that one end is "good" and the other "bad", which defeats the entire point of a spectrum diagram.
|
||||
|
||||
### End labels
|
||||
|
||||
The labels at the two ends describe the **extremes** the axis is measuring between. Keep them to 2–3 words, usually in all caps or title case to signal they're axis labels, not box labels. In image #5 these are "NO STRUCTURE" and "TOO RIGID".
|
||||
|
||||
Place them *above* the axis with at least 8px of clearance between the end of the axis arrowhead and the label's text baseline. Left label uses `text-anchor="start"` anchored at the axis x1; right label uses `text-anchor="end"` anchored at the axis x2. No leader lines — the proximity to the axis end is enough.
|
||||
|
||||
### Tick points and option boxes
|
||||
|
||||
Each tick is a small circle (r=6) sitting on the axis line. Tick circles use `c-gray` by default (subtle dots) — the sweet-spot tick upgrades to an accent ramp (`c-green` or `c-amber`) to draw the eye.
|
||||
|
||||
Each tick has a **matching option box** directly below it. The option box is a two-line node (60 tall, title + 1-word id) centered on the tick's x. The option's id (left subtitle like "A", "B", "C") is optional and mostly for referring back in the caption.
|
||||
|
||||
The sweet-spot option's box takes the same accent ramp as its tick — green/amber highlights "this is the one". All other option boxes stay neutral gray (`c-gray`).
|
||||
|
||||
### Italic per-option captions
|
||||
|
||||
Below each option box, an optional 1–2 line italic `ts` caption explains *what that option feels like*. Keep each line ≤24 characters (≈3 words) so the captions don't spill into each other horizontally. Captions are always italic (use inline `font-style="italic"`), matching the `caption` class from poster flowcharts but placed under each option individually.
|
||||
|
||||
```svg
|
||||
<text class="ts" x="160" y="292" text-anchor="middle" font-style="italic">vague and</text>
|
||||
<text class="ts" x="160" y="308" text-anchor="middle" font-style="italic">untestable</text>
|
||||
```
|
||||
|
||||
If the captions need more than 2 lines each, the spectrum is the wrong pattern — the options need their own diagram.
|
||||
|
||||
### Eyebrow
|
||||
|
||||
A spectrum diagram can carry a small `eyebrow`-class label at the very top (y=50) to name the trade-off: "CHOOSING AN EVALUATION APPROACH". This is optional but strongly recommended — without an eyebrow the reader has to infer "what is this spectrum *about*" from the end labels alone, and end labels are short by design.
|
||||
|
||||
### Worked example — sweet spot
|
||||
|
||||
Request: "Show the trade-off between an evaluation that's too loose and one that's too rigid."
|
||||
|
||||
Plan:
|
||||
- Eyebrow: "CHOOSING AN EVALUATION"
|
||||
- Axis: "NO STRUCTURE" on the left, "TOO RIGID" on the right
|
||||
- 3 tick points with option boxes: "A · none", "B · rubric", "C · checklist"
|
||||
- "B · rubric" is the sweet spot → `c-green`
|
||||
- Italic captions under each: "vague and / untestable", "structured / and flexible", "brittle, / doesn't generalize"
|
||||
- viewBox H ≈ 340
|
||||
|
||||
```svg
|
||||
<text class="eyebrow" x="340" y="50" text-anchor="middle">Choosing an evaluation</text>
|
||||
|
||||
<text class="ts" x="80" y="120" text-anchor="start">NO STRUCTURE</text>
|
||||
<text class="ts" x="600" y="120" text-anchor="end">TOO RIGID</text>
|
||||
<line class="arr" x1="80" y1="140" x2="600" y2="140"
|
||||
marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
||||
|
||||
<circle class="c-gray" cx="160" cy="140" r="6"/>
|
||||
<circle class="c-green" cx="340" cy="140" r="6"/>
|
||||
<circle class="c-gray" cx="520" cy="140" r="6"/>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="100" y="200" width="120" height="60" rx="6"/>
|
||||
<text class="th" x="160" y="222" text-anchor="middle">None</text>
|
||||
<text class="ts" x="160" y="242" text-anchor="middle">A</text>
|
||||
</g>
|
||||
<g class="c-green">
|
||||
<rect x="280" y="200" width="120" height="60" rx="6"/>
|
||||
<text class="th" x="340" y="222" text-anchor="middle">Rubric</text>
|
||||
<text class="ts" x="340" y="242" text-anchor="middle">B</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="460" y="200" width="120" height="60" rx="6"/>
|
||||
<text class="th" x="520" y="222" text-anchor="middle">Checklist</text>
|
||||
<text class="ts" x="520" y="242" text-anchor="middle">C</text>
|
||||
</g>
|
||||
|
||||
<text class="ts" x="160" y="292" text-anchor="middle" font-style="italic">vague and</text>
|
||||
<text class="ts" x="160" y="308" text-anchor="middle" font-style="italic">untestable</text>
|
||||
<text class="ts" x="340" y="292" text-anchor="middle" font-style="italic">structured</text>
|
||||
<text class="ts" x="340" y="308" text-anchor="middle" font-style="italic">and flexible</text>
|
||||
<text class="ts" x="520" y="292" text-anchor="middle" font-style="italic">brittle, doesn't</text>
|
||||
<text class="ts" x="520" y="308" text-anchor="middle" font-style="italic">generalize</text>
|
||||
```
|
||||
|
||||
viewBox: max_y = 308 + 4 = 312 → H = **332**.
|
||||
|
||||
### Pitfalls
|
||||
|
||||
- **Axis end labels clipped**: if `text-anchor="end"` is used on the left label or `text-anchor="start"` on the right, labels extend past the axis ends and read backwards. Left is `start`, right is `end`.
|
||||
- **Sweet spot not the right color**: using `c-coral` for the sweet spot because "it's the attention color" is backwards — `c-coral` reads as "warning", `c-green` reads as "good", `c-amber` reads as "deliberate". Match the ramp to the *outcome*, not just the emphasis.
|
||||
- **Tick count ≠ option count**: every tick needs an option box and vice versa. An orphan tick or an orphan option is a layout bug.
|
||||
- **Captions too long**: 2 italic lines at ≤24 chars each. Longer captions collide.
|
||||
|
||||
## Annotation circle on connector
|
||||
|
||||
For diagrams where a connector (arrow between two subjects) has a **named mediator** that deserves to be visible as a node itself. Image #13 (Claude ↔ Environment with Skill) is the canonical case: Claude talks to its environment, and the "Skill" that mediates the exchange is too important to be a mere label on the arrow — it becomes a labeled circle sitting on the connector.
|
||||
|
||||
**When to use.** Only in illustrative diagrams. Flowcharts and sequence diagrams label their arrows with text; structural diagrams use arrow labels or inline text. The annotation-circle-on-connector is an *illustrative* convention specifically — it promotes "the thing on the arrow" to a visual node without breaking the two-subject layout.
|
||||
|
||||
**When not to use.**
|
||||
|
||||
- The mediator is actually a step in a sequence — use a flowchart node.
|
||||
- The mediator is a system component — use a structural box.
|
||||
- There are two or more mediators on the same connector — the pattern doesn't stack; restructure the diagram.
|
||||
|
||||
### Geometry
|
||||
|
||||
See `glyphs.md` → "Annotation circle on connector" for the full glyph. The placement rule:
|
||||
|
||||
| Element | Coordinates |
|
||||
|------------------------|--------------------------------------------|
|
||||
| Subject A | e.g., `x=80 y=160 w=180 h=56` |
|
||||
| Subject B | e.g., `x=440 y=160 w=180 h=56` |
|
||||
| Bidirectional arrows | A↔B, offset by 10px vertically |
|
||||
| Annotation circle | `cx = (A_right + B_left) / 2`, `cy = arrows_y_midpoint` |
|
||||
| Short connector line | `x1 = cx, y1 = cy, x2 = cx, y2 = cy + 40`, class `arr` (no arrowhead) |
|
||||
|
||||
The annotation circle sits **above** the pair of bidirectional arrows, with a short vertical connector line (no arrowhead) dropping from the bottom of the circle to the midpoint of the arrow pair. This reads as "this circle is a flag attached to the arrow below it".
|
||||
|
||||
### Arrow routing
|
||||
|
||||
The two-way exchange between Subject A and Subject B uses a pair of offset horizontal arrows:
|
||||
|
||||
```svg
|
||||
<!-- Claude → Environment (top arrow) -->
|
||||
<line class="arr" x1="260" y1="184" x2="440" y2="184" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="350" y="178" text-anchor="middle">Action</text>
|
||||
<!-- Environment → Claude (bottom arrow) -->
|
||||
<line class="arr" x1="440" y1="204" x2="260" y2="204" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="350" y="218" text-anchor="middle">Feedback</text>
|
||||
```
|
||||
|
||||
The arrow-pair y midpoint is `(184 + 204) / 2 = 194`. The annotation circle's connector line ends at `(350, 194)` (the midpoint). The circle itself sits above, centered at `(350, 150)` with r=30.
|
||||
|
||||
### The annotation circle itself
|
||||
|
||||
```svg
|
||||
<g transform="translate(318, 118)">
|
||||
<circle class="box" cx="32" cy="32" r="30"/>
|
||||
<g class="c-teal">
|
||||
<rect x="4" y="22" width="56" height="20" rx="10"/>
|
||||
<text class="th" x="32" y="36" text-anchor="middle">Skill</text>
|
||||
</g>
|
||||
</g>
|
||||
<line class="arr" x1="350" y1="180" x2="350" y2="194"/>
|
||||
```
|
||||
|
||||
The outer circle uses the neutral `box` class (so the ring itself doesn't fight for attention), and the inner pill uses a ramp (usually `c-teal` — the mediator is an aid, not an adversary).
|
||||
|
||||
### Label length
|
||||
|
||||
The inner pill caps at ~6 Latin characters. "Skill" fits. "Constraint" does not — shorten or restructure. For CJK, cap at 3 characters.
|
||||
|
||||
### Multiple circles on one arrow
|
||||
|
||||
**Not allowed.** If two mediators sit on the same connector, you're describing a sequence, not a mediator — use a flowchart instead.
|
||||
|
||||
## Decorative icon inside box
|
||||
|
||||
The **only** place in the design system where an icon is allowed inside a rect is inside an illustrative diagram's subject box or a structural diagram's hub/shared-state box. The rule is strict: the icon must come from `glyphs.md`'s icon set, and it lives in the lower half of a box that's at least 80px tall.
|
||||
|
||||
### Why only here
|
||||
|
||||
Flowcharts and sequence diagrams use the label alone — adding an icon to a flowchart box just adds visual noise to a process the reader is tracking sequentially. In an illustrative diagram, the icon reinforces *what kind of thing the box represents* (a document store, a terminal, a code script), and that reinforcement is part of the intuition the diagram is trying to build.
|
||||
|
||||
### Allowed icon set
|
||||
|
||||
Only these, from `glyphs.md`:
|
||||
|
||||
- `doc-icon` — documents, artifacts, shared files, logs
|
||||
- `terminal-icon` — terminals, computers, shells, CLI environments
|
||||
- `script-icon` — inline scripts, programmatic tool calls (used in structural "Parallel independent rounds" pattern; rarely in illustrative)
|
||||
|
||||
**No other icons.** Do not draw an LLM brain, a wrench for "tools", a cloud for "cloud", a gear for "settings". Label the rect.
|
||||
|
||||
### Placement
|
||||
|
||||
For a box at `(box_x, box_y, box_w, box_h)`:
|
||||
|
||||
- Title `th` at `(box_x + box_w/2, box_y + 22)`.
|
||||
- Subtitle `ts` at `(box_x + box_w/2, box_y + 40)` (optional).
|
||||
- Icon anchor at `(box_x + box_w/2 − 12, box_y + box_h − 36)` (for a 24-wide, 28-tall icon).
|
||||
|
||||
The icon must have at least **8px of clear air** above it (between the subtitle baseline + descender and the icon's top). If the box is too short to fit both text and icon, grow the box — don't squeeze them.
|
||||
|
||||
### Color
|
||||
|
||||
The icon uses `arr` or `box` classes (neutral stroke), **not** a ramp. The parent box carries the ramp color — the icon is a secondary visual cue that inherits the reader's attention from the box, not from its own color.
|
||||
|
||||
```svg
|
||||
<g class="c-amber">
|
||||
<rect x="260" y="280" width="160" height="80" rx="10"/>
|
||||
<text class="th" x="340" y="302" text-anchor="middle">Shared state</text>
|
||||
<text class="ts" x="340" y="320" text-anchor="middle">Task artifacts</text>
|
||||
<g transform="translate(328, 324)">
|
||||
<path class="arr" d="M2 2 L16 2 L22 8 L22 26 L2 26 Z" fill="none"/>
|
||||
<path class="arr" d="M16 2 L16 8 L22 8" fill="none"/>
|
||||
<line class="arr" x1="6" y1="14" x2="18" y2="14"/>
|
||||
<line class="arr" x1="6" y1="18" x2="18" y2="18"/>
|
||||
<line class="arr" x1="6" y1="22" x2="14" y2="22"/>
|
||||
</g>
|
||||
</g>
|
||||
```
|
||||
|
||||
### Multiple icons in one diagram
|
||||
|
||||
One icon per box is the cap. Two in the same diagram is fine if they're in different boxes and each serves its own intuition. Three or more icons in one diagram starts to feel like a screenshot — simplify.
|
||||
|
||||
## Rules of thumb
|
||||
|
||||
- **Silhouette > outline.** A simple rounded rect labeled "Reactor" beats a detailed reactor drawing. The reader needs to recognize *what kind of thing* it is, not admire your drafting.
|
||||
- **Color on what matters.** If the hot zone is the point, color only the hot zone. Leave the rest of the drawing in neutral stroke lines. Color where the meaning is.
|
||||
- **One gradient, deliberately.** If you reach for a gradient, ask: "is this showing a continuous physical property?" If yes, use it. If no, use flat fills.
|
||||
- **Labels in the quiet zone.** Before placing a label, check that no stroke will cross its bounding box. If there's no quiet zone, remove something from the drawing.
|
||||
- **One idea per diagram.** If you catch yourself cramming two mechanisms into one drawing, split them. Two 10-element diagrams beat one 20-element diagram every time.
|
||||
|
||||
## Common failure modes specific to illustrative
|
||||
|
||||
- **Too literal** — tracing a real photo of the subject. Six Bézier curves, not sixty. Stylize.
|
||||
- **Label crossed by stroke** — text sits where a pipe or arrow also passes. Move the label to the margin with a leader line; never use a background rect to hide the stroke.
|
||||
- **Gradient as decoration** — using a gradient because it "looks nice". Remove it. Gradients are only for continuous physical properties.
|
||||
- **Color by category instead of intensity** — dormant and active nodes using different ramps for variety. Use gray for dormant, one accent for active. Let the color tell the story.
|
||||
- **Both-sides labels** — drawing has labels on the left *and* the right. Pick one side. At 680px wide, you don't have room for both.
|
||||
|
|
@ -1,552 +0,0 @@
|
|||
# Layout Math
|
||||
|
||||
SVG has no auto-layout. Every coordinate, width, and height is hand-computed. Most diagram failures trace back to text overflowing its container or elements landing outside the viewBox. This file is the arithmetic you run *before* writing any `<rect>`.
|
||||
|
||||
## The coordinate system
|
||||
|
||||
- **viewBox**: `0 0 680 H`. Width is always 680. Compute H from content.
|
||||
- **Safe area**: `x ∈ [40, 640]`, `y ∈ [40, H - 40]`. Leave 40px of breathing room on every edge.
|
||||
- **Usable width**: 600 px (640 − 40).
|
||||
- **Pixel units are 1:1.** The `width="100%"` with `viewBox="0 0 680 H"` means the browser scales the whole coordinate space to fit the container. A 14px font is really 14px. Character widths below assume this 1:1 ratio.
|
||||
|
||||
## Text width estimation
|
||||
|
||||
SVG `<text>` never auto-wraps. If you put more characters in a box than it can hold, they overflow visibly. Use these character-to-pixel multipliers, calibrated on Anthropic Sans:
|
||||
|
||||
| Case | Approx. width per character |
|
||||
|------------------------------------------|-----------------------------|
|
||||
| 14px latin, weight 500 (`th`, titles) | ~8 px |
|
||||
| 14px latin, weight 400 (`t`, body) | ~8 px |
|
||||
| 12px latin, weight 400 (`ts`, subtitles) | ~7 px |
|
||||
| 14px CJK (Chinese, Japanese, Korean) | ~15 px (~2× Latin) |
|
||||
| 12px CJK | ~13 px (~1.9× Latin) |
|
||||
|
||||
**Special characters:** chemical formulas (C₆H₁₂O₆), math symbols (∑ ∫ √), subscripts, superscripts all take the same horizontal space as normal characters — don't assume subscripts are narrower. For labels that mix Latin and formulas, add 30–50% padding to your width estimate.
|
||||
|
||||
### Empirical calibration samples
|
||||
|
||||
Real measurements of Anthropic Sans at the sizes this skill uses. The multipliers above are the safe ceilings derived from these samples — not the averages. Letter composition matters: narrow letters like `l`/`i`/`f` push the per-char average down, tall letters like `B`/`J`/`P`/`Q` push it up.
|
||||
|
||||
| Text | Chars | Weight | Size | Measured width | Per-char |
|
||||
|-----------------------------------------|-------|--------|-------|----------------|----------|
|
||||
| `Authentication Service` | 22 | 500 | 14 px | 167 px | ~7.6 |
|
||||
| `Background Job Processor` | 24 | 500 | 14 px | 201 px | ~8.4 |
|
||||
| `Detects and validates incoming tokens` | 37 | 400 | 14 px | 279 px | ~7.5 |
|
||||
| `forwards request to` | 19 | 400 | 12 px | 123 px | ~6.5 |
|
||||
| `データベースサーバー接続` | 12 | 400 | 14 px | 181 px | ~15.1 |
|
||||
|
||||
The formula uses **8** for 14px Latin and **7** for 12px Latin because those are the safe ceilings, not the average. A label like "Background Job Processor" at 8.4 px/char would overflow a box sized from a 7.6 px/char average — always round up, never down.
|
||||
|
||||
**Worked sanity check**: `"Detects and validates incoming tokens"` (37 chars, `ts` class at 12px) → formula gives `37 × 7 + 24 = 283 px`. Measured 279 px + 24 px padding = 303 px. The formula is ~7% conservative, which is the correct direction for rect sizing — bias toward extra padding, never toward clipping.
|
||||
|
||||
### Rect sizing formula
|
||||
|
||||
For a two-line node (title + subtitle) the width must fit whichever line is longer:
|
||||
|
||||
```
|
||||
width_needed = max(title_chars × 8, subtitle_chars × 7) + 24
|
||||
```
|
||||
|
||||
The `+ 24` is 12px of horizontal padding on each side. If the text is centered, that's 12px of clear air between the text edge and the rect stroke — looks balanced.
|
||||
|
||||
For a single-line node:
|
||||
|
||||
```
|
||||
width_needed = title_chars × 8 + 24
|
||||
```
|
||||
|
||||
For CJK content, replace the 8 with 15 and the 7 with 13.
|
||||
|
||||
**Round up to the nearest 10px** for cleaner coordinates. A box that "needs" 167px becomes 170px.
|
||||
|
||||
### Common spacing failures — Wrong / Right
|
||||
|
||||
Three mistakes that produce valid SVG but visually broken diagrams. Use these as a quick sanity check.
|
||||
|
||||
**Vertical gap too small (nodes overlap):**
|
||||
|
||||
```
|
||||
Wrong: Node A ends at y=176, Node B starts at y=180 → 4px gap, labels nearly touching
|
||||
Right: Node A ends at y=176, Node B starts at y=236 → 60px gap (the minimum vertical gap)
|
||||
```
|
||||
|
||||
**Legend inside container boundary:**
|
||||
|
||||
```
|
||||
Wrong: Container bottom at y=380, legend at y=370 → legend reads as container content
|
||||
Right: Container bottom at y=380, legend at y=400 → 20px clear air, legend is diagram metadata
|
||||
(extend viewBox H to 436 to fit)
|
||||
```
|
||||
|
||||
**Horizontal tier overflow:**
|
||||
|
||||
```
|
||||
Wrong: 4 boxes × 180 + 3 gaps × 20 = 780 → overflows the 600px usable width by 180px
|
||||
Right: 4 boxes × 130 + 3 gaps × 20 = 580 → fits within 600px, centered at x=50
|
||||
(shorten labels or drop subtitles to fit the narrower boxes)
|
||||
```
|
||||
|
||||
### Worked examples
|
||||
|
||||
- `"JWT authentication"` (18 chars, title) → `18 × 8 + 24 = 168` → round to **170**
|
||||
- `"Validates user credentials"` (26 chars, subtitle) in a two-line box with title `"Login service"` (13 chars, 8-wide) → `max(13 × 8, 26 × 7) + 24 = max(104, 182) + 24 = 206` → round to **210**
|
||||
- `"微服务架构"` (5 CJK chars, title) → `5 × 15 + 24 = 99` → round to **100**
|
||||
- `"发送 HTTP 请求 forwards request"` (mixed, treat CJK chars as 15px and Latin as 8px) → `3×15 + 4×8 + 13×8 = 45 + 32 + 104 = 181 + spaces + 24 ≈ 220`
|
||||
|
||||
**Rule of thumb**: if you find yourself adding characters to a label, recheck the width. It's easy to write "Authentication" once, size the box to fit, then expand it to "Authentication service" and forget to resize.
|
||||
|
||||
## viewBox height calculation
|
||||
|
||||
After placing every element, compute max_y:
|
||||
|
||||
1. For every `<rect>`, compute `y + height`.
|
||||
2. For every `<text>`, the bottom is `y + 4` (roughly — 4px descent below the baseline).
|
||||
3. For every `<circle>`, it's `cy + r`.
|
||||
4. `max_y` is the maximum across all of these.
|
||||
5. `H = max_y + 20`
|
||||
|
||||
**Don't guess.** A 20px buffer below the last element keeps things tight without clipping descenders.
|
||||
|
||||
**Don't leave huge empty space at the bottom.** If the diagram ends at y=280, H should be 300 — not 560. Excess whitespace below the content feels like a rendering bug.
|
||||
|
||||
## Tier packing (horizontal rows)
|
||||
|
||||
When placing N boxes in a horizontal row, check total width before you start writing coordinates:
|
||||
|
||||
```
|
||||
total = N × box_width + (N - 1) × gap
|
||||
```
|
||||
|
||||
- `box_width`: chosen to fit the longest label in that row
|
||||
- `gap`: 20px minimum between adjacent boxes
|
||||
|
||||
Required: `total ≤ 600` (the usable width). If not, one of three things must give:
|
||||
|
||||
1. **Shrink box_width** — cut subtitles, shorten titles
|
||||
2. **Wrap to 2 rows**
|
||||
3. **Split into an overview diagram + detail diagrams** (one per sub-flow)
|
||||
|
||||
### Worked example — four consumer boxes
|
||||
|
||||
- 4 boxes, each containing `"Consumer N"` (10 chars title) and `"Processes events"` (16 chars subtitle)
|
||||
- Needed width per box: `max(10×8, 16×7) + 24 = max(80, 112) + 24 = 136` → round to **140**
|
||||
- 4 boxes × 140 + 3 gaps × 20 = 560 + 60 = **620** → **overflows** by 20
|
||||
- Fix: shrink to **130** each (check: `max(10×8, 16×7) + 24 = 136`, so 130 is tight — cut subtitle to 14 chars or drop it entirely)
|
||||
- Or: drop to 3 boxes per row, stack the fourth below
|
||||
- Or: 4 × 130 + 3 × 20 = 580, centered at `x = 40 + (600-580)/2 = 50` → boxes at x = 50, 200, 350, 500
|
||||
|
||||
## Swim-lane (sequence) layout
|
||||
|
||||
Sequence diagrams have their own coordinate system — actor columns ("lanes") along the top, dashed lifelines running down, numbered horizontal messages between them. The numbers below are fixed conventions so every sequence diagram in the skill feels consistent.
|
||||
|
||||
### Lane widths and centers
|
||||
|
||||
N = number of actors. Lane width `lane_w`, header rect width `header_w = lane_w − 20`, lifeline x = lane center. Gap between lanes = 20.
|
||||
|
||||
| N | lane_w | header_w | First offset | Lane centers (x) |
|
||||
|---|--------|----------|--------------|--------------------------------|
|
||||
| 2 | 290 | 270 | 40 | 185, 495 |
|
||||
| 3 | 186 | 166 | 40 | 133, 340, 547 |
|
||||
| 4 | 140 | 120 | 30 | 100, 260, 420, 580 |
|
||||
| 5 | 112 | 92 | 26 | 82, 214, 346, 478, 610 |
|
||||
| 6 | 100 | 80 | 30 | 80, 190, 300, 410, 520, 630 |
|
||||
|
||||
Hard cap: **4 actors** (comfortable), **6 actors** (maximum). 5 is allowed only when every actor title is ≤12 characters and has no subtitle. 6 requires every title ≤9 characters (`9 × 8 + 24 = 96`, just inside `header_w=100`) and no subtitle. N=6 also collapses the intra-lane gap (the 20px padding between `lane_w` and `header_w` becomes 0 — the headers pack shoulder-to-shoulder) and borrows 10px of left-edge margin and all of the right-edge margin: the rightmost header rect sits flush against x=680. Use N=6 only when every actor is genuinely essential — otherwise merge actors or split the diagram. More than 6 doesn't fit 680px legibly.
|
||||
|
||||
### Vertical geometry (fixed for all sequence diagrams)
|
||||
|
||||
```
|
||||
actor header box y = 40..88 (height 48, rx 8)
|
||||
title (th) y = 60 (dominant-baseline="central")
|
||||
role subtitle (ts) y = 78 (optional, one line, ≤16 chars)
|
||||
lifeline start y = 92
|
||||
first message row arrow_y[0] = 120
|
||||
message row pitch 44 px → arrow_y[k] = 120 + 44 · k
|
||||
lifeline tail y = last_arrow_y + 24
|
||||
```
|
||||
|
||||
### viewBox height
|
||||
|
||||
```
|
||||
header_bottom = 88
|
||||
first_arrow_y = 120
|
||||
last_arrow_y = 120 + 44 × (M − 1) # M = number of messages
|
||||
lifeline_bottom = last_arrow_y + 24
|
||||
note_bottom = lifeline_bottom + 16 + 48 # only if side note is at the bottom
|
||||
H = max(lifeline_bottom, note_bottom) + 20
|
||||
```
|
||||
|
||||
### Message arrows
|
||||
|
||||
Each message is a horizontal `<line>` at `y = arrow_y[k]`, running from the sender lifeline x to the receiver lifeline x with a 6px offset so the arrowhead doesn't touch the dashes:
|
||||
|
||||
- Left-to-right: `x1 = sender_x + 6`, `x2 = receiver_x − 6`
|
||||
- Right-to-left: `x1 = sender_x − 6`, `x2 = receiver_x + 6`
|
||||
|
||||
Use `class="arr-{ramp}"` where `{ramp}` matches the sender's actor ramp. Always set `marker-end="url(#arrow)"`.
|
||||
|
||||
### Message labels
|
||||
|
||||
Label sits **above** the arrow at `y = arrow_y − 10` with `text-anchor="middle"`, `class="ts"`, and the number prefix baked into the text (`"1. Click login"`, not separate tspans). Label x is the midpoint of sender and receiver: `(sender_x + receiver_x) / 2`.
|
||||
|
||||
Max characters that fit in a label between two adjacent lanes (e.g. lanes 1↔2 for N=4):
|
||||
|
||||
```
|
||||
label_chars_max ≈ (|sender_x − receiver_x| − 8) / 7
|
||||
```
|
||||
|
||||
For N=4 adjacent lanes (|Δx|=160): ~21 chars. For the longest cross-diagram leap (lane 1↔4, |Δx|=480): ~67 chars. In practice, aim for ≤30 chars — short messages read faster, even when there's room.
|
||||
|
||||
### Self-messages
|
||||
|
||||
A self-message is a 16×24 `<rect>` using the actor's `c-{ramp}` class, horizontally centered on the lifeline: `rect_x = lifeline_x − 8`, `rect_y = arrow_y − 12`. The label sits **to the left** of the rect (so it stays inside the diagram for the rightmost lane) at `x = rect_x − 8` with `text-anchor="end"`, `class="ts"`, `dominant-baseline="central"`, and the same numbered format.
|
||||
|
||||
### Side note
|
||||
|
||||
A `class="box"` rect with a `th` title and optional `ts` subtitle. Default size 240×48, `rx="10"`. Placement depends on N:
|
||||
|
||||
- **N ≤ 3**: top-left at `(40, 40)` — tucks beside the first actor header, no overlap because `first_offset ≥ 40`.
|
||||
- **N ≥ 4**: bottom-left at `(40, lifeline_bottom + 16)` — the top is packed tight against the leftmost header, so use the bottom margin.
|
||||
|
||||
### Worked example — OAuth 2.0 (N=4, M=10)
|
||||
|
||||
```
|
||||
lane centers 100, 260, 420, 580 (lane_w=140, header_w=120)
|
||||
header_bottom 88
|
||||
first_arrow_y 120
|
||||
last_arrow_y 120 + 44×9 = 516 (10 messages)
|
||||
lifeline_bottom 540
|
||||
note_bottom 540 + 16 + 48 = 604 (note at bottom)
|
||||
H 604 + 20 = 624
|
||||
viewBox "0 0 680 624"
|
||||
```
|
||||
|
||||
Label budgets: adjacent lanes (|Δx|=160) fit ~21 chars; "Redirect with client_id, scope" at 30 chars needs `|Δx| ≥ 218`, so it must span at least 2 lanes — put it between Client app (260) and Auth server (420), |Δx|=160 — **too tight**. Shorten to "Redirect (client_id + scope)" (28 chars, still over). Shorten further to "Redirect + client_id" (20 chars). Always verify every label against its span before finalizing.
|
||||
|
||||
## Centering inside the safe area
|
||||
|
||||
For N boxes of width `w` with gap `g`:
|
||||
|
||||
```
|
||||
total = N × w + (N - 1) × g
|
||||
offset = 40 + (600 - total) / 2
|
||||
box[i].x = offset + i × (w + g)
|
||||
```
|
||||
|
||||
Centered content feels deliberate. Left-aligned content feels incomplete.
|
||||
|
||||
## Sibling subsystem containers (2-up)
|
||||
|
||||
The structural subsystem-architecture pattern (see `structural.md` → "Subsystem architecture pattern") places **two sibling dashed-border containers** side by side, each holding a short internal flow. The 2-up layout sacrifices the default 40px horizontal safe margin in exchange for a usable container interior wide enough to hold 3–4 flowchart nodes.
|
||||
|
||||
Standard 2-up geometry at viewBox width 680:
|
||||
|
||||
```
|
||||
container_w = 315 # each sibling
|
||||
container_gap = 10 # space between siblings
|
||||
left_margin = 20 # leaner than the usual 40
|
||||
container_A.x = 20
|
||||
container_B.x = 345 # left_margin + container_w + container_gap
|
||||
rightmost edge = 345 + 315 = 660 # leaves 20px to x=680
|
||||
interior_w = 315 − 40 = 275 # after 20px padding on each side
|
||||
interior_A.x = 40
|
||||
interior_B.x = 365
|
||||
```
|
||||
|
||||
Inside each 275-wide interior, node widths follow the normal text-width formula but should cap at ~235 so the L-bend connectors have clear vertical channels on both sides. Typical nodes are 180 wide centered, or 150 wide for a 2-column row.
|
||||
|
||||
A **labeled cross-system arrow** bridges the 10px container gap:
|
||||
|
||||
```
|
||||
from B_right_node.right_edge → to A_left_node.left_edge
|
||||
arrow_x1 = interior_B.rightmost_node.x + 10
|
||||
arrow_x2 = interior_A.leftmost_node.x − 10
|
||||
arrow_y = matching row y # both nodes on the same tier
|
||||
label at (x_mid, arrow_y − 6), class="ts", text-anchor="middle"
|
||||
```
|
||||
|
||||
The cross-system arrow label is the one place where a flowchart-style connector *must* have a label — the reader cannot infer the relationship from the container titles alone. Keep the label to ≤3 words.
|
||||
|
||||
Dashed container borders use **inline `stroke-dasharray="4 4"`** on the `<rect>` rather than a CSS class — dashed is a one-off container-styling concern, not a reusable shape property, so the template's class list stays stable.
|
||||
|
||||
### Worked example — Pi session + Background analyzer
|
||||
|
||||
Two siblings, each with 3 internal nodes stacked vertically:
|
||||
|
||||
```
|
||||
container A "Pi session" container B "Background analyzer"
|
||||
x=20, y=40, w=315, h=260 x=345, y=40, w=315, h=260
|
||||
rx=20, stroke-dasharray="4 4" same styling
|
||||
|
||||
Inside A (x_center = 177): Inside B (x_center = 502):
|
||||
[ User input ] y=96 [ Analyze patterns ] y=96
|
||||
[ Pi responds ] y=170 [ Write summary ] y=170
|
||||
[ (session ends) ] y=244 [ Update memory ] y=244
|
||||
|
||||
Cross-system arrow: B top row → A top row at y=118
|
||||
x1 = 347 (container B left edge + 2)? No — from interior node B₁ left edge
|
||||
Actually: from B₁.x − 10 (headed right-to-left pointing at A₁)
|
||||
|
||||
Details below in structural.md's worked example.
|
||||
```
|
||||
|
||||
viewBox H for 3 stacked rows: last node bottom y = 244 + 44 = 288; container bottom 40 + 260 = 300; H = 300 + 20 = **320**.
|
||||
|
||||
## Sub-pattern coordinate tables
|
||||
|
||||
Fixed coordinate tables for the sub-patterns introduced in `structural.md`, `illustrative.md`, and `sequence.md`. Use these values verbatim — they're tuned so each sub-pattern respects the 680 viewBox and the 40 safe margin.
|
||||
|
||||
### Bus topology geometry
|
||||
|
||||
For `structural.md` → "Bus topology sub-pattern". A central horizontal bar bridges N agents above it and N agents below it.
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------------|------------------------------------------|
|
||||
| Bus bar | `x=40 y=280 w=600 h=40 rx=20` |
|
||||
| Bus label baseline | `y=304`, `text-anchor="middle"` at x=340 |
|
||||
| Top agent row (box top) | `y=80`, `h=60` |
|
||||
| Bottom agent row (box top) | `y=400`, `h=60` |
|
||||
| Default viewBox H | 500 |
|
||||
|
||||
Agent centers by row count:
|
||||
|
||||
| Agents per row | Box width | Centers (x) |
|
||||
|----------------|-----------|--------------------------------------------|
|
||||
| 2 | 180 | 180, 500 |
|
||||
| 3 | 140 | 170, 340, 510 |
|
||||
| 4 | 110 | 120, 260, 420, 560 |
|
||||
|
||||
Publish/subscribe arrow pair: two vertical lines per agent, offset by 8px horizontally (one at `agent_cx − 8`, one at `agent_cx + 8`). Publish goes down from agent to bar; Subscribe goes up from bar to agent. Labels sit at the channel midpoint y, `text-anchor="end"` (Publish on left) and `text-anchor="start"` (Subscribe on right).
|
||||
|
||||
### Radial star geometry (3 / 4 / 5 / 6 satellites)
|
||||
|
||||
For `structural.md` → "Radial star topology sub-pattern". A central hub surrounded by N peripheral satellites, each bidirectionally connected.
|
||||
|
||||
**Hub (always fixed):**
|
||||
|
||||
| Element | Coordinates |
|
||||
|---------------|----------------------------|
|
||||
| Hub rect | `x=260 y=280 w=160 h=80 rx=10` |
|
||||
| Hub center | `(340, 320)` |
|
||||
| viewBox H | 560 |
|
||||
|
||||
**Satellite positions:**
|
||||
|
||||
| N | Satellites (box top-left → w=160 h=60) |
|
||||
|---|-----------------------------------------------------------------------------------|
|
||||
| 3 | `(60, 120)`, `(460, 120)`, `(260, 460)` |
|
||||
| 4 | `(60, 120)`, `(460, 120)`, `(60, 460)`, `(460, 460)` |
|
||||
| 5 | `(260, 60)`, `(60, 200)`, `(460, 200)`, `(60, 460)`, `(460, 460)` |
|
||||
| 6 | `(20, 120)`, `(260, 60)`, `(500, 120)`, `(20, 460)`, `(260, 520)`, `(500, 460)` |
|
||||
|
||||
Satellite centers: add `(80, 30)` to the box top-left to get `(cx, cy)`.
|
||||
|
||||
**Arrow pairs:** each satellite connects to the hub with two single-headed arrows offset by 8 px perpendicular to the arrow direction. For a satellite at `(sx, sy)` and hub center `(340, 320)`, compute the direction vector, normalize to unit length, then offset each line by `(+4dy, −4dx)` and `(−4dy, +4dx)` respectively (where `(dx, dy)` is the unit direction).
|
||||
|
||||
For straightforward placement, use these pre-computed endpoint offsets for N=4:
|
||||
|
||||
| Satellite center | Outbound start (satellite → hub) | Outbound end |
|
||||
|------------------|----------------------------------|--------------|
|
||||
| TL `(140, 150)` | `(224, 176)` | `(264, 276)` |
|
||||
| TR `(540, 150)` | `(456, 176)` | `(416, 276)` |
|
||||
| BL `(140, 490)` | `(224, 464)` | `(264, 364)` |
|
||||
| BR `(540, 490)` | `(456, 464)` | `(416, 364)` |
|
||||
|
||||
Inbound (hub → satellite) is the reverse of each outbound, offset perpendicular by 8.
|
||||
|
||||
### Spectrum geometry
|
||||
|
||||
For `illustrative.md` → "Spectrum / continuum". A 1-D axis with end labels, tick points, option boxes, and italic captions.
|
||||
|
||||
**Fixed vertical positions:**
|
||||
|
||||
| Element | Coordinates |
|
||||
|---------------------------|--------------------------------------------|
|
||||
| Eyebrow (optional) | `y=50`, `text-anchor="middle"` at x=340 |
|
||||
| End labels (L / R) | `y=120`, at `x=80` (start) / `x=600` (end) |
|
||||
| Axis line | `x1=80 y1=140 x2=600 y2=140` |
|
||||
| Tick circles | `cy=140`, `r=6` |
|
||||
| Option box top | `y=200`, `h=60` |
|
||||
| Caption line 1 | `y=292`, italic `ts` |
|
||||
| Caption line 2 | `y=308`, italic `ts` |
|
||||
| Default viewBox H | 332 |
|
||||
|
||||
**Tick and option-box x positions by tick count:**
|
||||
|
||||
| Ticks | Tick centers (x) | Option box x (w=120) |
|
||||
|-------|-----------------------------|-----------------------------|
|
||||
| 2 | 200, 480 | 140, 420 |
|
||||
| 3 | 160, 340, 520 | 100, 280, 460 |
|
||||
| 4 | 140, 280, 420, 560 | 80, 220, 360, 500 |
|
||||
| 5 | 120, 240, 360, 480, 600 (w=100) | 70, 190, 310, 430, 550 |
|
||||
|
||||
Axis uses `marker-start` **and** `marker-end` (both ends arrowed to signal no natural direction). Axis stroke class is `arr` (neutral gray) — never a color ramp.
|
||||
|
||||
Sweet-spot tick and its option box use `c-green` or `c-amber`; all other ticks and boxes stay `c-gray`. Captions cap at 24 characters per line, 2 lines per option.
|
||||
|
||||
### Parallel rounds geometry
|
||||
|
||||
For `sequence.md` → "Parallel independent rounds". Stacked independent call/response rounds without shared lifelines.
|
||||
|
||||
**Stacked rounds variant (full-width):**
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|----------------------------------------|
|
||||
| Source actor | `x=60 y=120 w=120 h=60` |
|
||||
| Round k action box | `x=340 y=100 + (k-1)×80 w=160 h=48` |
|
||||
| Round k call arrow | `y = 124 + (k-1)×80` |
|
||||
| Round k response | `y = 134 + (k-1)×80` |
|
||||
| Call label | `y = 115 + (k-1)×80` |
|
||||
| Response label | `y = 145 + (k-1)×80` |
|
||||
| Row pitch | 80 |
|
||||
|
||||
Arrow x endpoints: `x1 = source_right_edge + 6 = 186`, `x2 = action_left_edge − 6 = 334` (going right); reverse for the return arrow.
|
||||
|
||||
**Script-wrapper variant:**
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|----------------------------------------|
|
||||
| Source actor | `x=60 y=120 w=120 h=60` |
|
||||
| Script wrapper | `x=240 y=100 w=140 h=260 rx=6` |
|
||||
| `{ }` label | `(310, 124)`, class `th` |
|
||||
| Divider line | `x1=256 y1=140 x2=364 y2=140` |
|
||||
| Wrapped band k | `x=252 y=152 + (k-1)×60 w=116 h=48` |
|
||||
| External action box | `x=440 y=200 w=160 h=48` (optional) |
|
||||
| Default viewBox H | 380 |
|
||||
|
||||
Up to 3 wrapped bands inside the script wrapper (with `script_h = 260`). Grow `script_h` by 60 for each additional band.
|
||||
|
||||
**Inside a 315-wide subsystem container** (half-width), scale the coordinates:
|
||||
|
||||
| Element | Container A (x=20) |
|
||||
|--------------------|---------------------------------|
|
||||
| Source actor | `x=40 y=120 w=100 h=50` |
|
||||
| Round k action | `x=200 y=104 + (k-1)×64 w=120 h=40` |
|
||||
| Row pitch | 64 |
|
||||
|
||||
### Multi-line box body line heights
|
||||
|
||||
For `structural.md` → "Multi-line box body". A three-line box used for advisor-role labels: title + italic role + meta.
|
||||
|
||||
| Line | y-offset from `rect_y` | Class |
|
||||
|------------|--------------------------|---------------|
|
||||
| Title | `+22` | `th` |
|
||||
| Role | `+42` | `ts` + italic |
|
||||
| Meta | `+62` | `ts` muted |
|
||||
| Min h | 80 | |
|
||||
|
||||
Row pitch is 20. For a box at y=140, lines sit at `162, 182, 202`. Box height never drops below 80 — if the role or meta line is absent, use a standard 56-tall two-line box instead, don't shrink a three-line box.
|
||||
|
||||
### Annotation circle on connector geometry
|
||||
|
||||
For `illustrative.md` → "Annotation circle on connector". A labeled circle sitting above a pair of bidirectional arrows between two subjects.
|
||||
|
||||
| Element | Coordinates |
|
||||
|-----------------------|----------------------------------------------|
|
||||
| Subject A box | `x=80 y=160 w=180 h=56` |
|
||||
| Subject B box | `x=440 y=160 w=180 h=56` |
|
||||
| A → B arrow | `(266, 184) → (434, 184)`, `arr` |
|
||||
| B → A arrow | `(434, 204) → (266, 204)`, `arr` |
|
||||
| Arrow pair y midpoint | 194 |
|
||||
| Annotation circle | outer `c="box"` r=30 at `(cx, cy) = (350, 150)` |
|
||||
| Inner pill | `x=cx−28 y=cy+12 w=56 h=20 rx=10` (uses ramp)|
|
||||
| Connector line | `(cx, cy+30) → (cx, 194)`, `arr` (no marker) |
|
||||
|
||||
Label goes inside the pill at `(cx, cy+26)`, `th`, centered. Subject A/B labels use `th` at `(subject_cx, subject_cy)`.
|
||||
|
||||
### Container loop geometry (simple flowchart)
|
||||
|
||||
For `flowchart.md` → "Loop container". An outer rounded rect framing a simple flowchart.
|
||||
|
||||
| Element | Coordinates |
|
||||
|--------------------|--------------------------------------------|
|
||||
| Container rect | `x=20 y=40 w=640 h=H−60 rx=20`, class `box`|
|
||||
| Title | `(340, 72)`, class `th` |
|
||||
| Subtitle | `(340, 92)`, class `ts` |
|
||||
| First inner box y | ≥ 116 |
|
||||
| Inner safe area | `x ∈ [40, 620]` |
|
||||
| Bottom padding | 20px |
|
||||
|
||||
After placing the inner flow, compute `inner_bottom = max y of any inner element`, then `H = inner_bottom + 40` and `container_h = H − 60`.
|
||||
|
||||
## Arrow routing
|
||||
|
||||
Every arrow has a start point (source box edge) and an end point (target box edge), with an optional bend in between.
|
||||
|
||||
**Direct arrow** — when source and target are aligned on an axis (same x for vertical, same y for horizontal):
|
||||
|
||||
```svg
|
||||
<line x1="200" y1="76" x2="200" y2="120" class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Leave **10px** between the arrow's endpoint and the target box's top edge so the arrowhead doesn't touch the border.
|
||||
|
||||
**L-bend arrow** — when the direct line would cross another box:
|
||||
|
||||
```svg
|
||||
<path d="M x1 y1 L x1 ymid L x2 ymid L x2 y2" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
- `ymid` is a horizontal "channel" chosen to thread cleanly between the two levels
|
||||
- Always include `fill="none"` on `<path>` used as a connector — SVG defaults to black fill
|
||||
|
||||
**Collision check.** Before finalizing every arrow, trace its path against every rect you've already placed:
|
||||
|
||||
```
|
||||
for each rect R in the diagram:
|
||||
if rect R is not the source or target of this arrow:
|
||||
does the arrow's line segment intersect the interior of R?
|
||||
if yes: use an L-bend to route around R
|
||||
```
|
||||
|
||||
This is the #1 diagram failure mode. Arrows slashing through unrelated boxes.
|
||||
|
||||
**Connection-point convention.** Every arrow anchors to the midpoint of a box edge (top, bottom, left, or right), never to a corner. With `rx="6"` or `rx="12"` rounded corners, the actual corner curve occupies the first ~6–12px of each edge — so stay **≥20px from any corner** when picking a connection point. An arrow that enters a box 4px from its corner reads as "touching the rounded curve" instead of "attached to the edge", and the arrowhead looks smeared. For a 180-wide rect, valid top-edge connection points sit at `x ∈ [rect_x + 20, rect_x + 160]`; the outer 20px on each side belong to the corner radius.
|
||||
|
||||
**Multi-arrow stagger.** When two or more arrows terminate on the same edge of the same box from different sources (three fan-in arrows hitting a single aggregator; two feedback lines returning to a start node), stagger their arrival points by **≥12px** along the target edge. Example: three arrows fanning into the top edge of a 180-wide box sit at `target_x − 24`, `target_x`, `target_x + 24`. Same rule applies to horizontal arrows converging on a left/right edge (stagger the y by ≥12px). Without the stagger, the arrowheads stack on top of each other and the reader can't tell how many incoming edges there are — a 3-to-1 fan-in reads as a single thick arrow.
|
||||
|
||||
## text-anchor safety
|
||||
|
||||
`text-anchor` controls which point of the text aligns to the `x` coordinate:
|
||||
|
||||
- `"start"` — x is the left edge. Text extends right.
|
||||
- `"middle"` — x is the horizontal center. Text extends both ways.
|
||||
- `"end"` — x is the right edge. Text extends left.
|
||||
|
||||
**Danger**: `text-anchor="end"` at low x values. If `x=50` and the label is 200px wide, the text starts at `x = -150` — outside the viewBox. Check: `label_chars × 8 < x` must hold for `text-anchor="end"` labels.
|
||||
|
||||
**Default to `text-anchor="start"` on the left side and `"middle"` in the center.** Use `"end"` only when you've verified the x coordinate is high enough.
|
||||
|
||||
## Vertical text centering inside a rect
|
||||
|
||||
Every `<text>` inside a rect needs `dominant-baseline="central"`. Without it, SVG treats `y` as the baseline — the glyph body sits ~4px above where you think and descenders hit the next line.
|
||||
|
||||
```svg
|
||||
<rect x="100" y="20" width="180" height="44" rx="6" class="c-blue"/>
|
||||
<text class="th" x="190" y="42" text-anchor="middle" dominant-baseline="central">Login</text>
|
||||
```
|
||||
|
||||
For a rect at `(x, y, w, h)`:
|
||||
- Single line centered: `text_x = x + w/2`, `text_y = y + h/2`, `dominant-baseline="central"`
|
||||
- Two lines (44 + 14 layout inside a 56-tall box):
|
||||
- Title: `text_y = y + 20` (center of top 40px)
|
||||
- Subtitle: `text_y = y + 40` (center of bottom 16px)
|
||||
- Both use `dominant-baseline="central"`
|
||||
|
||||
## Quick-reference sizes
|
||||
|
||||
Standard dimensions that work well and keep the diagram rhythm consistent:
|
||||
|
||||
| Element | Size |
|
||||
|-----------------------------|----------------------|
|
||||
| Single-line node height | 44 px |
|
||||
| Two-line node height | 56 px |
|
||||
| Default node width | 180 px (adjust up) |
|
||||
| Minimum gap between nodes | 20 px horizontal, 60 px vertical |
|
||||
| Container padding | 20 px |
|
||||
| Rect corner radius | `rx="6"` (subtle), `rx="12"` (container), `rx="20"` (outer container) |
|
||||
| Connector stroke width | 1.5 px (set by `.arr`) |
|
||||
| Diagram border stroke | 0.5 px |
|
||||
| Viewport safe margin | 40 px on each edge |
|
||||
|
||||
Stick to these unless you have a concrete reason to deviate.
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# AI pattern catalog
|
||||
|
||||
Pre-planned diagram starters for recurring multi-agent coordination shapes — research orchestrators, message buses, shared-state stores, agent-with-skills composition. Each file here is a thin wrapper around one specific pattern: a one-line description, the diagram type baoyu prefers for it, the ramp palette that works, a reference mermaid block (industry-standard shorthand you can sanity-check against), and a pre-cooked baoyu SVG plan that saves you a planning pass.
|
||||
|
||||
## How to use this directory
|
||||
|
||||
1. **Check the index below** for a pattern name matching the user's topic. Exact matches are rare — usually the user says *"agents coordinating through a shared channel"* and you recognize *message-bus*, or *"agents building on each other's findings in a store"* and you recognize *shared-state*.
|
||||
2. **If a pattern matches**, open its file and read end-to-end. The mermaid block tells you *what* to draw (structurally), the baoyu SVG plan tells you *how* (coordinates, widths, arrow routing).
|
||||
3. **If no pattern matches**, fall back to the normal Step 4 planning flow in `SKILL.md`. Do not force a near-miss — two coordination patterns that share a surface name often have different topologies (message bus ≠ shared state, even though both put a central element between agents).
|
||||
|
||||
The mermaid reference is **authoritative for structure**, not for rendering. Never emit mermaid as the final output; always convert to a hand-written baoyu SVG using the plan in the same file.
|
||||
|
||||
## Scope
|
||||
|
||||
This directory covers **AI-system topologies**, not generic software patterns. For flowchart / sequence / structural / illustrative / class diagram techniques, stay in the top-level references files. If an AI-system pattern needs a technique that isn't documented in those files yet, add the technique upstream — not here.
|
||||
|
||||
## Index
|
||||
|
||||
| Pattern | Default type | One-line hook |
|
||||
|--------------------------|-----------------|-----------------------------------------------------------------------------------------------------|
|
||||
| [multi-agent-research](multi-agent-research.md) | flowchart | Lead agent + memory sidecar + parallel search subagents (each looping) + citation stage (Anthropic) |
|
||||
| [message-bus](message-bus.md) | structural | N agents coordinate via a central publish/subscribe bar — no direct agent-to-agent edges |
|
||||
| [shared-state](shared-state.md) | structural | N peer agents read/write a central store — no orchestrator, findings immediately visible to all |
|
||||
| [agent-skills](agent-skills.md) | structural | Agent loop + runtime + MCP servers (left) + skills library on filesystem (right) — composition view |
|
||||
| [contextual-retrieval](contextual-retrieval.md) | flowchart | Contextualizer LLM prepends 50–100 tokens to each chunk → dual-track (embedding + BM25) + rank fusion |
|
||||
|
||||
## Adding a new pattern
|
||||
|
||||
Keep each file under ~80 lines. A pattern file has six sections in this order:
|
||||
|
||||
1. **Name + 1-line description**
|
||||
2. **Default diagram type** — plus when to pick an alternate type
|
||||
3. **Palette** — which ramps, tied to which roles
|
||||
4. **Sub-pattern** — the specific section in a top-level reference file that does the heavy lifting
|
||||
5. **Mermaid reference** — the canonical industry-standard sketch, in a ` ```mermaid` block
|
||||
6. **Baoyu SVG plan** — node list with widths, arrow list, viewBox dimensions, any gotchas
|
||||
|
||||
When you add a new pattern, update this README's index table in the same commit. Do not create orphan files — if you can't write a one-line hook for the index, the pattern isn't well-defined yet.
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
# Agent with skills
|
||||
|
||||
**One-line description.** The architectural snapshot of a general-purpose agent composed of four layers — **agent loop** (reasoning), **agent runtime** (code + filesystem), **MCP servers** (outbound connectors to external tools and data), and a **skills library** (local, progressively-disclosed domain expertise on the filesystem). This is the "Skills: the complete picture" diagram from Anthropic's skills blog post — the shape that explains *why you stop building specialized agents and start shipping skills to one general agent*. It is a **system composition** diagram, not a workflow: it answers "what the agent is", not "what it does next".
|
||||
|
||||
## Default diagram type
|
||||
|
||||
**Structural — central hub with bilateral satellite groups.** Single agent box in the middle with a visible interior loop glyph; MCP servers stacked vertically on the **left**; skills stacked vertically inside a **filesystem container** on the **right**. The left/right split is the point: *left is network-reachable tools, right is on-disk expertise*.
|
||||
|
||||
Alternate types:
|
||||
- **Before/after poster flowchart** (two stacked frames, per `flowchart.md` → "Poster flowchart") when illustrating the evolution from specialized-per-domain agents → general agent + skills. This is Anthropic's Figure 1 / Figure 2 shape.
|
||||
- **Flowchart with a loop container** when the user wants to show activation order (loop → read skill → call MCP → write file → loop). The structural snapshot loses sequencing; the flowchart gains it.
|
||||
|
||||
## Palette
|
||||
|
||||
Three role ramps + gray. This is a structural diagram where the two satellite groups must be visually distinct:
|
||||
|
||||
- **`c-gray`** — filesystem container, title bar, legend, arrow labels.
|
||||
- **`c-teal`** — agent (central hub). Teal anchors the LLM/reasoning role.
|
||||
- **`c-purple`** — MCP servers. Shared across all three (homogeneous satellite group — instances of one role do not get distinct colors).
|
||||
- **`c-coral`** — skills. Shared across all three; coral reads as Anthropic's own skills brand-orange and sits opposite the MCP group.
|
||||
|
||||
This is a **category coloring**, not a rainbow: one ramp per satellite *group*, not per satellite. The structural radial-star rule in `structural.md` ("satellites stay neutral gray") is deliberately overridden here because we have **two distinct kinds of satellite** that the reader must tell apart at a glance.
|
||||
|
||||
Legend is required.
|
||||
|
||||
## Sub-pattern
|
||||
|
||||
`structural.md` → **Radial star topology** as the base (hub with satellites, bidirectional arrows), extended to an **asymmetric two-group** variant: left group is a loose stack (no container), right group is wrapped in a `structural.md` → **Container box** to show the filesystem boundary. The agent's interior loop uses `glyphs.md` → **terminal-icon** plus a hand-drawn circular arrow pair around it (the "reason · act" cycle) — this is what distinguishes the agent visually from its satellites.
|
||||
|
||||
## Mermaid reference
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph MCP
|
||||
M1[MCP server 1]
|
||||
M2[MCP server 2]
|
||||
M3[MCP server 3]
|
||||
end
|
||||
A[Agent<br/>reason · runtime]
|
||||
subgraph Filesystem
|
||||
S1[Skill A]
|
||||
S2[Skill B]
|
||||
S3[Skill C]
|
||||
end
|
||||
M1 <--> A
|
||||
M2 <--> A
|
||||
M3 <--> A
|
||||
A <--> S1
|
||||
A <--> S2
|
||||
A <--> S3
|
||||
A -. reason · act loop .-> A
|
||||
```
|
||||
|
||||
Two subgraphs flanking a central hub plus a self-loop on the hub is the defining shape. Drop either subgraph and the pattern collapses into something else — a plain tool-calling loop (if you drop skills) or a read-only retrieval shape (if you drop MCP).
|
||||
|
||||
## Baoyu SVG plan
|
||||
|
||||
Central agent with an interior loop glyph; three MCP servers stacked on the left; three skills stacked inside a filesystem container on the right.
|
||||
|
||||
- **viewBox**: `0 0 820 460`
|
||||
- **Agent (hub)** — `c-teal`, `x=310 y=110 w=200 h=240`, large central box:
|
||||
- Title *Agent* at `(410, 140)` class `th`.
|
||||
- Interior loop glyph centered at `(410, 230)`: a `terminal-icon` at `(398, 218)` (24×24 from `glyphs.md`), with two curved arrows forming a circle around it — `path d="M 370 230 A 40 40 0 1 1 450 230" class="arr" marker-end="url(#arrow)"` plus the mirror arc below. This is the reason-act loop made visible.
|
||||
- Subtitle *reason · runtime* at `(410, 310)` class `ts`.
|
||||
- **MCP server stack** (3 boxes, `c-purple`, single-line title, same size):
|
||||
- *MCP server 1*, `x=60 y=140 w=170 h=52`.
|
||||
- *MCP server 2*, `x=60 y=210 w=170 h=52`.
|
||||
- *MCP server 3*, `x=60 y=280 w=170 h=52`.
|
||||
- **Filesystem container** — `rect x=570 y=100 w=220 h=260 rx=16` class `box`, title *Filesystem* at `(680, 128)` class `th`, centered.
|
||||
- **Skills stack** inside the filesystem (3 boxes, `c-coral`, single-line title, same size):
|
||||
- *Skill A*, `x=600 y=150 w=160 h=52`.
|
||||
- *Skill B*, `x=600 y=216 w=160 h=52`.
|
||||
- *Skill C*, `x=600 y=282 w=160 h=52`.
|
||||
|
||||
**Arrow plan.** Six bidirectional pairs, three per side. Each pair uses two single-headed arrows offset 8px perpendicular to direction (same rule as `structural.md` → "Radial star → Arrow pairs"):
|
||||
|
||||
- MCP server *n* ↔ Agent: short horizontal channel from `(230, y_center)` to `(310, y_center)` and back, where `y_center ∈ {166, 236, 306}`. Both solid `arr`.
|
||||
- Agent ↔ Skill *n*: short horizontal channel from `(510, y_center)` to `(600, y_center)` and back, where `y_center ∈ {176, 242, 308}`. Arrows cross the filesystem container edge — that crossing is semantically important and must not be hidden.
|
||||
|
||||
No external self-loop arc on the agent — the interior loop glyph (`terminal-icon` + circular arrows) already carries that meaning, and an exterior arc would collide with the MCP/skills channels.
|
||||
|
||||
**Legend** (bottom, required):
|
||||
|
||||
```
|
||||
[■] Agent [■] MCP server [■] Skill [▭] Filesystem [↔] bidirectional channel
|
||||
```
|
||||
|
||||
**Gotchas.**
|
||||
- Never color the 3 MCP servers differently from each other, nor the 3 skills. Each group is a homogeneous tier; per-item coloring turns an architecture diagram into a role diagram.
|
||||
- Keep the filesystem container around the skills. Dropping it makes skills look like peers of MCP servers and erases the "local, progressively disclosed from disk" property that is the whole reason skills are not MCP servers.
|
||||
- Convention: **MCP on the left, skills on the right**, matching Anthropic's own materials. Flipping the sides loses a free point of recognition.
|
||||
- If the user wants to show **progressive disclosure** (metadata → SKILL.md → references/), add a nested three-tier rect *inside each skill box* — that is an extension of this pattern, not an alternate type.
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# Contextual retrieval
|
||||
|
||||
**One-line description.** Anthropic's preprocessing recipe for RAG: before chunks are embedded and indexed, an LLM (the *contextualizer*) reads the whole document together with each chunk and emits 50–100 tokens of situating context that gets prepended to the chunk. The contextualized chunk then feeds **both** a semantic index (embedding model → vector DB) and a lexical index (TF-IDF → BM25). At runtime the query hits both indices, results merge through rank fusion, and the top-K chunks go to the generative model. The diagram's job is to show the contextualizer as the distinctive new step and the dual-track preprocessing it feeds.
|
||||
|
||||
## Default diagram type
|
||||
|
||||
**Flowchart (poster style) with two stacked phases.** The pattern has a name, a clear preprocessing / runtime split, a fan-out into parallel tracks, and a distinctive new step — that's poster flowchart territory. A flat linear chart would smear the two phases and hide what's new. Stack the phases vertically with eyebrow dividers; each phase reads left-to-right.
|
||||
|
||||
Alternate types:
|
||||
- **Structural — subsystem containers side by side** when contrasting contextual retrieval against plain RAG (two siblings, each a mini pipeline). See `structural.md` → "Rich interior for subsystem containers".
|
||||
- **Preprocessing-only flowchart** when the runtime story isn't needed — drop phase 2 and end at the two indices.
|
||||
|
||||
## Palette
|
||||
|
||||
Three accent ramps plus gray, under the poster-flowchart 4-ramp exception (ramps encode role *categories*, not sequence):
|
||||
|
||||
- **`c-gray`** — corpus, query, rank fusion, top-K chunks, response. Neutral data / IO.
|
||||
- **`c-purple`** — Claude in both its roles: contextualizer and generative model. One ramp for both anchors the "same Claude, two prompts" story without adding a fourth color.
|
||||
- **`c-teal`** — semantic track (embedding model + vector DB).
|
||||
- **`c-amber`** — lexical track (TF-IDF + BM25 index).
|
||||
|
||||
Do **not** color the contextualizer and generative model differently — doing so implies different models or different roles, but the whole point is the same Claude doing both jobs.
|
||||
|
||||
## Sub-pattern
|
||||
|
||||
`flowchart.md` → **Poster flowchart pattern** (eyebrow-divided phases, ≤4 ramps for role categories) + **Fan-out + aggregator (simple mode)** applied twice: the contextualizer splits into two tracks that never reconverge in phase 1, and query + both indices converge at rank fusion in phase 2.
|
||||
|
||||
## Mermaid reference
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Preprocessing
|
||||
C[Corpus] -- chunks --> CTX[Contextualizer · Claude]
|
||||
CTX -- context + chunk --> EM[Embedding model]
|
||||
CTX -- context + chunk --> TF[TF-IDF]
|
||||
EM --> VDB[(Vector DB)]
|
||||
TF --> BM[(BM25 index)]
|
||||
end
|
||||
subgraph Runtime
|
||||
Q[User query] --> RF[Rank fusion]
|
||||
VDB --> RF
|
||||
BM --> RF
|
||||
RF --> TK[Top-K chunks] --> GM[Generative model · Claude] --> R[Response]
|
||||
end
|
||||
```
|
||||
|
||||
Defining edges: `CTX --> EM` *and* `CTX --> TF` (the contextualized chunk goes to both tracks) plus `VDB --> RF` *and* `BM --> RF` (both indices feed fusion). Drop either pair and the diagram collapses into plain RAG or embedding-only retrieval.
|
||||
|
||||
## Baoyu SVG plan
|
||||
|
||||
Two stacked phases with eyebrow labels and a thin horizontal divider between them.
|
||||
|
||||
- **viewBox**: `0 0 680 540`
|
||||
- **Phase 1 eyebrow** — *Preprocessing · Runs once per corpus update* at `(40, 50)`, class `eyebrow`.
|
||||
|
||||
Phase 1 interior:
|
||||
- **Corpus** — `c-gray`, `x=40 y=80 w=100 h=56`, two-line (*Corpus*, *Documents*).
|
||||
- **Contextualizer** — `c-purple`, `x=180 y=72 w=260 h=72`, multi-line (*Contextualizer*, *Claude*, *50–100 tokens per chunk*). Visibly the largest box — it's the pattern's signature step.
|
||||
- **Embedding model** — `c-teal`, `x=140 y=180 w=160 h=48`, single-line.
|
||||
- **TF-IDF** — `c-amber`, `x=380 y=180 w=160 h=48`, single-line.
|
||||
- **Vector DB** — `c-teal`, `x=140 y=260 w=160 h=56`, two-line (*Vector DB*, *Semantic index*).
|
||||
- **BM25 index** — `c-amber`, `x=380 y=260 w=160 h=56`, two-line (*BM25 index*, *Lexical index*).
|
||||
|
||||
**Phase 1 arrows:**
|
||||
- *Corpus → Contextualizer*: `(140, 108) → (180, 108)`, label *chunks* at `(160, 102)`.
|
||||
- *Contextualizer → Embedding model*: L-bend `(260, 144) → (260, 160) → (220, 160) → (220, 180)`, label *context + chunk* at `(170, 164)` `text-anchor="end"`.
|
||||
- *Contextualizer → TF-IDF*: L-bend `(360, 144) → (360, 160) → (460, 160) → (460, 180)`, label *context + chunk* at `(470, 164)` `text-anchor="start"`. (Both arrows labeled — the reader must see that *both* tracks receive the contextualized chunk.)
|
||||
- *Embedding model → Vector DB*: straight vertical `(220, 228) → (220, 260)`.
|
||||
- *TF-IDF → BM25 index*: straight vertical `(460, 228) → (460, 260)`.
|
||||
|
||||
- **Phase divider** — dashed line `x1=40 y1=340 x2=640 y2=340`, class `arr-alt`.
|
||||
- **Phase 2 eyebrow** — *Runtime · Per user query* at `(40, 362)`, class `eyebrow`.
|
||||
|
||||
Phase 2 interior (single horizontal row at y=400–456):
|
||||
- *User query* `c-gray` `x=40 w=100`, *Rank fusion* `c-gray` `x=160 w=100`, *Top-K chunks* `c-gray` `x=280 w=100` (two-line with subtitle *Top 20*), *Generative model* `c-purple` `x=400 w=140` (two-line with subtitle *Claude*), *Response* `c-gray` `x=560 w=80`. All `y=400 h=56`.
|
||||
|
||||
**Phase 2 arrows** (straight horizontal, 20px gaps between boxes at y=428): query→fusion, fusion→top-K, top-K→generator, generator→response.
|
||||
|
||||
**Cross-phase arrows** (indices into rank fusion):
|
||||
- *Vector DB → Rank fusion*: vertical drop `(200, 316) → (200, 400)` — lands inside rank fusion's top edge (x=160–260).
|
||||
- *BM25 index → Rank fusion*: L-bend `(460, 316) → (460, 372) → (220, 372) → (220, 400)`. The 20px x-offset from the Vector DB arrow keeps the two inbound arrows from stacking.
|
||||
|
||||
Both cross-phase arrows are solid `.arr` — they're the main data flow, nothing alternate.
|
||||
|
||||
**Legend** (bottom, required — 3 accent ramps encode category):
|
||||
|
||||
```
|
||||
[■] Claude (contextualizer + generator) [■] Semantic track [■] Lexical track
|
||||
```
|
||||
|
||||
Place at `y=510`, centered at `x=340`.
|
||||
|
||||
**Gotchas.**
|
||||
- Both tracks must show they receive the *contextualized* chunk — label both outgoing arrows from the contextualizer. If only one is labeled, readers assume the other track still uses raw chunks.
|
||||
- Do not draw the contextualizer as a self-loop on the Corpus. It's a distinct LLM step that runs once per chunk with whole doc + chunk as input, conceptually closer to an orchestrator than an inline transform.
|
||||
- Keep rank fusion gray, not amber — it merges two tracks but it's a structural aggregator, not an accent role. Giving it amber visually absorbs it into the lexical track.
|
||||
|
||||
**Reranker variant.** The reranking extension inserts a **reranker** box between *Rank fusion* and *Top-K chunks*. Insert `Reranker` at `x=280 y=400 w=120 h=56` (shift Top-K, generator, response right by 140 and widen the viewBox to 820). Annotate the reranker's input arrow with *top 150* and its output with *top 20* — the winnowing ratio is the whole point.
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
# Message bus
|
||||
|
||||
**One-line description.** N agents coordinate through a shared publish/subscribe channel rather than talking to each other directly. Each agent subscribes to the topics it cares about and publishes events others may handle. Unlike orchestrator-subagent (central router) or shared-state (central store), the bus carries *events in flight* — agents react to what's happening, not to what's been accumulated. Canonical use cases: event-driven pipelines, security-ops triage → investigation → response, growing agent ecosystems where new capabilities plug in without rewiring.
|
||||
|
||||
## Default diagram type
|
||||
|
||||
**Structural — bus topology.** The defining visual is a central horizontal bar with agents fanning out above and below, each linked by a pair of offset arrows (publish down, subscribe up). A flowchart would force one specific event sequence, but the whole point of a bus is that the workflow emerges from events; a structural diagram shows the coordination shape without committing to a single path.
|
||||
|
||||
Alternate types:
|
||||
- **Sequence** when the user wants to show one specific event cascade (alert arrives → triage classifies → network agent investigates → response agent acts) with explicit ordering. Use 4–5 lifelines, not the bus geometry.
|
||||
- **Flowchart** only if the pipeline really is fixed, in which case it's not a message bus — it's just a linear workflow.
|
||||
|
||||
## Palette
|
||||
|
||||
- **`c-gray`** — the event source / external input (the thing that only publishes, never subscribes). Neutral because it's outside the coordinated agent set.
|
||||
- **`c-teal`** — the agent role for all subscribed agents. One shared ramp because every agent on the bus is a peer; coloring them differently implies a hierarchy that the pattern explicitly rejects.
|
||||
- **`c-amber`** — the bus bar itself. Amber is the convention for "the shared channel everyone looks at" per `structural.md` → "Bus topology sub-pattern".
|
||||
|
||||
Do **not** rainbow the agents by role (network / identity / response / enrichment → four different ramps). The reader should feel the agents are interchangeable peers that differ only in what topics they subscribe to, not in what kind of thing they are.
|
||||
|
||||
## Sub-pattern
|
||||
|
||||
`structural.md` → **Bus topology sub-pattern** + `glyphs.md` → **Publish/subscribe arrow pair**. This pattern is the flagship use case for both; the bus topology section is written with this diagram in mind and `layout-math.md` → "Bus topology geometry" has the fixed coordinates.
|
||||
|
||||
## Mermaid reference
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
S[Alert source] -- publish --> B[[Message bus]]
|
||||
B -- subscribe --> T[Triage agent]
|
||||
T -- publish --> B
|
||||
B -- subscribe --> E[Enrichment agent]
|
||||
E -- publish --> B
|
||||
B -- subscribe --> N[Network agent]
|
||||
B -- subscribe --> I[Identity agent]
|
||||
B -- subscribe --> R[Response agent]
|
||||
```
|
||||
|
||||
The `[[bus]]` notation stands in for a central bar — mermaid can't draw the real geometry. What matters structurally is that every agent talks only to the bus, never agent-to-agent, and that most agents both publish *and* subscribe (the source is the exception).
|
||||
|
||||
## Baoyu SVG plan
|
||||
|
||||
Bus bar centered horizontally; 3 agents on top, 3 agents on bottom. Uses the Anthropic security-ops example labels verbatim — swap them for the user's domain when adapting.
|
||||
|
||||
- **viewBox**: `0 0 680 500`
|
||||
- **Bus bar** — `c-amber`, `x=40 y=280 w=600 h=40 rx=20`. Label *Message bus (publish/subscribe)* at `(340, 304)`, class `th`, centered.
|
||||
- **Top row** (3 boxes, centers at `x = 170, 340, 510`, `w=140 h=60`, `y=80`, two-line):
|
||||
- *Alert source*, **`c-gray`**, subtitle *External events* — the only pure publisher. Mark it gray to distance it from the coordinated agent set.
|
||||
- *Triage agent*, `c-teal`, subtitle *Classifies severity*.
|
||||
- *Enrichment agent*, `c-teal`, subtitle *Gathers context*.
|
||||
- **Bottom row** (same centers, `y=400`, `h=60`):
|
||||
- *Network agent*, `c-teal`, subtitle *Investigates traffic*.
|
||||
- *Identity agent*, `c-teal`, subtitle *Checks credentials*.
|
||||
- *Response agent*, `c-teal`, subtitle *Triggers actions*.
|
||||
|
||||
**Publish/subscribe arrow pairs** (use the glyph template verbatim, 8px offset):
|
||||
|
||||
- For each agent centered at `agent_cx`, with top agents at `agent_y_bottom=140` and the bus top at `bar_y_top=280`, draw two vertical lines:
|
||||
- Publish: `(agent_cx − 8, 140) → (agent_cx − 8, 280)` with arrowhead.
|
||||
- Subscribe: `(agent_cx + 8, 280) → (agent_cx + 8, 140)` with arrowhead.
|
||||
- For bottom agents, `agent_y_top=400`, `bar_y_bottom=320`, mirror: Publish goes down from agent to bus, Subscribe goes up from bus to agent. (Yes — publish on a bottom agent still goes *out of* the agent toward the bus, which is visually upward.)
|
||||
- **Gotcha — Alert source exception.** Alert source is a pure publisher; draw only its Publish arrow (`(162, 140) → (162, 280)`) and omit the Subscribe return. Do *not* draw a subscribe arrow with no label, and do not put a "(source)" parenthetical in the subtitle — the gray ramp + missing return arrow is the signal.
|
||||
- **Labels.** Only label the Publish/Subscribe arrows for one representative agent (e.g., Triage), not all six. With six pairs on one diagram, labeling every pair becomes text soup — a single labeled example plus the legend below is enough.
|
||||
|
||||
**Legend.** Required because the two arrow directions encode distinct semantics and the color-off source agent needs a key:
|
||||
|
||||
```
|
||||
[↓] Publish [↑] Subscribe [■] Event source [■] Subscribed agent [■] Bus
|
||||
```
|
||||
|
||||
Place at `y=480`, `text-anchor="middle"` at `x=340`.
|
||||
|
||||
**When to drop to 2+2 or go up to 4+4.** The 3+3 layout is the sweet spot. With 2 agents per row use `w=180` centered at `x=180, 500`. With 4 per row use `w=110` centered at `x=120, 260, 420, 560` and drop the Publish/Subscribe *labels* entirely — four pairs plus labels per row is too dense. Beyond 8 total agents, the diagram is telling you the ecosystem has outgrown a single-canvas structural view; consider grouping agents by topic or splitting into two diagrams.
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
# Multi-agent research
|
||||
|
||||
**One-line description.** A lead agent plans a research task, persists its plan to a memory sidecar, spawns a row of parallel search subagents (each running its own search-and-think loop over a different aspect of the question), synthesizes their findings, then hands the synthesized draft to a dedicated citation subagent that attributes sources before the final report goes back to the user. This is Anthropic's Research-feature shape. The parts that distinguish it from a plain orchestrator fan-out are the **memory sidecar**, the **per-subagent iteration loop**, and the **post-processing citation stage**.
|
||||
|
||||
## Default diagram type
|
||||
|
||||
**Structural flowchart — central orchestrator with two flanking peers plus a subagent row.** The topology has one big star (the lead agent) with a memory store on one side and a citation subagent on the other, and a row of interchangeable search subagents underneath. A plain fan-out misses memory; a vertical queue-backed worker pool misses the pre/post peers. Draw the lead agent large and central, flank it with memory + citations, and put search subagents in a horizontal row below.
|
||||
|
||||
Alternate types:
|
||||
- **Sequence** when the user wants to show turn order — `LeadResearcher → Memory (save plan) → Subagent1/2 (parallel dispatch) → LeadResearcher (synthesize) → CitationAgent → User`. This is the shape of the process diagram in Anthropic's own post.
|
||||
|
||||
## Palette
|
||||
|
||||
Four role ramps (at the `design-system.md` limit for multi-agent diagrams):
|
||||
|
||||
- **`c-gray`** — user box, final report, structural labels.
|
||||
- **`c-teal`** — lead agent / orchestrator. The primary role anchors the strongest color.
|
||||
- **`c-purple`** — search subagents, **all the same color**. Instances of one role share a ramp; they are interchangeable workers, not distinct actors.
|
||||
- **`c-coral`** — citation subagent. Distinct from the search pool because it is a different specialist running at a different stage.
|
||||
- **`c-amber`** — memory store. Amber is the standing convention for a retriever/store sidecar, which also prevents readers from mistaking memory for a third agent role.
|
||||
|
||||
Legend is required — three distinct non-gray role ramps plus the store color.
|
||||
|
||||
## Sub-pattern
|
||||
|
||||
`flowchart.md` → **Fan-out + aggregator (simple mode)** for the lead ↔ subagents channel, plus `flowchart.md` → **Self-loops** (borrowed from the state-machine section) on each search subagent to show its internal search/think iteration. The flanking peers (memory, citation) are **bidirectional satellites** off the lead agent — not part of the fan-out.
|
||||
|
||||
## Mermaid reference
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
U[User query] --> L[Lead agent<br/>orchestrator]
|
||||
L <--> M[(Memory<br/>plan + context)]
|
||||
L <--> C[Citation subagent]
|
||||
L --> S1[Search subagent A]
|
||||
L --> S2[Search subagent B]
|
||||
L --> S3[Search subagent C]
|
||||
S1 --> L
|
||||
S2 --> L
|
||||
S3 --> L
|
||||
S1 -. search + think .-> S1
|
||||
S2 -. search + think .-> S2
|
||||
S3 -. search + think .-> S3
|
||||
L --> R[Final report]
|
||||
R --> U
|
||||
```
|
||||
|
||||
The distinctive edges vs. a plain orchestrator fan-out: the bidirectional `L <--> M` sidecar, the `L <--> C` post-processor, and the three self-loops on the search subagents.
|
||||
|
||||
## Baoyu SVG plan
|
||||
|
||||
Central lead agent with two flanking peers on the top row and three parallel search subagents in a row below. User box on the far left.
|
||||
|
||||
- **viewBox**: `0 0 820 460`
|
||||
- **User** — `c-gray`, `x=30 y=150 w=120 h=64`, two-line (*User*, *Submits query*).
|
||||
- **Lead agent** — `c-teal`, `x=310 y=90 w=260 h=180`, multi-line block:
|
||||
- Title *Lead agent* at `(440, 120)` class `th`.
|
||||
- Subtitle *(orchestrator)* at `(440, 140)` class `ts`.
|
||||
- Tool stack at y=178, 198, 218, 238, class `ts`, centered: *Tools:*, *search · MCP · memory*, *run_subagent*, *complete_task*.
|
||||
- **Citations subagent** — `c-coral`, `x=170 y=156 w=120 h=60`, two-line (*Citations*, *subagent*).
|
||||
- **Memory** — `c-amber`, `x=590 y=156 w=200 h=60`, two-line (*Memory*, *plan + context*).
|
||||
- **Search subagent row** (3 workers, all `c-purple`, same size):
|
||||
- *Search subagent A*, `x=200 y=340 w=160 h=64`, two-line (*Search subagent*, *Aspect A*).
|
||||
- *Search subagent B*, `x=390 y=340 w=160 h=64`, two-line (*Search subagent*, *Aspect B*).
|
||||
- *Search subagent C*, `x=580 y=340 w=160 h=64`, two-line (*Search subagent*, *Aspect C*).
|
||||
|
||||
**Arrow plan.**
|
||||
- User ↔ Lead — stacked request/response pair: `user → lead` solid `arr` `M 150 174 L 310 174` labeled *query*; `lead → user` dashed `arr-alt` `M 310 198 L 150 198` labeled *final report*.
|
||||
- Lead ↔ Citations — short bidirectional pair on lead's left edge: `M 290 180 L 310 180` (in), `M 310 204 L 290 204` (out). Both solid `arr`.
|
||||
- Lead ↔ Memory — matching pair on lead's right edge: `M 570 180 L 590 180`, `M 590 204 L 570 204`.
|
||||
- Lead → each subagent — vertical L-bends from lead's bottom edge `(y=270)` via a shared channel at `y=305`. Dispatch arrows anchor on lead at `(360, 270)`, `(470, 270)`, `(520, 270)` and land at each subagent's top-center `(280, 340)`, `(470, 340)`, `(660, 340)`. Middle arrow is straight; outer two L-bend through the channel.
|
||||
- Subagent → Lead — matching return arrows offset 16px to the right of each dispatch arrow, using `arr-alt` to mark the return.
|
||||
- **Self-loop on each search subagent** — per `flowchart.md` → Self-loops, a short arc off the right edge: `M {x+w} {y+16} C {x+w+24} {y+8}, {x+w+24} {y+h-8}, {x+w} {y+h-16}` class `arr`, with a `ts` label *search + think* at `({x+w+28}, {y+h/2})`. Repeat for all three subagents. This is the key visual that distinguishes research subagents from plain workers.
|
||||
|
||||
**Legend** (bottom, required):
|
||||
|
||||
```
|
||||
[■] Lead agent [■] Search subagent [■] Citation subagent [■] Memory [──] dispatch [- -] return
|
||||
```
|
||||
|
||||
**Gotchas.**
|
||||
- Do not color the 3 search subagents differently — they are one pool, not three roles. Rainbow-ing them turns a homogeneous worker pool into a role fan-out and misrepresents the pattern.
|
||||
- Keep memory amber even though it is a peer of lead — the store convention keeps readers from reading it as a third agent.
|
||||
- The citation subagent runs *at the end* logically, but the architecture view draws it as a peer of the lead agent (not a downstream successor), matching Anthropic's own diagram. If the user asks for the turn-order view instead, switch to the sequence alternate.
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# Shared state
|
||||
|
||||
**One-line description.** N peer agents coordinate by reading and writing a persistent shared store — database, filesystem, document — with no central orchestrator, router, or message bus between them. Each agent checks the store for relevant findings, acts on what it sees, and writes its own contributions back. Findings are immediately visible to every other agent; the store becomes an evolving knowledge base. Canonical use case: a research synthesis system where an academic-literature agent, industry-reports agent, patent agent, and news agent all build on each other's discoveries in real time.
|
||||
|
||||
## Default diagram type
|
||||
|
||||
**Structural — radial star.** The point of the pattern is *no central coordinator* — the hub is a passive store, not an active router. A radial star diagram puts the store in the center and the agents around it, with bidirectional read/write channels on every spoke. A flowchart would smuggle in an implied order; a bus topology would suggest events in flight rather than accumulated state.
|
||||
|
||||
Alternate types:
|
||||
- **Illustrative** when the user wants a *cross-section* metaphor — concentric layers, store as a well, agents as drawers dipping in. Rarely the right call for production docs but occasionally useful for teaching the intuition.
|
||||
- **Structural (subsystem)** when the user is contrasting shared-state with a different pattern (agent teams, message bus) — in that case use the two-sibling container layout and make this pattern's side a mini radial star inside the right container. See `structural.md` → "Rich interior for subsystem containers".
|
||||
|
||||
## Palette
|
||||
|
||||
- **`c-amber`** — the shared state hub. This is the one place in the diagram that "everybody is looking at," and amber is the documented convention for shared-state hubs per `structural.md` → "Radial star topology sub-pattern".
|
||||
- **`c-gray`** — the peripheral satellite agents. They are peers doing the same *kind* of work (read + write), and the pattern's whole argument is that they're interchangeable. Giving each satellite its own ramp is rainbow coloring that implies a hierarchy that doesn't exist.
|
||||
- **`c-teal`** — acceptable *instead of* gray for all satellites together (single ramp for "agent peer"), when the diagram needs to distinguish the agents from other gray scaffolding elsewhere on the canvas. Never mix gray and teal satellites in the same diagram — it looks like two tiers.
|
||||
|
||||
Never promote one satellite to a different ramp "to show the primary agent". If one agent really is primary, the topology isn't radial star — it's orchestrator-subagent or agent-teams.
|
||||
|
||||
## Sub-pattern
|
||||
|
||||
`structural.md` → **Radial star topology sub-pattern**. The central hub carries a `doc-icon` glyph from `glyphs.md` → "Document & terminal icons" to signal it's a store (not just an abstract coordinator). `layout-math.md` → "Radial star geometry (3 / 4 / 5 / 6 satellites)" has the fixed coordinate table for N=3 through N=6.
|
||||
|
||||
## Mermaid reference
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Academic agent] <--> S[(Shared state store)]
|
||||
I[Industry agent] <--> S
|
||||
P[Patent agent] <--> S
|
||||
N[News agent] <--> S
|
||||
```
|
||||
|
||||
The defining shape is the bidirectional double-headed arrow between every satellite and the store, with *no arrows between satellites*. Any agent-to-agent edge turns the topology into a mesh and is a signal to switch patterns.
|
||||
|
||||
## Baoyu SVG plan
|
||||
|
||||
N=4 radial star with a doc-icon hub. Uses the Anthropic research-synthesis example labels verbatim — swap them for the user's domain.
|
||||
|
||||
- **viewBox**: `0 0 680 560`
|
||||
- **Hub (shared state store)** — `c-amber`, `x=260 y=280 w=160 h=80 rx=10`.
|
||||
- Title *Shared state* at `(340, 302)`, `th`, centered.
|
||||
- Subtitle *Database / filesystem / doc* at `(340, 320)`, `ts`, centered.
|
||||
- `doc-icon` glyph at `translate(328, 328)` (bottom-center of hub rect). Copy the 5-line doc-icon path from `structural.md` → "Hub content" verbatim — it's the worked example for exactly this diagram.
|
||||
- **Satellites** (4, all `c-gray`, `w=160 h=60`, two-line):
|
||||
- *Academic agent*, `x=60 y=120`, subtitle *Literature search*.
|
||||
- *Industry agent*, `x=460 y=120`, subtitle *Market reports*.
|
||||
- *Patent agent*, `x=60 y=460`, subtitle *Patent filings*.
|
||||
- *News agent*, `x=460 y=460`, subtitle *Current coverage*.
|
||||
|
||||
**Bidirectional arrow pairs** (use the pre-computed N=4 endpoints from `layout-math.md` → "Radial star geometry"):
|
||||
|
||||
- TL Academic: outbound `(224, 176) → (264, 276)`, inbound offset perpendicular by 8.
|
||||
- TR Industry: outbound `(456, 176) → (416, 276)`, inbound offset.
|
||||
- BL Patent: outbound `(224, 464) → (264, 364)`, inbound offset.
|
||||
- BR News: outbound `(456, 464) → (416, 364)`, inbound offset.
|
||||
- Label each pair with a single `ts` *Read / write* next to the satellite end (not between the two offset lines — the 8px gap is too narrow). For the top satellites, place the label just below the satellite box at `y ≈ 198`. For the bottom satellites, just above at `y ≈ 448`.
|
||||
|
||||
**Centered banner for termination rule.** Shared-state systems cycle indefinitely without an explicit termination condition. Drop a small centered `ts` caption at the top of the canvas — `y=60`, `text-anchor="middle"` at `x=340` — naming the rule the system uses: *Until convergence (no new findings for N cycles)*, *Until time budget exhausted*, or *Until a designated terminator agent signals done*. This is not ornamental — a shared-state diagram without a termination line misrepresents the pattern. See the blog's "reactive loops" failure mode.
|
||||
|
||||
**Legend.** Not needed — the single accent color on the hub and the shared gray on satellites self-document. If you used `c-teal` satellites instead of gray, still no legend: one ramp on all peers means "they're all the same role", which is the whole message.
|
||||
|
||||
**When to use N≠4.** Stick to N=4 unless the source explicitly names a different count. N=3 when the user names three investigators, N=5–6 for larger ecosystems. Beyond 6 satellites, switch to the bus topology pattern — a shared store with 8+ agents suggests event-driven coordination, not accumulated state.
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
# Pitfalls Checklist
|
||||
|
||||
Run through this list after writing the SVG and before saving the file. Most diagram failures are one of these ten, and they all happen silently — the SVG is "valid" but looks wrong when rendered.
|
||||
|
||||
Read this as a code review on yourself. If any item fails, fix the SVG and re-check.
|
||||
|
||||
## 1. viewBox clips content
|
||||
|
||||
**Symptom**: the bottom row of boxes is cut off, or a label disappears past the right edge.
|
||||
|
||||
**Check**:
|
||||
- Find the lowest element. For rects: `y + height`. For text: `y + 4` (descender). For circles: `cy + r`.
|
||||
- `viewBox` height must be `max(those) + 20` or more.
|
||||
- Find the rightmost element. For rects: `x + width`. No rect's right edge should exceed 640.
|
||||
|
||||
**Fix**: increase `H` in `viewBox="0 0 680 H"`, or shrink the content horizontally.
|
||||
|
||||
**Exception — subsystem architecture pattern.** The 2-up sibling containers (see `structural.md` → "Subsystem architecture pattern" and `layout-math.md` → "Sibling subsystem containers (2-up)") deliberately sacrifice the 40px horizontal safe margin so each sibling interior is wide enough for a short internal flow. With `container_A.x=20` and `container_B.x=345, width=315`, container B's right edge lands at **660**. That is the documented layout; rects belonging to the 2-up pattern may extend up to x=660 instead of 640. All content *inside* each container (internal nodes, cross-system labels) still respects its own interior edge — no individual flowchart rect inside a sibling should touch the dashed border.
|
||||
|
||||
## 2. Text overflows its box
|
||||
|
||||
**Symptom**: a label spills out past the border of its container.
|
||||
|
||||
**Check**: for every labeled rect, compute `label_chars × char_width + 24` (8 for 14px latin, 7 for 12px latin, 15 for 14px CJK). The rect's width must be ≥ that number.
|
||||
|
||||
**Fix**: widen the rect, or shorten the label. Subtitles > 5 words are always a smell — cut them.
|
||||
|
||||
## 3. Arrow crosses an unrelated box
|
||||
|
||||
**Symptom**: an arrow visibly slashes through the interior of a rect it's not anchored to.
|
||||
|
||||
**Check**: for every arrow, trace its line segment(s). For every other rect in the diagram, does the arrow cross the rect's interior? If yes, it's a bug.
|
||||
|
||||
**Fix**: replace the straight `<line>` with an L-bend `<path>` that routes around the obstacle:
|
||||
|
||||
```svg
|
||||
<path d="M x1 y1 L x1 ymid L x2 ymid L x2 y2" class="arr" fill="none" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Pick `ymid` so the horizontal channel runs through empty space between rows.
|
||||
|
||||
## 4. Connector path is missing `fill="none"`
|
||||
|
||||
**Symptom**: a curved connector renders as a giant black blob instead of a thin line.
|
||||
|
||||
**Check**: every `<path>` or `<polyline>` used as a connector must have `fill="none"` as an explicit attribute. SVG defaults paths to `fill: black`.
|
||||
|
||||
**Fix**: add `fill="none"` — or use the `arr` class, which already sets it.
|
||||
|
||||
## 5. Text has no class, renders as default
|
||||
|
||||
**Symptom**: a label looks slightly different from the rest — wrong size, wrong color, or black in dark mode.
|
||||
|
||||
**Check**: every `<text>` element must have a class: `t`, `ts`, `th`, or (in poster flowcharts only) `title`, `eyebrow`, `caption`, `anno`. No unclassed text. No `fill="inherit"`. No hardcoded `fill="black"`.
|
||||
|
||||
**Fix**: add the appropriate class. `t` for single-line body, `th` for titles (bold), `ts` for subtitles and callouts. In a poster flowchart, `title` for the top label, `eyebrow` for section dividers, `caption` for the footer hook, `anno` for right-column side notes.
|
||||
|
||||
## 6. Title and subtitle use the same color stop
|
||||
|
||||
**Symptom**: a two-line node looks visually flat — the title and subtitle blur together.
|
||||
|
||||
**Check**: inside any `c-{ramp}` box, the `th` and `ts` children must land on different stops. The template handles this automatically (th → stop 800, ts → stop 600), so this only breaks if you manually override a fill. Don't.
|
||||
|
||||
**Fix**: remove the inline `fill=` override; let the template classes do their job.
|
||||
|
||||
## 7. `text-anchor="end"` at low x
|
||||
|
||||
**Symptom**: a label is missing from the left side of the diagram — it's actually there, but it extends past x=0 and is clipped by the viewBox.
|
||||
|
||||
**Check**: for every `<text>` with `text-anchor="end"`, the label's width must fit to the left of its x coordinate: `label_chars × char_width < x`.
|
||||
|
||||
**Fix**: use `text-anchor="start"` and right-align the column manually, or move the text to a higher x.
|
||||
|
||||
## 8. Color rainbow (colors cycle instead of encoding meaning)
|
||||
|
||||
**Symptom**: a 5-step flowchart uses blue-teal-amber-coral-purple, one per step. Reader can't tell if the colors mean anything.
|
||||
|
||||
**Check**: do colors encode categories (all "immune cells" share one color) or do they encode sequence (step-1 blue, step-2 teal)? Sequence is wrong.
|
||||
|
||||
**Fix**: collapse to ≤2 ramps. Use gray for neutral/structural/sequential steps. Reserve one accent color for whichever nodes deserve emphasis — the decision point, the anomaly, the main character of the story.
|
||||
|
||||
## 9. Too many boxes in a full-width row
|
||||
|
||||
**Symptom**: boxes overlap each other, or text spills across box borders.
|
||||
|
||||
**Check**: `N × box_width + (N - 1) × gap ≤ 600`. If you tried to fit 5+ boxes at default width (180 each), you've already overflowed.
|
||||
|
||||
**Fix**:
|
||||
- Shrink box_width to ≤110 (drops subtitles)
|
||||
- Wrap to 2 rows
|
||||
- Split into overview + detail diagrams
|
||||
|
||||
## 10. Cycle drawn as a physical ring
|
||||
|
||||
**Symptom**: a four-stage cycle (Krebs, event loop, GC) is laid out with boxes orbiting a dashed circle. Labels collide with stage boxes; feedback arrows point at weird angles.
|
||||
|
||||
**Check**: does the diagram try to show a loop by arranging boxes in a circle?
|
||||
|
||||
**Fix**: lay the stages out linearly (horizontal or vertical) and draw a single return arrow from the last stage back to the first — or simply add a small `↻ returns to start` label near the endpoint. The loop is conveyed by the return arrow, not by literal ring geometry.
|
||||
|
||||
## 11. Dark-mode invisible text (bonus check)
|
||||
|
||||
**Symptom**: the SVG looks fine in light mode, but text disappears in dark mode.
|
||||
|
||||
**Check**: did you hardcode any `fill="#..."` or `fill="black"` on a `<text>` or ignore the `t`/`ts`/`th` classes? If yes, dark mode won't override it.
|
||||
|
||||
**Fix**: remove hardcoded fills on text. Let the template classes handle both modes.
|
||||
|
||||
**Exception**: physical-color scenes (sky blue, grass green, water teal in an illustrative diagram) *should* stay the same in both modes. Hardcode those hex values deliberately. But all label text — the `<text>` elements with callouts and titles — must use the classes.
|
||||
|
||||
## 12. `<!--` comments left in the SVG (bonus check)
|
||||
|
||||
**Symptom**: final SVG has HTML comments. They waste bytes and some markdown renderers show them.
|
||||
|
||||
**Check**: grep for `<!--` in your generated SVG. There should be none in the output (even though they appear in the template documentation above).
|
||||
|
||||
**Fix**: strip them before saving.
|
||||
|
||||
## 13. Lifeline clipped at the bottom (sequence)
|
||||
|
||||
**Symptom**: the last message arrow looks like it's hanging in space because the lifeline ends at the arrow's y, with no tail beneath it.
|
||||
|
||||
**Check**: `lifeline_y2 ≥ last_arrow_y + 24`. Every lifeline needs a 24px tail past the last message.
|
||||
|
||||
**Fix**: extend all lifeline `<line y2="...">` values to `last_arrow_y + 24` and bump viewBox H accordingly.
|
||||
|
||||
## 14. Actor header overflow (sequence)
|
||||
|
||||
**Symptom**: the title or role text spills past the actor header rect.
|
||||
|
||||
**Check**: `max(title_chars × 8, role_chars × 7) + 24 ≤ header_w`. For N=4, header_w=120, so the title caps at (120 − 24) / 8 = 12 characters. Longer titles need a shorter actor, a merged actor, or dropping the role subtitle.
|
||||
|
||||
**Fix**: cut the role subtitle first (it's optional), then shorten the title. Never widen the lane beyond the table in `layout-math.md` — it breaks the tier packing.
|
||||
|
||||
## 15. Message label too long for its lane span (sequence)
|
||||
|
||||
**Symptom**: a message label collides with an adjacent actor's header or another message label.
|
||||
|
||||
**Check**: `label_chars × 7 ≤ |sender_x − receiver_x| − 8`. For N=4 adjacent lanes (|Δx|=160), labels cap at ~21 chars. For longer leaps the budget grows linearly.
|
||||
|
||||
**Fix**: shorten the label (the most effective fix — "Redirect with client_id, scope" → "Redirect + client_id"), or restructure the message between actors that are further apart on the diagram (rare).
|
||||
|
||||
## 16. Arrow endpoint mispositioned on lifeline (sequence)
|
||||
|
||||
**Symptom**: the arrowhead touches the dashed lifeline stroke and looks smeared, or the arrow stops 15px short and looks disconnected from the target.
|
||||
|
||||
**Check**: every message arrow's `x2` must equal `receiver_x ± 6` (minus 6 for left-to-right, plus 6 for right-to-left). Same for `x1` on the sender side.
|
||||
|
||||
**Fix**: recompute every arrow's x1/x2 from the lifeline center table. Never eyeball.
|
||||
|
||||
## 17a. Poster flowchart missing its title or subtitle
|
||||
|
||||
**Symptom**: the diagram contains eyebrow-divided phases and a fan-out row, but no `title` at the top — it looks like a collection of boxes floating in space.
|
||||
|
||||
**Check**: if the diagram qualifies as a poster (≥3 of the poster triggers in `flowchart.md`), it *must* have a `.title` at the top. The title is the reader's first anchor — without it, the eyebrows and side notes feel unmoored.
|
||||
|
||||
**Fix**: add a one-line `.title` with the mechanism name at (340, 46), and a `.ts` subtitle at (340, 68) that frames the "why".
|
||||
|
||||
## 17b. Fan-out row conflates "candidates" with "sequence"
|
||||
|
||||
**Symptom**: three side-by-side boxes labeled step 1 / step 2 / step 3, each in a different color.
|
||||
|
||||
**Check**: a fan-out row should contain **parallel alternatives** that all feed into a judge, not sequential steps. If the boxes have a reading order, they belong in a column, not a row. Colors on a fan-out row are fine *if* they represent distinct candidate identities (keep / rewrite / synthesize), not fine if they're decorating sequence.
|
||||
|
||||
**Fix**: either reorient to a vertical column (sequence) or rewrite the labels so each is clearly a *kind* of candidate (A · keep, B · rewrite, AB · synthesize), not a step number.
|
||||
|
||||
## 17c. Side-annotation column floats too far from its box
|
||||
|
||||
**Symptom**: an `anno` line at y=200 is supposed to belong to a box at y=300 — 100px away — and the reader has to guess which box it describes.
|
||||
|
||||
**Check**: an annotation line's y must fall inside the vertical range of its target box (top to bottom). For a 56-tall box at y=300, annotations belong at y between 304 and 350. One line at the box center, or up to three lines vertically centered on the box.
|
||||
|
||||
**Fix**: recompute each annotation's y from its target box's y + height/2, then stack lines at ±14px from center.
|
||||
|
||||
## 17d. Loop-rail rotated label clips the top or bottom
|
||||
|
||||
**Symptom**: the rotated "loop until …" label extends past the loop rail line, or overlaps the first/last box inside the loop.
|
||||
|
||||
**Check**: `transform="rotate(-90 cx cy)"` rotates around `(cx, cy)`, where the text after rotation occupies ≈ `label_chars × 7` horizontally (now vertically post-rotation). The rotated label's extent must fit between `loop_top` and `loop_bottom`.
|
||||
|
||||
**Fix**: shorten the label (≤20 chars), or extend the rail.
|
||||
|
||||
## 18. Self-message rect not centered on its lifeline (sequence)
|
||||
|
||||
**Symptom**: the small self-message rect sits to one side of the lifeline, not straddling it.
|
||||
|
||||
**Check**: `rect_x = lifeline_x − rect_w / 2`. For `rect_w = 16`, that's `lifeline_x − 8`. Off-by-one is easy when rect_w is small.
|
||||
|
||||
**Fix**: recompute rect_x from the lifeline x. Double-check the rect has the actor's `c-{ramp}` class so the fill matches the lifeline's owning actor.
|
||||
|
||||
## 19. Bus-topology bar not centered or shifted off-axis
|
||||
|
||||
**Symptom**: the central horizontal bar in a message-bus diagram is slightly off-center, so the Publish/Subscribe arrow pairs from the top and bottom agents don't line up symmetrically and the whole diagram feels tilted.
|
||||
|
||||
**Check**: the bus bar must sit at `x=40 y=280 w=600 h=40` (see `layout-math.md` → "Bus topology geometry"). `bar_cx = 340`. Every agent row must center on that same `bar_cx` axis — if the top row is centered at x=340 but the bottom row drifts to x=335, the diagram is crooked.
|
||||
|
||||
**Fix**: recompute top and bottom agent x positions from the shared center using the "Bus topology geometry" table. Agents per row: 2 → centers at 180, 500. 3 → 170, 340, 510. 4 → 120, 260, 420, 560. Don't eyeball.
|
||||
|
||||
## 20. Radial-star satellites not at symmetric offsets
|
||||
|
||||
**Symptom**: three or four "satellite" boxes around a hub are almost, but not quite, evenly spaced — one is 20px further from the hub than the others, or one sits at a slightly different y than its mirror.
|
||||
|
||||
**Check**: use the fixed satellite coordinate table in `layout-math.md` → "Radial star geometry". The N=4 boxes must sit at `(60, 120)`, `(460, 120)`, `(60, 460)`, `(460, 460)` — any deviation breaks the mirror symmetry and the eye catches it immediately.
|
||||
|
||||
**Fix**: copy the coordinate table verbatim. If you need a satellite at a non-symmetric position because its label is longer, the pattern isn't the right shape — drop the satellite to a subtitle or switch to a bus topology.
|
||||
|
||||
## 21. Spectrum axis arrow covered by end label
|
||||
|
||||
**Symptom**: the left or right end of the spectrum axis's arrowhead sits behind the end label's text, or the label's descender touches the arrowhead.
|
||||
|
||||
**Check**: axis at `y=140`, end labels at `y=120` — that's only 20px of vertical separation. For end labels that include descender glyphs (p, g, y, q), the descenders sit around `y=124`, leaving 16px to the arrowhead. Verify the label baseline at 120 and that `text-anchor` correctly flushes each label to its axis end (`start` on left, `end` on right).
|
||||
|
||||
**Fix**: if the label still collides after fixing the text-anchor, bump end-label `y` to `116` (4px clearance above the arrowhead).
|
||||
|
||||
## 22. Box icon overlapping its text
|
||||
|
||||
**Symptom**: a `doc-icon` or `terminal-icon` inside a structural/illustrative box touches or overlaps the subtitle text below the title.
|
||||
|
||||
**Check**: for a box with title `y+22` and subtitle `y+40`, the subtitle baseline sits at `box_y + 44` (including descender). The icon's top must sit at **≥ 8px below that**, so `icon_top ≥ box_y + 52`. For a 28-tall icon, `box_h ≥ 52 + 28 + 8 = 88`. The documented minimum in `glyphs.md` is 80, which assumes a title-only box (no subtitle) — grow to 88 when both title and subtitle are present.
|
||||
|
||||
**Fix**: grow `box_h` until the math works, or drop the subtitle if the box is already at the design max height.
|
||||
|
||||
## 23. Checklist row width overflow
|
||||
|
||||
**Symptom**: a checkbox + label row inside a subsystem container runs past the container's right edge and the label clips.
|
||||
|
||||
**Check**: inside a 315-wide subsystem container, checklist width budget is `interior_w − checkbox_w − gap − right_padding = 275 − 14 − 8 − 20 = 233` px, so `label_chars × 8 ≤ 233` → cap at ~29 Latin chars (or 15 CJK). The `glyphs.md` doc cites ~31 Latin chars for a looser budget — use 29 when you want a safety margin inside a sibling container.
|
||||
|
||||
**Fix**: truncate the label or wrap to two rows. Don't shrink the checkbox — 14px is the floor.
|
||||
|
||||
## 24. Status circle not aligned with the arrow path
|
||||
|
||||
**Symptom**: a `status-circle-check` (or `-x`, `-dot`) glyph sits next to the arrow instead of **on** it, or sits exactly on the arrow's path but the arrow still draws through the circle's center as a single continuous line.
|
||||
|
||||
**Check**: the arrow must be **split into two segments**: segment 1 from source to `(circle_cx − 14, circle_cy)`, segment 2 from `(circle_cx + 14, circle_cy)` to target. If the original `<line>` is still there in one piece, delete it and replace with the two segments.
|
||||
|
||||
**Fix**: recompute the two segments. Both get `marker-end="url(#arrow)"` so each has its own arrowhead landing 2px before the circle's edge. See `flowchart.md` → "Status-circle junctions" for the exact template.
|
||||
|
||||
## 25. Dashed future-state node drifts out of row alignment
|
||||
|
||||
**Symptom**: a dashed `arr-alt`-class rect in a DAG sits at y=180 while the solid nodes around it sit at y=180.5 (or whatever the stroke-centering math produces), so the row looks misaligned even though the coordinates match.
|
||||
|
||||
**Check**: SVG strokes are centered on the path, so a rect with `stroke-width="1.5"` extends 0.75 px into the interior *and* 0.75 px outside its `x/y/width/height` attributes. Because the dashed rect's stroke is a different *pattern* from the solid rect's stroke, the visual difference can magnify this half-pixel. For perfect row alignment, the dashed rect and the solid rect must share the exact same x, y, width, and height — any "nudge by 1 to compensate" breaks the row.
|
||||
|
||||
**Fix**: keep the coordinates identical. If the visual misalignment persists and you've confirmed the coords match, the issue is the dash pattern start position, not the coordinates — try `stroke-dashoffset="0"` to force dash alignment, or accept the micro-difference (it's imperceptible at 1× zoom).
|
||||
|
||||
## 26. Parallel-rounds y's not at a consistent pitch
|
||||
|
||||
**Symptom**: three rounds stacked vertically look visually uneven because the gaps between rounds aren't equal — round 1→2 is 80px but round 2→3 is 72px.
|
||||
|
||||
**Check**: round pitch is **80** in the full-width variant (`layout-math.md` → "Parallel rounds geometry"). Round k's call arrow y = `124 + (k−1) × 80`. If you computed round 2's y from round 1 by eye, recheck — "visually close enough" is not close enough for a side-by-side comparison diagram.
|
||||
|
||||
**Fix**: recompute all round y's from the table.
|
||||
|
||||
## 27. Annotation-circle connector line not perpendicular to the arrow
|
||||
|
||||
**Symptom**: the small vertical line connecting an annotation circle to its underlying arrow pair is slightly tilted because its x doesn't match the circle's center x.
|
||||
|
||||
**Check**: the connector line's `x1` and `x2` must both equal `circle_cx`. The circle is placed via `translate(cx − 32, cy − 32)`, so the circle's center is `(cx, cy)` in diagram coordinates. The connector's `y1 = cy + 30` (bottom of circle), `y2 = arrows_y_midpoint`.
|
||||
|
||||
**Fix**: recompute `x1 = x2 = circle_cx`. Don't set them from the translate offsets directly — use the diagram-space `cx` you passed to translate.
|
||||
|
||||
## 28. Glyph using inline fill or stroke, broken in dark mode
|
||||
|
||||
**Symptom**: a `status-circle-check`, `doc-icon`, or `annotation-circle` renders correctly in light mode but is invisible (or wrong color) in dark mode.
|
||||
|
||||
**Check**: grep the SVG for any `fill="#..."` or `stroke="#..."` inside a glyph's element tags. Glyphs must use only CSS classes (`c-{ramp}`, `arr-{ramp}`, `arr`, `arr-alt`, `box`, `t`/`th`/`ts`) so the template's `@media (prefers-color-scheme: dark)` block inverts them automatically.
|
||||
|
||||
**Fix**: replace every hardcoded color on a glyph element with the corresponding class. If you need a color that isn't in the 9-ramp palette, you don't need a new color — you're asking the glyph to say something it shouldn't. See `glyphs.md` → "Hard rules" for the full policy.
|
||||
|
||||
## 29. Arrow bleeds through semi-transparent fill
|
||||
|
||||
**Symptom**: a connector line that passes behind a node is faintly visible *through* the node's fill — especially noticeable on illustrative diagrams with gradient overlays or on nodes where the fill color is very light.
|
||||
|
||||
**Check**: does any node use a translucent or very light fill (stop 50 from a ramp) through which a 1.5px stroke behind it would show? The default `c-{ramp}` fills are opaque enough that this rarely matters, but when a plan calls for a translucent overlay (gradient, or a manually lightened fill), the bleed becomes visible.
|
||||
|
||||
**Fix**: draw an opaque background rect at the same position *before* the styled rect. The background rect masks the arrow; the styled rect paints on top with the desired appearance:
|
||||
|
||||
```svg
|
||||
<!-- Opaque mask to hide arrow behind this node -->
|
||||
<rect x="200" y="100" width="180" height="56" rx="6" class="box"/>
|
||||
<!-- Styled node on top -->
|
||||
<g class="c-purple">
|
||||
<rect x="200" y="100" width="180" height="56" rx="6"/>
|
||||
<text class="th" x="290" y="124" text-anchor="middle" dominant-baseline="central">Title</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
The `box` class works as the mask because it uses opaque fill in both light and dark mode. Only add the mask rect when bleed-through is visible — most diagrams don't need it because the z-order rule (connectors drawn before nodes) already handles opaque fills.
|
||||
|
||||
## 30. Legend clips into a container boundary
|
||||
|
||||
**Symptom**: a legend strip sits inside (or overlaps) a dashed container's bottom edge — the legend text reads as part of the container's content instead of as diagram-level metadata.
|
||||
|
||||
**Check**: find the lowest container boundary: `container_y + container_h`. The legend's top edge must be ≥20px below that boundary. If the legend sits inside or touching any container, it's a bug.
|
||||
|
||||
**Fix**: move the legend below all container boundaries. Calculate: `legend_y = max(all_container_bottoms) + 20`. Then extend viewBox H to accommodate: `H = legend_y + legend_h + 20`. Never squeeze a legend inside a container to save vertical space — expand the viewBox instead.
|
||||
|
||||
```
|
||||
Container bottom at y=380
|
||||
Legend at y=400 → 20px clear ✓
|
||||
viewBox H = 400 + 16 + 20 = 436
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick self-review template
|
||||
|
||||
Before writing the file, mentally run through:
|
||||
|
||||
> 1. Lowest element is at y = ___ → viewBox H = ___ + 20 = ___ ✓
|
||||
> 2. Rightmost rect edge is at x = ___ → ≤ 640 ✓
|
||||
> 3. Longest label is "___" (___ chars) → needs width ___ → actual width ___ ✓
|
||||
> 4. Arrows checked against all boxes → no crossings ✓
|
||||
> 5. All connector paths have `fill="none"` ✓
|
||||
> 6. All `<text>` elements have a class ✓
|
||||
> 7. Colors: ≤ 2 ramps → ___ and ___ → assigned by category ✓
|
||||
> 8. No hardcoded text fills ✓
|
||||
> 9. No comments in final output ✓
|
||||
> 10. No arrow bleed-through on translucent fills (mask rect if needed) ✓
|
||||
> 11. Legend sits ≥20px below all container boundaries ✓
|
||||
|
||||
If any of these feel fuzzy, the diagram isn't ready.
|
||||
|
|
@ -1,303 +1,88 @@
|
|||
# Sequence Diagram
|
||||
# Sequence Diagram Layout
|
||||
|
||||
For multi-actor protocols and conversations — "who talks to whom, in what order". A row of actor headers at the top, dashed lifelines dropping down, and numbered horizontal message arrows between them. The classic UML sequence diagram, adapted to this skill's design system.
|
||||
## Core Elements
|
||||
|
||||
## When to use
|
||||
| Element | Visual | Description |
|
||||
|---------|--------|-------------|
|
||||
| Actor/Participant | Box at top + dashed vertical lifeline | Each entity in the interaction |
|
||||
| Sync message | Solid arrow → | Request or call |
|
||||
| Async message | Open arrowhead → | Fire-and-forget |
|
||||
| Return message | Dashed arrow ← | Response |
|
||||
| Activation bar | Narrow filled rect on lifeline | Entity is processing |
|
||||
| Self-message | Arrow looping back to same lifeline | Internal processing |
|
||||
| Note | Rounded rect with folded corner | Annotation |
|
||||
| Alt/Opt frame | Dashed boundary with label tab | Conditional block |
|
||||
| Loop frame | Dashed boundary with "loop" tab | Repetition |
|
||||
|
||||
- Authentication and authorization flows — OAuth, OIDC, SAML, Kerberos, JWT refresh, mTLS handshakes
|
||||
- Wire protocols — TCP three-way handshake, TLS handshake, WebSocket upgrade, HTTP/2 multiplex
|
||||
- RPC patterns — gRPC unary and streaming, JSON-RPC, GraphQL over websockets
|
||||
- Webhooks, callbacks, and event dispatch — stripe webhooks, GitHub events, PubSub fan-out
|
||||
- Any "request + response" dance between 2–4 named services where *order* and *who initiates what* is the point
|
||||
## Layout Algorithm
|
||||
|
||||
**Trigger verbs**: "protocol", "handshake", "auth flow", "request/response", "exchange between", "round trip", "who calls what", "API dance".
|
||||
1. **Place actors** horizontally across the top, evenly spaced (150-200px apart)
|
||||
2. **Draw lifelines** as vertical dashed lines from each actor box downward
|
||||
3. **Place messages** as horizontal arrows between lifelines, top to bottom in time order
|
||||
4. **Vertical spacing** between messages: 40-50px
|
||||
5. **Activation bars:** 10px wide, centered on lifeline, spanning from incoming to outgoing message
|
||||
|
||||
**When not to use**:
|
||||
- Single-actor sequential process → `flowchart.md` ("what steps does the build pipeline run")
|
||||
- Containment / architecture / "what lives inside what" → `structural.md`
|
||||
- "Feel how X works" / mechanism intuition → `illustrative.md`
|
||||
|
||||
**Routing test**: if the prompt names ≥2 distinct actors/participants/services (User + Server, Client + Auth + Resource, Browser + CDN + Origin), prefer sequence even when the noun is "flow" or "process". Single-actor "X flow" stays flowchart.
|
||||
|
||||
## Planning before you write SVG
|
||||
|
||||
1. **List the actors**, 2–4 of them, in the order they appear across the page (usually left = initiator, right = downstream services). Each actor needs a short title (≤12 chars for N=4) and an optional single-line role subtitle.
|
||||
2. **Assign one color ramp to each actor**. Default palette: `[c-gray, c-teal, c-purple, c-blue]` left-to-right. Swap in `c-coral`, `c-pink`, `c-amber`, `c-green` as needed — pick cool/neutral ramps and avoid semantic collisions (green for success, red for error) unless the actor literally means that.
|
||||
3. **List the messages** as ordered tuples `(sender, receiver, short label)`. Cap at 10 messages — 6–8 is the sweet spot. If your protocol has more, split into an overview diagram and one detail diagram per sub-flow.
|
||||
4. **Mark self-messages** — messages that stay on the same lifeline (e.g., "validate token" on the resource server). Limit to 1–2 per diagram.
|
||||
5. **Plan a side note** — a short title that names the protocol. For N ≤ 3, place it at top-left beside the first actor header. For N ≥ 4, place it at bottom-left under the lifelines.
|
||||
6. **Check every message label** against its lane span using the budget formula in `layout-math.md`. Shorten aggressively — labels that barely fit today will overflow if you tweak the actor order.
|
||||
|
||||
Save the plan to `plan.md` alongside the SVG so the next iteration can read it.
|
||||
|
||||
## Actor headers
|
||||
|
||||
Each actor header is a two-line node (title + role) or a single-line node (title only), wrapped in a `c-{ramp}` group. Height is always 48, y=40..88.
|
||||
|
||||
Single-line (title only):
|
||||
## Actor Box
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="40" y="40" width="120" height="48" rx="8"/>
|
||||
<text class="th" x="100" y="64" text-anchor="middle" dominant-baseline="central">User</text>
|
||||
</g>
|
||||
<!-- Actor box -->
|
||||
<rect x="X" y="20" width="130" height="45" rx="6" fill="#0f172a"/>
|
||||
<rect x="X" y="20" width="130" height="45" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
|
||||
<text x="CX" y="47" fill="white" font-size="11" font-weight="600" text-anchor="middle">Actor Name</text>
|
||||
|
||||
<!-- Lifeline -->
|
||||
<line x1="CX" y1="65" x2="CX" y2="BOTTOM" stroke="#334155" stroke-width="1" stroke-dasharray="6,4"/>
|
||||
```
|
||||
|
||||
Two-line (title + role):
|
||||
## Message Arrows
|
||||
|
||||
```svg
|
||||
<g class="c-teal">
|
||||
<rect x="200" y="40" width="120" height="48" rx="8"/>
|
||||
<text class="th" x="260" y="60" text-anchor="middle" dominant-baseline="central">Client app</text>
|
||||
<text class="ts" x="260" y="78" text-anchor="middle" dominant-baseline="central">Web</text>
|
||||
</g>
|
||||
<!-- Sync message (solid arrow) -->
|
||||
<line x1="FROM_CX" y1="Y" x2="TO_CX" y2="Y" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="MID_X" y="Y-8" fill="#e2e8f0" font-size="9" text-anchor="middle">methodCall()</text>
|
||||
|
||||
<!-- Return message (dashed arrow, reversed direction) -->
|
||||
<line x1="TO_CX" y1="Y" x2="FROM_CX" y2="Y" stroke="#64748b" stroke-width="1" stroke-dasharray="6,3" marker-end="url(#arrow)"/>
|
||||
<text x="MID_X" y="Y-8" fill="#94a3b8" font-size="8" text-anchor="middle" font-style="italic">response</text>
|
||||
|
||||
<!-- Self-message (loop arrow) -->
|
||||
<path d="M CX,Y L CX+40,Y L CX+40,Y+25 L CX,Y+25" fill="none" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
|
||||
<text x="CX+45" y="Y+15" fill="#e2e8f0" font-size="8">process()</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Header width matches `header_w` from `layout-math.md` (e.g., 120 for N=4).
|
||||
- Header x = lane center − header_w/2.
|
||||
- Title `y = 60` when role is present, `y = 64` when title-only (centered in the 48px box).
|
||||
- Role `y = 78`, always `ts`.
|
||||
- Sentence case. "Auth server", not "Auth Server".
|
||||
|
||||
### Pill-shaped actor header (optional)
|
||||
|
||||
An actor that represents an **external participant** — `Human`, `User`, `Environment`, an input source, an output sink — can use a pill-shaped header (`rx = height / 2 = 24`) instead of the default rounded rect. The capsule shape visually separates "outside the system" from "inside the system" without using color.
|
||||
## Activation Bar
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="40" y="40" width="120" height="48" rx="24"/>
|
||||
<text class="th" x="100" y="64" text-anchor="middle" dominant-baseline="central">Human</text>
|
||||
</g>
|
||||
<rect x="CX-5" y="START_Y" width="10" height="H" rx="2" fill="rgba(8,51,68,0.6)" stroke="#22d3ee" stroke-width="1"/>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Only for external actors.** Internal services (Auth server, Client app, Resource server) keep the `rx="8"` rectangle. Mixing pills and rects in the same diagram is fine as long as the distinction tracks "external vs internal" — if every actor is a pill, drop them all back to rectangles.
|
||||
- **Height is still 48.** `rx` must equal `height / 2 = 24`. Don't adjust the height.
|
||||
- **No role subtitle** inside a pill. The capsule is for actors whose name is self-explanatory (Human, Environment). If you need a subtitle, you want a rectangle, not a pill.
|
||||
- **At most 2 pills per diagram.** More than that and the visual contrast between external and internal actors disappears.
|
||||
|
||||
## Lifelines
|
||||
|
||||
One `<line>` per actor, using `class="lifeline"`, from y=92 down to the tail below the last message.
|
||||
## Conditional / Loop Frames
|
||||
|
||||
```svg
|
||||
<line class="lifeline" x1="100" y1="92" x2="100" y2="540"/>
|
||||
<line class="lifeline" x1="260" y1="92" x2="260" y2="540"/>
|
||||
<line class="lifeline" x1="420" y1="92" x2="420" y2="540"/>
|
||||
<line class="lifeline" x1="580" y1="92" x2="580" y2="540"/>
|
||||
<!-- Frame boundary -->
|
||||
<rect x="X" y="Y" width="W" height="H" rx="4" fill="none" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<!-- Frame label tab -->
|
||||
<rect x="X" y="Y" width="50" height="18" rx="4" fill="rgba(30,41,59,0.8)" stroke="#64748b" stroke-width="1"/>
|
||||
<text x="X+25" y="Y+13" fill="#94a3b8" font-size="8" font-weight="600" text-anchor="middle">alt</text>
|
||||
<!-- Condition text -->
|
||||
<text x="X+60" y="Y+13" fill="#94a3b8" font-size="8" font-style="italic">[condition]</text>
|
||||
<!-- Divider line for else -->
|
||||
<line x1="X" y1="MID_Y" x2="X+W" y2="MID_Y" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="X+10" y="MID_Y+13" fill="#94a3b8" font-size="8" font-style="italic">[else]</text>
|
||||
```
|
||||
|
||||
All lifelines share the same y2. Compute once: `y2 = last_arrow_y + 24`.
|
||||
## Numbering
|
||||
|
||||
## Messages
|
||||
|
||||
Each message is a single `<line>` at its row's `arrow_y`, with a `ts` label above. The stroke class encodes the sender's actor ramp.
|
||||
For complex sequences (8+ messages), number each message:
|
||||
|
||||
```svg
|
||||
<!-- arrow row 0, arrow_y = 120 -->
|
||||
<text class="ts" x="180" y="110" text-anchor="middle">1. Click login</text>
|
||||
<line class="arr-gray" x1="106" y1="120" x2="254" y2="120" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- arrow row 1, arrow_y = 164, right-to-left -->
|
||||
<text class="ts" x="180" y="154" text-anchor="middle">3. Show consent screen</text>
|
||||
<line class="arr-purple" x1="254" y1="164" x2="106" y2="164" marker-end="url(#arrow)"/>
|
||||
<circle cx="FROM_CX-15" cy="Y" r="8" fill="rgba(59,130,246,0.3)" stroke="#60a5fa" stroke-width="1"/>
|
||||
<text x="FROM_CX-15" y="Y+3" fill="#60a5fa" font-size="7" font-weight="600" text-anchor="middle">1</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Label `y = arrow_y − 10`, centered between sender and receiver x.
|
||||
- Arrow `y1 = y2 = arrow_y`.
|
||||
- Arrow endpoints are offset ±6 from the lifeline x so the arrowhead doesn't touch the dashes:
|
||||
- Left-to-right: `x1 = sender_x + 6`, `x2 = receiver_x − 6`
|
||||
- Right-to-left: `x1 = sender_x − 6`, `x2 = receiver_x + 6`
|
||||
- Stroke class = `arr-{sender_ramp}` — `arr-gray` for User, `arr-teal` for Client app, etc. Never use `class="arr"` (the default mono-gray arrow class) on a sequence message.
|
||||
- Label text is sentence case with a numeric prefix: `"1. Click login"`, `"3. Show consent screen"`. Numbers make the reading order unambiguous when arrows cross.
|
||||
- Keep labels short — verify every label fits its lane span using the budget formula in `layout-math.md`.
|
||||
## Color Assignment
|
||||
|
||||
## Self-messages
|
||||
|
||||
A self-message stays on one actor's lifeline. Draw it as a small 16×24 rect on the lifeline, colored in that actor's ramp, with the label to the **left** (so it stays inside the diagram even for the rightmost lane).
|
||||
|
||||
```svg
|
||||
<!-- self-message on Resource server at arrow_y = 472 -->
|
||||
<rect class="c-blue" x="572" y="460" width="16" height="24" rx="4"/>
|
||||
<text class="ts" x="564" y="472" text-anchor="end" dominant-baseline="central">9. Validate token</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- `rect_x = lifeline_x − 8` (rect is 16 wide, centered on the lifeline).
|
||||
- `rect_y = arrow_y − 12` (rect is 24 tall, centered on the arrow row).
|
||||
- Label `x = rect_x − 8`, `text-anchor="end"`, `dominant-baseline="central"`, `class="ts"`.
|
||||
- Label still gets its numeric prefix (`"9. Validate token"`), following the overall message order.
|
||||
- Limit self-messages to 1–2 per diagram — more than that and the metaphor of "an actor talking to itself" stops reading clearly.
|
||||
|
||||
## Message frames
|
||||
|
||||
A **frame** is a dashed rounded rect that visually groups a contiguous run of messages under a single condition — *"Until tasks clear"*, *"Iterative research loop"*, *"Only if authenticated"*. It reads like a bracket around the enclosed messages, not like a new container. Anthropic's house-style sequence diagrams use frames for repeating or conditional sub-flows that don't warrant their own diagram.
|
||||
|
||||
```svg
|
||||
<rect x="90" y="108" width="490" height="112"
|
||||
rx="8" fill="none" stroke="#B4B2A9" stroke-width="1"
|
||||
stroke-dasharray="5 4"/>
|
||||
<text class="ts" x="100" y="120" text-anchor="start" dominant-baseline="central">Until tasks clear</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Dashed rounded rect**, `rx="8"`, 1px stroke in the muted gray (`#B4B2A9` light / `#5F5E5A` dark — match `.lifeline`). Use inline `stroke-dasharray="5 4"` rather than a new CSS class.
|
||||
- **Horizontal span**: `x = leftmost_lane_x − 20`, `width = rightmost_lane_x − leftmost_lane_x + 40`. The frame stretches 20px past the outermost lanes it covers, so the label has clear air at the corner.
|
||||
- **Vertical span**: `y = first_arrow_y − 12`, `height = last_arrow_y − first_arrow_y + 24`. Tight enough that the frame feels like a bracket, not a new section.
|
||||
- **Label position**: top-left inside the frame, at `(frame_x + 10, frame_y + 12)`, `class="ts"`, `text-anchor="start"`, `dominant-baseline="central"`. Keep the label ≤24 characters.
|
||||
- **No numeric prefix** on the frame label — numbering is for individual messages. The frame is meta.
|
||||
- **At most 2 frames per diagram**, and **frames never nest**. Two frames can sit one above the other, but they cannot contain each other. If you need nested scoping, split into two diagrams.
|
||||
- **Draw frames BEFORE the messages** in source order so the dashed border sits behind the arrows and labels.
|
||||
|
||||
When to use a frame:
|
||||
- A **loop** — "Until tests pass", "Until tasks clear", "Iterative research loop"
|
||||
- A **conditional block** — "Only if authenticated", "When cache miss"
|
||||
- A **named sub-protocol** — "Token refresh", "Retry with backoff"
|
||||
|
||||
Don't use a frame to decorate a single message, and don't use one to group messages that have nothing in common — frames are a scoping tool, not a visual group.
|
||||
|
||||
## Side note
|
||||
|
||||
A titled annotation for the protocol itself. Uses `class="box"` with a `th` title and an optional `ts` subtitle.
|
||||
|
||||
Top-left (for N ≤ 3):
|
||||
|
||||
```svg
|
||||
<rect class="box" x="40" y="40" width="240" height="48" rx="10"/>
|
||||
<text class="th" x="60" y="60" dominant-baseline="central">OAuth 2.0</text>
|
||||
<text class="ts" x="60" y="78" dominant-baseline="central">Authorization code flow</text>
|
||||
```
|
||||
|
||||
Bottom-left (for N ≥ 4, because the top row is filled with actor headers):
|
||||
|
||||
```svg
|
||||
<rect class="box" x="40" y="556" width="240" height="48" rx="10"/>
|
||||
<text class="th" x="60" y="576" dominant-baseline="central">OAuth 2.0</text>
|
||||
<text class="ts" x="60" y="594" dominant-baseline="central">Authorization code flow</text>
|
||||
```
|
||||
|
||||
Budget: title ≤24 chars, subtitle ≤32 chars. The box is 240 wide, padding 20 per side → usable width 200 → ~24 `th` chars or ~28 `ts` chars.
|
||||
|
||||
## Layout patterns
|
||||
|
||||
### 2-actor request/response (N=2)
|
||||
|
||||
User asks, server responds. Two lanes at x=185 and x=495. Typical message count: 2–4. Side note at top-left.
|
||||
|
||||
### 3-leg handshake (N=2, M=3)
|
||||
|
||||
Classic TCP. Same layout as above, three messages going SYN → SYN-ACK → ACK. Color the initiator arrows in one ramp and the responder arrows in another — the alternation reads as the handshake.
|
||||
|
||||
### 4-actor OAuth-style flow (N=4, M=8–10)
|
||||
|
||||
User, Client app, Auth server, Resource server. The canonical shape. Lane centers at 100, 260, 420, 580. Side note at bottom-left. One self-message on the Resource server for the "validate token" step.
|
||||
|
||||
### Async webhook (N=2 or 3)
|
||||
|
||||
Sender fires an event, receiver acknowledges later. Often has a gap in the middle that you can skip — no need to draw "wait 30 seconds" as a message; just put consecutive messages in the natural reading order.
|
||||
|
||||
## Parallel independent rounds
|
||||
|
||||
A sub-pattern for topics where **multiple independent call/response rounds** happen in sequence but share no lifeline state — each round is its own contained exchange, and the diagram's job is to contrast the structure of the rounds rather than track continuous state on a lifeline. Image #12 (Tool calling vs. Programmatic tool calling) is the canonical case.
|
||||
|
||||
**When to use.**
|
||||
|
||||
- Comparing tool-call vs. programmatic-script patterns where each round is an independent call/response.
|
||||
- Showing "N separate rounds of the same exchange" to communicate volume or variety.
|
||||
- The right half of a 2-up comparison where each side shows a different protocol style.
|
||||
|
||||
**When not to use.**
|
||||
|
||||
- There's genuine shared state between rounds (conversation history, pending requests) — that's a regular sequence with full lifelines.
|
||||
- There are only 2 actors and 2–4 messages — that's a regular 2-actor sequence.
|
||||
- The rounds are conditional ("if X then do round 2") — that's a frame on a regular sequence.
|
||||
|
||||
### Geometry — stacked rounds variant
|
||||
|
||||
Each round is a **row-sized mini-exchange** between a fixed source on the left and a round-specific action box on the right. Rounds stack vertically with no shared lifeline. See `layout-math.md` → "Parallel rounds geometry".
|
||||
|
||||
| Element | Coordinates |
|
||||
|--------------------|-------------------------------------------------|
|
||||
| Source actor | `x=60 y=120 w=120 h=60` (persistent on left) |
|
||||
| Round 1 action | `x=340 y=100 w=160 h=48 rx=6` |
|
||||
| Round 2 action | `x=340 y=180 w=160 h=48 rx=6` |
|
||||
| Round 3 action | `x=340 y=260 w=160 h=48 rx=6` |
|
||||
| Row pitch | 80 (round_y increases by 80 per round) |
|
||||
|
||||
The source actor is drawn **once** at the top-left, not repeated per round. Its box height is just tall enough to span the first round's arrow row, with no lifeline extending below it — the absence of a lifeline is the signal that the source isn't carrying state across rounds.
|
||||
|
||||
**Why no lifeline.** A lifeline would visually suggest that state persists; in this pattern each round is a fresh stateless call. The source box itself is the anchor — the left edge of every arrow starts at `source_right_edge`, not at a lifeline x.
|
||||
|
||||
**Arrow pairs per round.** Each round is two arrows:
|
||||
|
||||
```svg
|
||||
<!-- Round 1 -->
|
||||
<text class="ts" x="260" y="115" text-anchor="middle">1. Call tool A</text>
|
||||
<line class="arr-gray" x1="186" y1="124" x2="334" y2="124" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="260" y="145" text-anchor="middle">2. Response</text>
|
||||
<line class="arr-teal" x1="334" y1="134" x2="186" y2="134" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Round k's arrow y values: `call_y = 100 + 24 + (k-1) × 80`, `response_y = call_y + 10`. Action box for round k: `y = call_y − 24`.
|
||||
|
||||
**Action box colors.** Each round's action box **can** take a different ramp — this is the only place in a sequence diagram where color is allowed to cycle across messages. The reason: each round calls a *different tool* (file write, api call, db query), and the color communicates that variety. Use up to 3 different ramps; more than that and the reader can't remember what each ramp means.
|
||||
|
||||
Source actor ramp stays **constant** across all rounds (usually `c-gray` — "Claude" or "Agent").
|
||||
|
||||
### Script-wrapper variant
|
||||
|
||||
The right half of image #12 uses the same stacked-rounds shape but wraps **multiple action boxes inside a single tall script rect** that represents an inline program executing several tool calls. Uses the `script-icon` pattern from `glyphs.md`.
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|-----------------------------------------|
|
||||
| Source actor | `x=60 y=120 w=120 h=60` |
|
||||
| Script wrapper | `x=240 y=100 w=140 h=260 rx=6`, `c-gray`|
|
||||
| `{ }` label in wrapper | `(310, 124)`, `th` |
|
||||
| Divider line | `(256, 140) → (364, 140)`, `arr` |
|
||||
| Wrapped action 1 | `x=252 y=152 w=116 h=48 rx=4` |
|
||||
| Wrapped action 2 | `x=252 y=212 w=116 h=48 rx=4` |
|
||||
| Wrapped action 3 | `x=252 y=272 w=116 h=48 rx=4` |
|
||||
| External action | `x=440 y=200 w=160 h=48 rx=6` (optional)|
|
||||
|
||||
The script wrapper replaces the individual action boxes with a single container that contains N smaller colored bands. Only the **script wrapper itself** gets arrows from the source — the inner bands are decorative, showing what's inside the script without each needing its own call arrow.
|
||||
|
||||
```svg
|
||||
<!-- Single arrow from source to script -->
|
||||
<line class="arr-gray" x1="186" y1="230" x2="234" y2="230" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="210" y="220" text-anchor="middle">run script</text>
|
||||
```
|
||||
|
||||
If the script calls an external tool (image #12's right half: a `file_system` external box on the far right), draw a single arrow from the script's right edge to the external box at the script's middle y.
|
||||
|
||||
### Side-by-side comparison
|
||||
|
||||
The most common use of parallel rounds is inside a 2-up structural subsystem layout (`structural.md` → "Subsystem architecture pattern"): left container shows the stacked-rounds variant, right container shows the script-wrapper variant. Each sibling container is 315 wide, so halve the coordinates above:
|
||||
|
||||
- Source actor inside container: `x = container_x + 20, w=100 h=50`
|
||||
- Action box inside container: `x = container_x + 180, w=120 h=40`
|
||||
- Round pitch inside container: 64
|
||||
|
||||
See `structural.md` → "Rich interior for subsystem containers" for how this interior type registers.
|
||||
|
||||
### Rules
|
||||
|
||||
- **No lifelines.** If you draw lifelines, you're describing a regular stateful sequence, not parallel independent rounds. Drop them.
|
||||
- **Source actor appears once.** Never duplicate the source per round — that multiplies the visual noise and breaks the "same caller, different calls" intuition.
|
||||
- **Numeric prefixes still apply.** Within each round the two arrows are numbered (`1. Call tool A`, `2. Response`), and the numbering resets per round (or continues across rounds, at your choice — document it in the side note).
|
||||
- **≤3 rounds per diagram.** 4+ rounds runs out of vertical space and overwhelms the comparison. Use two diagrams if you need to show more.
|
||||
- **No frames around rounds.** A frame is a scoping bracket for a loop or a condition; parallel rounds are already independent, so a frame would be redundant.
|
||||
- **Action ramps cap at 3.** Each round can take a distinct ramp, but stop at 3 distinct colors.
|
||||
|
||||
## Rules of thumb
|
||||
|
||||
- **Max 4 actors (comfortable), max 6 actors (absolute ceiling).** 5 is allowed only when every title is ≤12 chars and every subtitle is omitted. 6 requires every title ≤9 chars (so it fits the 100-wide header rect) and no subtitles, and the diagram loses its 40px horizontal safe margin — see `layout-math.md` → "Swim-lane (sequence) layout" for the full N=5 and N=6 coordinate tables. Beyond 6, split into an overview diagram plus one detail diagram per sub-flow — a 7-actor sequence diagram is guaranteed to feel cramped no matter how carefully you pack the labels.
|
||||
- **6–10 messages is the sweet spot.** 4 is too sparse (use a flowchart); 12+ is too dense (split).
|
||||
- **One ramp per actor**, up to 4 total. The default `[gray, teal, purple, blue]` works for most protocols. Swap ramps only when a topic needs it.
|
||||
- **Number every message.** Even "obvious" single-step diagrams benefit from "1. ...". Readers scan numbers before they read words.
|
||||
- **Every arrow gets a label.** Sequence diagrams are the one place in this skill where arrow labels are mandatory, not optional — the label *is* the message, and without it the diagram says nothing.
|
||||
- **Sender's ramp colors the arrow.** Never use `class="arr"` (mono-gray) for a sequence message.
|
||||
- **Labels above the line, never on it.** Labels float 10px above the arrow at the row midpoint, `text-anchor="middle"`, `class="ts"`.
|
||||
- **No crossings within a single row.** Two messages at the same `arrow_y` collide — always move one to a new row.
|
||||
- **No arcs, no curves.** Flat horizontal lines only. Self-messages use a labeled rect, not a small arc.
|
||||
Assign each actor a distinct color from the palette. Use that color for:
|
||||
- Actor box stroke
|
||||
- Activation bar on that lifeline
|
||||
- Outgoing arrows from that actor (optional, for visual clarity in complex diagrams)
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
# Structural: Comparison Matrix
|
||||
|
||||
Load this file when the content is a **feature comparison**: "which of these databases supports X", side-by-side feature tables, ✓/✗ grids, or any "option × attribute" matrix.
|
||||
|
||||
Sub-pattern for side-by-side feature comparisons — *"which of these three databases supports transactions"*, *"which of these four frameworks has a migration path"*. The subject is a tabular matrix with **rows = features** and **columns = systems** (or the reverse). Structural diagram type is the right home because the matrix is really a structured grid of containers, just with a disciplined layout.
|
||||
|
||||
## When to use it
|
||||
|
||||
- The reader's question is *"which option has property X"*, and they'll compare across rows or columns.
|
||||
- You have 2–5 columns and 2–8 rows. Beyond 5 columns, split into two matrices. Beyond 8 rows, promote the most important 6 and drop the rest into prose.
|
||||
- Each cell's value fits in **one word or a ✓/✗ glyph**. If cells need sentences, this isn't a matrix — it's a comparison essay, which belongs in prose around the diagram.
|
||||
|
||||
## Structure
|
||||
|
||||
A comparison matrix is one header row (column labels) plus N body rows (attribute on the left, values in each column). The grid has no visible gridlines — the zebra-striped row backgrounds provide the horizontal separation, and the column alignment provides the vertical separation. Flat aesthetic, no table borders.
|
||||
|
||||
| Element | Width / coordinates |
|
||||
|--------------------|-------------------------------------------------------------|
|
||||
| Attribute column | 160 wide |
|
||||
| Value columns | `(600 − 160) / N` wide, equal to each other |
|
||||
| Row height | 36 (single-line `t` value) or 52 (two-line title + subtitle)|
|
||||
| Row gap | 0 (rows touch — the alternating fill creates the band) |
|
||||
| Header row height | 40 (slightly taller than body rows) |
|
||||
| Header text | `th` class, centered |
|
||||
| Body attribute | `t` class, left-anchored at `col_x + 12` |
|
||||
| Body value (text) | `t` class, centered in column |
|
||||
| Body value (glyph) | `status-circle-check` / `status-circle-x`, centered in column|
|
||||
|
||||
**Column count formula.** For 3 value columns: value_width = 440/3 ≈ 147. For 4: 110. For 5: 88. At 5 columns the value column barely fits *"Supported"*; past that, any label longer than 8 characters clips. Hard cap at 5 columns — if you have 6 systems to compare, split into *(A, B, C)* and *(D, E, F)* matrices.
|
||||
|
||||
## Alternating row fills
|
||||
|
||||
Zebra striping uses the `.row-alt` class from `svg-template.md` on every other body row. The header row uses `.box` with a slightly darker inline `stroke-width="1"` or the default `.box`:
|
||||
|
||||
```svg
|
||||
<!-- Header row -->
|
||||
<rect class="box" x="40" y="60" width="600" height="40" rx="6"/>
|
||||
|
||||
<!-- Body row 1 (default .box) -->
|
||||
<rect class="box" x="40" y="100" width="600" height="36"/>
|
||||
|
||||
<!-- Body row 2 (.row-alt) -->
|
||||
<rect class="row-alt" x="40" y="136" width="600" height="36"/>
|
||||
|
||||
<!-- Body row 3 (default .box) -->
|
||||
<rect class="box" x="40" y="172" width="600" height="36"/>
|
||||
```
|
||||
|
||||
Rounded corners (`rx="6"`) only on the *header* row and on the *last* body row — the middle rows are sharp-cornered so they stack seamlessly. Implementation trick: give the first body row `rx="0"` explicitly, and only round the bottom of the last row using the standard outer-wrapper approach, or accept square corners throughout for simplicity (which is also fine).
|
||||
|
||||
## Cell contents
|
||||
|
||||
Cells fall into three buckets:
|
||||
|
||||
- **Boolean** — use `status-circle-check` (c-green) for *yes*, `status-circle-x` (c-coral) for *no*, from `glyphs.md`. Centered in the cell. No extra text label. ✓/✗ is a visual shortcut that reads faster than the word.
|
||||
- **Short text** — 1–2 words like *"v2.1"*, *"Postgres"*, *"Yes"*, *"Optional"*. Use class `t`, `text-anchor="middle"`, centered at the cell's x-midpoint.
|
||||
- **Tier** — when the value is a level (*"Full"*, *"Partial"*, *"None"*), still use short text. Resist the urge to map tiers to colors — that re-categorizes the data and breaks the 2-ramp budget.
|
||||
|
||||
**No subtitle text inside a cell.** If a value *needs* a qualifier (*"Yes, since v3"*), the qualifier belongs in a footnote marker (*"Yes†"*) with the footnote written as `ts` text below the whole matrix. Don't stack two `ts` lines inside a 36px cell.
|
||||
|
||||
## Worked example — 3-system × 5-feature comparison
|
||||
|
||||
Request: *"Compare Postgres, MySQL, and SQLite on: transactions, JSON columns, full-text search, replication, on-disk encryption."*
|
||||
|
||||
Plan:
|
||||
- Header row: attribute column + 3 system columns. Value_width = 440/3 ≈ 147.
|
||||
- 5 body rows, alternating `.box` and `.row-alt`.
|
||||
- Feature names are 14px body text; system values are mostly ✓/✗ glyphs, with one short text ("Built-in"/"Plugin").
|
||||
- Single accent: none — the green/coral in the status circles already encodes meaning, and adding a ramp to the matrix would clash with the semantic greens.
|
||||
|
||||
```svg
|
||||
<svg ... viewBox="0 0 680 320">
|
||||
<!-- Header row -->
|
||||
<rect class="box" x="40" y="40" width="600" height="40" rx="6"/>
|
||||
<text class="th" x="120" y="64" text-anchor="middle">Feature</text>
|
||||
<text class="th" x="267" y="64" text-anchor="middle">Postgres</text>
|
||||
<text class="th" x="413" y="64" text-anchor="middle">MySQL</text>
|
||||
<text class="th" x="560" y="64" text-anchor="middle">SQLite</text>
|
||||
|
||||
<!-- Row 1: Transactions (all yes) -->
|
||||
<rect class="box" x="40" y="80" width="600" height="36"/>
|
||||
<text class="t" x="52" y="103">Transactions</text>
|
||||
<g transform="translate(255, 86)"><circle class="c-green" cx="12" cy="12" r="12"/><path class="arr-green" d="M6 12.5 L10.5 17 L18 8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g>
|
||||
<g transform="translate(401, 86)"><circle class="c-green" cx="12" cy="12" r="12"/><path class="arr-green" d="M6 12.5 L10.5 17 L18 8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g>
|
||||
<g transform="translate(548, 86)"><circle class="c-green" cx="12" cy="12" r="12"/><path class="arr-green" d="M6 12.5 L10.5 17 L18 8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g>
|
||||
|
||||
<!-- Row 2: JSON columns (all yes — alternating stripe) -->
|
||||
<rect class="row-alt" x="40" y="116" width="600" height="36"/>
|
||||
<text class="t" x="52" y="139">JSON columns</text>
|
||||
<!-- ✓ ✓ ✓ glyphs at same offsets -->
|
||||
|
||||
<!-- Row 3: Full-text search (text value varies) -->
|
||||
<rect class="box" x="40" y="152" width="600" height="36"/>
|
||||
<text class="t" x="52" y="175">Full-text search</text>
|
||||
<text class="t" x="267" y="175" text-anchor="middle">Built-in</text>
|
||||
<text class="t" x="413" y="175" text-anchor="middle">Built-in</text>
|
||||
<text class="t" x="560" y="175" text-anchor="middle">Plugin</text>
|
||||
|
||||
<!-- Row 4: Replication (SQLite no — alternating stripe) -->
|
||||
<rect class="row-alt" x="40" y="188" width="600" height="36"/>
|
||||
<text class="t" x="52" y="211">Replication</text>
|
||||
<!-- ✓ Postgres, ✓ MySQL, ✗ SQLite -->
|
||||
|
||||
<!-- Row 5: On-disk encryption (all ✗ except one) -->
|
||||
<rect class="box" x="40" y="224" width="600" height="36" rx="6"/>
|
||||
<text class="t" x="52" y="247">On-disk encryption</text>
|
||||
<!-- ✗ ✗ ✓ -->
|
||||
</svg>
|
||||
```
|
||||
|
||||
A 5-row × 4-column matrix at 36px row height lands the final body row's bottom at y=260, plus 40 bottom margin = viewBox height 300. Six rows pushes the bottom to 296 + 40 = 336 — still comfortable on the 680-wide canvas.
|
||||
|
||||
**Column x-midpoints (for 3 value columns).** Attribute column center: 120. Value column centers: 160 + 147/2 = 233.5 ≈ 234, 160 + 147 × 1.5 = 380.5 ≈ 381, 160 + 147 × 2.5 = 527.5 ≈ 528. The worked example above uses 267 / 413 / 560 because it centers the value columns on their actual bounding boxes after the 40px left canvas margin — recompute from `40 + 160 + (i + 0.5) × value_width` for your own grids.
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
# Structural: Network Topology
|
||||
|
||||
Load this file when the prompt asks about **network infrastructure**: "where do the wires go", "which zone is this device in", "DMZ / firewall / VPC topology", security zones, or device connectivity. The reader's question is *"what is connected to what, and across which boundary"*.
|
||||
|
||||
Sub-pattern for diagrams whose subject is **where the wires go** — which devices sit where, which security zones contain them, which links are wired vs wireless. The structural diagram type is the right home for this (devices are containers, zones are outer containers, cables are arrows), but topology drawings have some conventions of their own.
|
||||
|
||||
## Domain color conventions
|
||||
|
||||
When a structural or network diagram has 3+ component categories, these conventional ramp assignments help the reader parse component types at a glance. These are **suggestions**, not hard rules — the ≤2 ramp constraint from `design-system.md` still applies for simple diagrams with only 1–2 categories.
|
||||
|
||||
| Component domain | Suggested ramp | Rationale |
|
||||
|--------------------|---------------|----------------------------------------------|
|
||||
| Frontend / Client | teal | cool, user-facing |
|
||||
| Backend / API | green | processing, data transformation |
|
||||
| Database / Storage | purple | depth, persistence |
|
||||
| Cloud / Infra | amber | external boundary, warning-adjacent |
|
||||
| Security / Auth | coral | attention, trust boundary |
|
||||
| External / Generic | gray | neutral, not categorized |
|
||||
|
||||
When a diagram uses ≥3 of these, include a one-line legend strip at the bottom mapping colors to categories. When the diagram is simple enough to stay within 2 ramps, prefer the semantic pairing from `design-system.md` over these domain conventions.
|
||||
|
||||
## When to use it
|
||||
|
||||
- The reader's question is *"what is connected to what, and across which boundary"*, not *"what happens when a request arrives"* (that's a sequence diagram).
|
||||
- You can name ≤10 devices. More than that, split into *edge topology* and *internal topology* — no single network diagram should try to show the whole datacenter.
|
||||
- The zones matter as much as the devices. A network drawing without zones is just a bus or radial topology in disguise — use those sub-patterns instead.
|
||||
|
||||
## No custom device icons
|
||||
|
||||
baoyu is a flat-rect aesthetic. A router is a rounded rect labeled *Router*, a firewall is a rounded rect labeled *Firewall*, a cloud/Internet boundary is a rounded rect labeled *Internet*. **Do not draw a cylinder for a server, a trapezoid for a switch, a cloud silhouette for the Internet, or a shield-with-flames for a firewall.** The two-line node (type on top in `th`, name on bottom in `ts`) carries the same information with no aesthetic break:
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="120" y="160" width="160" height="56" rx="6"/>
|
||||
<text class="th" x="200" y="182" text-anchor="middle">Firewall</text>
|
||||
<text class="ts" x="200" y="202" text-anchor="middle">edge-fw-01</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
The device *type* is the title; the device's specific *name* or role (hostname, function) is the subtitle. If the diagram only has generic types without specific instances, a single-line 44-tall rect with the type as the title is fine.
|
||||
|
||||
## Zone containers
|
||||
|
||||
Use the existing dashed-container trick from `structural.md` → "Subsystem architecture pattern", with a top-left pill label for the zone name. One container per security zone:
|
||||
|
||||
| Zone | Typical label | Notes |
|
||||
|----------|---------------|-----------------------------------------------------------------|
|
||||
| Internet | *Internet* | Anything outside the org — clients, public DNS, SaaS endpoints |
|
||||
| DMZ | *DMZ* | Reverse proxies, WAFs, public web/API frontends |
|
||||
| Internal | *Internal* | Application servers, internal APIs, caches |
|
||||
| Data | *Data* | Databases, object stores, message brokers — isolate from DMZ |
|
||||
| Mgmt | *Management* | Bastion, monitoring, config — often spans other zones via ACLs |
|
||||
|
||||
Keep containers to **≤4 zones per diagram**. A 5-zone network is better split into two diagrams (*edge flow* + *internal flow*) than crammed into one 680-wide canvas.
|
||||
|
||||
The container rect uses `class="arr-alt"` — it inherits the dashed stroke from the template and dark-mode-safe coloring. The pill-shaped zone label sits in the top-left at `(container_x + 12, container_y − 8)` with class `ts` and an optional small background rect:
|
||||
|
||||
```svg
|
||||
<rect class="arr-alt" x="40" y="60" width="600" height="180" rx="12"/>
|
||||
<rect class="box" x="40" y="52" width="80" height="16" rx="8"/>
|
||||
<text class="ts" x="80" y="64" text-anchor="middle">DMZ</text>
|
||||
```
|
||||
|
||||
The label's background rect (`class="box"`) sits on top of the container's dashed top edge, masking it — the label reads as a tab attached to the zone rather than text floating inside it.
|
||||
|
||||
## Wired vs wireless
|
||||
|
||||
Two distinct link styles, both already in the template:
|
||||
|
||||
- **Wired link** — solid `class="arr"`. Optional `ts` label on the arrow midpoint carries the bandwidth or protocol (*1 Gbps*, *VPN*, *TLS*). Follows the normal 1–3 word arrow-label rule.
|
||||
- **Wireless link** — dashed `class="arr-alt"`. Same label rules. Wireless is also the right choice for *logical* links (VPN tunnels across the public internet, cross-AZ replication) where the link isn't a single physical cable.
|
||||
|
||||
**Two link styles means you need a legend.** Per `design-system.md`, any diagram that uses ≥2 arrow styles with distinct meaning must emit a one-line legend. For network topology:
|
||||
|
||||
```
|
||||
[──] Wired [- -] Wireless / VPN [■] DMZ [■] Internal
|
||||
```
|
||||
|
||||
Place the legend at the bottom of the canvas, 20px above the bottom edge, aligned with the subject matter above it.
|
||||
|
||||
## Security zone containers
|
||||
|
||||
When a network diagram includes explicit trust boundaries (security groups, VPNs, firewalls-as-perimeters), use a **coral-tinted dashed container** to distinguish security zones from structural zones. This gives the reader an immediate visual signal: gray dashed = organizational grouping, coral dashed = trust boundary.
|
||||
|
||||
```svg
|
||||
<rect x="60" y="100" width="560" height="160" rx="12" fill="none"
|
||||
stroke-dasharray="4 4" class="arr-coral"/>
|
||||
<rect class="c-coral" x="60" y="92" width="120" height="16" rx="8"/>
|
||||
<text class="ts" x="120" y="104" text-anchor="middle">Trust boundary</text>
|
||||
```
|
||||
|
||||
The coral container uses `class="arr-coral"` instead of `class="arr-alt"` — both are dashed, but `arr-coral` carries the semantic color. The pill label uses `class="c-coral"` for matching fill/stroke.
|
||||
|
||||
Typical security zone labels: *Security group*, *Trust boundary*, *VPN tunnel*, *Private subnet*, *Encrypted zone*, *DMZ* (when DMZ is drawn as a security boundary rather than an organizational tier).
|
||||
|
||||
When mixing structural zones (gray) and security zones (coral) in the same diagram, add a legend entry for the coral dash: `[- -] Trust boundary` alongside the structural zone entries.
|
||||
|
||||
## Tiered top-down layout
|
||||
|
||||
Network topology almost always reads top-down because the conventional mental model is *"traffic flows from the public internet down into the protected core"*. Lay out the zones vertically, one tier per row:
|
||||
|
||||
```
|
||||
Row 1 (y=60..96) Internet container
|
||||
Row 2 (y=116..180) Edge container (reverse proxy, firewall)
|
||||
Row 3 (y=200..264) Core container (app servers, services)
|
||||
Row 4 (y=284..348) Access / data container (databases, caches)
|
||||
```
|
||||
|
||||
Each tier is a separate dashed container. Links run **between** containers, not inside — intra-zone links are assumed (everything in the same zone can reach everything else in that zone via the switch fabric) and cluttering the diagram with them wastes space.
|
||||
|
||||
## Worked example — 3-tier network
|
||||
|
||||
Request: *"Draw a 3-tier network: public internet talking to a DMZ firewall, which routes to internal app servers, which talk to a database."*
|
||||
|
||||
Plan:
|
||||
- 3 zones: Internet, DMZ, Internal.
|
||||
- 4 devices: Client (Internet), Firewall (DMZ), App (Internal), DB (Internal).
|
||||
- All wired, so no legend needed — single arrow style.
|
||||
- Direction: top-down.
|
||||
- Colors: all `c-gray`. Devices aren't categorized by function strongly enough to warrant a ramp split.
|
||||
|
||||
```svg
|
||||
<svg ... viewBox="0 0 680 340">
|
||||
<!-- Zone: Internet -->
|
||||
<rect class="arr-alt" x="40" y="32" width="600" height="64" rx="12"/>
|
||||
<rect class="box" x="40" y="24" width="80" height="16" rx="8"/>
|
||||
<text class="ts" x="80" y="36" text-anchor="middle">Internet</text>
|
||||
<g class="c-gray">
|
||||
<rect x="260" y="50" width="160" height="36" rx="6"/>
|
||||
<text class="th" x="340" y="74" text-anchor="middle">Client</text>
|
||||
</g>
|
||||
|
||||
<!-- Internet → DMZ arrow -->
|
||||
<line x1="340" y1="86" x2="340" y2="118" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Zone: DMZ -->
|
||||
<rect class="arr-alt" x="40" y="118" width="600" height="80" rx="12"/>
|
||||
<rect class="box" x="40" y="110" width="60" height="16" rx="8"/>
|
||||
<text class="ts" x="70" y="122" text-anchor="middle">DMZ</text>
|
||||
<g class="c-gray">
|
||||
<rect x="240" y="140" width="200" height="52" rx="6"/>
|
||||
<text class="th" x="340" y="162" text-anchor="middle">Firewall</text>
|
||||
<text class="ts" x="340" y="180" text-anchor="middle">edge-fw-01</text>
|
||||
</g>
|
||||
|
||||
<!-- DMZ → Internal arrow -->
|
||||
<line x1="340" y1="192" x2="340" y2="220" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Zone: Internal -->
|
||||
<rect class="arr-alt" x="40" y="220" width="600" height="100" rx="12"/>
|
||||
<rect class="box" x="40" y="212" width="80" height="16" rx="8"/>
|
||||
<text class="ts" x="80" y="224" text-anchor="middle">Internal</text>
|
||||
<g class="c-gray">
|
||||
<rect x="120" y="244" width="180" height="52" rx="6"/>
|
||||
<text class="th" x="210" y="266" text-anchor="middle">App server</text>
|
||||
<text class="ts" x="210" y="284" text-anchor="middle">web-api-*</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="380" y="244" width="180" height="52" rx="6"/>
|
||||
<text class="th" x="470" y="266" text-anchor="middle">Database</text>
|
||||
<text class="ts" x="470" y="284" text-anchor="middle">postgres-primary</text>
|
||||
</g>
|
||||
<!-- App → DB wired link -->
|
||||
<line x1="300" y1="270" x2="380" y2="270" class="arr" marker-end="url(#arrow)"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
Notice that the diagram shows inter-zone links (Client → Firewall → App) *and* one intra-zone link (App → DB). The intra-zone App → DB link is drawn because it's the whole point of the diagram — otherwise the reader wouldn't know the DB is in the *Internal* zone, not a fourth *Data* zone. When you leave intra-zone links out, make sure the device names alone answer *"who talks to whom"*.
|
||||
|
|
@ -1,827 +1,100 @@
|
|||
# Structural Diagram
|
||||
# Structural Diagram Layout
|
||||
|
||||
For concepts where physical or logical containment matters — "things inside other things". A large outer container with smaller regions inside it, labeled, and optionally connected by arrows or external inputs and outputs.
|
||||
Covers: class diagrams, ER diagrams, component diagrams, package diagrams, org charts.
|
||||
|
||||
## When to use
|
||||
## Class Diagram
|
||||
|
||||
- "How is X organised" / "what's the architecture" / "where does Y live"
|
||||
- File systems (blocks inside inodes inside partitions)
|
||||
- Cloud topologies (instance inside subnet inside VPC)
|
||||
- Biological containment (organelles inside cells)
|
||||
- Service architectures with clear boundaries (microservices inside a service mesh inside a cluster)
|
||||
- CPU cache hierarchies (L1 inside core, L2 shared between cores)
|
||||
|
||||
**When not to use:** if the explanation is about *what happens over time*, reach for a flowchart. Containment diagrams answer "where", flowcharts answer "what order".
|
||||
|
||||
## Container rules
|
||||
|
||||
### Outer container
|
||||
|
||||
- Large rounded rect
|
||||
- `rx="20"` (or up to 24 if the diagram needs a softer feel)
|
||||
- Light fill (stop 50) and 0.5px stroke (stop 600) — both come automatically from the `c-{ramp}` class
|
||||
- Label sits at the top-center inside the container, 20–30px down from the top edge, as a `th` title
|
||||
- A short subtitle can sit 18px below the title, as a `ts`
|
||||
|
||||
Example frame:
|
||||
### Class Box (3-compartment)
|
||||
|
||||
```svg
|
||||
<g class="c-green">
|
||||
<rect x="60" y="40" width="560" height="300" rx="20"/>
|
||||
<text class="th" x="340" y="72" text-anchor="middle" dominant-baseline="central">VPC</text>
|
||||
<text class="ts" x="340" y="92" text-anchor="middle" dominant-baseline="central">us-east-1</text>
|
||||
<g transform="translate(X, Y)">
|
||||
<!-- Mask -->
|
||||
<rect width="180" height="120" rx="6" fill="#0f172a"/>
|
||||
<!-- Box -->
|
||||
<rect width="180" height="120" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
|
||||
<!-- Class name compartment -->
|
||||
<text x="90" y="24" fill="white" font-size="11" font-weight="700" text-anchor="middle">ClassName</text>
|
||||
<!-- Divider 1 -->
|
||||
<line x1="0" y1="35" x2="180" y2="35" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
|
||||
<!-- Attributes -->
|
||||
<text x="10" y="52" fill="#94a3b8" font-size="8">- id: int</text>
|
||||
<text x="10" y="64" fill="#94a3b8" font-size="8">- name: string</text>
|
||||
<!-- Divider 2 -->
|
||||
<line x1="0" y1="75" x2="180" y2="75" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
|
||||
<!-- Methods -->
|
||||
<text x="10" y="92" fill="#94a3b8" font-size="8">+ getName(): string</text>
|
||||
<text x="10" y="104" fill="#94a3b8" font-size="8">+ setName(s: string)</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
### Inner regions
|
||||
For abstract classes, italicize the class name. For interfaces, add `«interface»` above the name in smaller font.
|
||||
|
||||
- Medium rounded rects, `rx="10"` (8–12 range)
|
||||
- Different color ramp from the parent (see "Color pairing" below)
|
||||
- 20px minimum padding between the inner region edge and the container edge on all sides
|
||||
- 16px minimum gap between adjacent inner regions
|
||||
- Same dimensions as a flowchart node: 44px tall for single-line, 56px for two-line, or larger rects (100+ tall) to represent regions of space
|
||||
### Relationship Lines
|
||||
|
||||
### Nesting depth
|
||||
| Relationship | Line Style | Arrow/End |
|
||||
|-------------|------------|-----------|
|
||||
| Inheritance | Solid | Empty triangle (▷) pointing to parent |
|
||||
| Implementation | Dashed | Empty triangle pointing to interface |
|
||||
| Composition | Solid | Filled diamond (◆) at owner end |
|
||||
| Aggregation | Solid | Empty diamond (◇) at owner end |
|
||||
| Dependency | Dashed | Open arrowhead at dependency target |
|
||||
| Association | Solid | Open arrowhead or none |
|
||||
|
||||
Maximum **2–3 levels**. Deeper nesting gets unreadable at 680px width. If you need four levels, split into two diagrams: the outer two levels in one diagram, then a detail diagram zooming into one of the inner regions.
|
||||
|
||||
## Color pairing
|
||||
|
||||
When you nest containers, pick ramps that *relate* rather than clash. A library branch + circulation desk is clearly two parts of one building, so use related cool ramps (green outer, teal inner). A library branch + café is two functionally different spaces, so use one cool and one warm (green + amber).
|
||||
|
||||
**Rule of thumb**:
|
||||
- Same-category nesting → related cool ramps (green + teal, blue + teal, purple + pink)
|
||||
- Different-category nesting → one cool + one warm (green + amber, blue + coral)
|
||||
- Structural/neutral containers → gray + any accent
|
||||
|
||||
The key point: don't reuse the same ramp on parent and child. The template's `c-{ramp}` classes resolve to fixed fill/stroke stops, so a nested `c-blue` inside a parent `c-blue` gives identical fills — the hierarchy flattens visually.
|
||||
|
||||
## Layout
|
||||
|
||||
Inner regions sit **side by side** inside the container, not stacked unless you have a tall narrow container (e.g., a CPU cache hierarchy with L1 on top of L2 on top of L3). Horizontal layout is the default because the viewBox is 680 wide — you have room for 2–3 regions across but only 1–2 stacked vertically before it gets too tall.
|
||||
|
||||
Example layout math for 2 inner regions inside a 560-wide container:
|
||||
|
||||
```
|
||||
container: x=60, width=560
|
||||
inner padding: 20 on each side → inner safe area is x=80 to x=600, width 520
|
||||
gap between regions: 20
|
||||
region width: (520 - 20) / 2 = 250
|
||||
region A: x=80, width=250
|
||||
region B: x=350, width=250
|
||||
```
|
||||
|
||||
For 3 inner regions:
|
||||
|
||||
```
|
||||
inner safe area: width 520
|
||||
gaps: 2 × 20 = 40
|
||||
region width: (520 - 40) / 3 ≈ 160
|
||||
regions at x = 80, 260, 440 (all width 160)
|
||||
```
|
||||
|
||||
## External inputs and outputs
|
||||
|
||||
Arrows entering or leaving the container represent external data, requests, or physical flows. They sit outside the container with arrows pointing in or out:
|
||||
**Markers:**
|
||||
|
||||
```svg
|
||||
<text class="ts" x="40" y="185" text-anchor="middle">Request</text>
|
||||
<line x1="40" y1="200" x2="58" y2="200" class="arr" marker-end="url(#arrow)"/>
|
||||
<!-- Inheritance triangle -->
|
||||
<marker id="inherit" markerWidth="12" markerHeight="10" refX="12" refY="5" orient="auto">
|
||||
<polygon points="0 0, 12 5, 0 10" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
</marker>
|
||||
|
||||
<!-- Composition diamond -->
|
||||
<marker id="composition" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
|
||||
<polygon points="0 4, 6 0, 12 4, 6 8" fill="#94a3b8"/>
|
||||
</marker>
|
||||
|
||||
<!-- Aggregation diamond -->
|
||||
<marker id="aggregation" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
|
||||
<polygon points="0 4, 6 0, 12 4, 6 8" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
</marker>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- External labels are ≤2 words ("Request", "Output", "Cold water", "Sunlight"). Long labels belong in prose around the diagram.
|
||||
- Place the text label directly above the arrow, `text-anchor="middle"` aligned with the arrow's horizontal center.
|
||||
- Leave 2px of gap between the arrow endpoint and the container edge.
|
||||
- If you have multiple inputs/outputs, stack them vertically at the same x coordinate with 40–60px spacing between them.
|
||||
### Cardinality Labels
|
||||
|
||||
## Internal arrows between regions
|
||||
|
||||
Sometimes two inner regions talk to each other. Draw an arrow between them with a short text label centered above or below:
|
||||
Place at each end of the relationship line, offset 5-8px from the box edge:
|
||||
|
||||
```svg
|
||||
<text class="ts" x="305" y="175" text-anchor="middle">Books</text>
|
||||
<line x1="330" y1="185" x2="370" y2="185" class="arr" marker-end="url(#arrow)"/>
|
||||
<text x="X" y="Y" fill="#94a3b8" font-size="8">1..*</text>
|
||||
```
|
||||
|
||||
Keep these labels to 1–2 words. If the arrow's meaning is obvious from the region names (e.g., "Request Router" → "Auth Service"), skip the label entirely.
|
||||
## ER Diagram
|
||||
|
||||
## What goes inside a region
|
||||
|
||||
Text only. A region is not a container for another flowchart — if you find yourself drawing a nested flowchart inside a region, you've reached the nesting limit. Split it into a separate diagram.
|
||||
|
||||
Each region has:
|
||||
- A title (`th` class, 14px bold)
|
||||
- An optional subtitle (`ts` class, 12px, ≤5 words) describing what happens there
|
||||
- Nothing else. No icons, no illustrations, no mini-boxes inside
|
||||
|
||||
**Exception — subsystem architecture pattern.** When the topic is "two or three parallel subsystems cooperating" (see "Subsystem architecture pattern" below), each sibling container *may* contain a short internal flow of up to 5 flowchart-style nodes with arrows between them. This is the only structural layout where a region holds more than text, and the 5-node cap is a hard ceiling. If you need more than 5 nodes inside a region, the pattern isn't the right shape — split the inner flow into its own flowchart diagram and keep the structural diagram at the containment level.
|
||||
|
||||
## Worked example
|
||||
|
||||
Request: "AWS VPC with a web tier and a database tier"
|
||||
|
||||
Plan:
|
||||
- Outer container: VPC (green, subtitle "us-east-1")
|
||||
- Two inner regions side by side: "Public subnet" (teal, subtitle "Web servers") and "Private subnet" (blue, subtitle "Database")
|
||||
- External input arrow: "Internet" → public subnet
|
||||
- Internal arrow: public subnet → private subnet, labeled "SQL"
|
||||
|
||||
Layout:
|
||||
- Container at x=80, y=40, width=540, height=280 → `rx="20"`
|
||||
- Public subnet at x=120, y=140, width=220, height=140 → `rx="10"` (inside container with 40px padding from left, 60px below title)
|
||||
- Private subnet at x=380, y=140, width=220, height=140
|
||||
- Container title at y=72, subtitle at y=92
|
||||
- External "Internet" arrow: from (48, 210) to (78, 210) pointing right, with text at (48, 196)
|
||||
- Internal "SQL" arrow: from (350, 210) to (378, 210), with text at (364, 196)
|
||||
|
||||
viewBox: max_y = 40 + 280 = 320 → H = 340
|
||||
|
||||
## Full architecture layout
|
||||
|
||||
A richer variant for topics like "microservices architecture", "Kubernetes cluster", "cloud topology", or any system design where the reader expects to see **technology choices, data stores, messaging, and client types** — not just generic labeled boxes. This is the most information-dense structural layout in the skill, routinely containing 12–20 named elements and using up to 4 color ramps (see `design-system.md` rule 9).
|
||||
|
||||
### When to use
|
||||
|
||||
- "Microservices architecture" / "system architecture" / "platform design"
|
||||
- Any structural prompt where the user's seed is a broad system name and the reader expects specific technologies
|
||||
- The reader needs to learn **what** is in the system (tech stack, ports, protocols), not just **how it's organized**
|
||||
|
||||
### When not to use
|
||||
|
||||
- Simple containment (VPC with two subnets) — use the basic structural pattern above
|
||||
- Two subsystems cooperating — use the subsystem architecture pattern below
|
||||
- The user provided detailed source material that already lists every component — follow the source instead of enriching
|
||||
|
||||
### Layout structure
|
||||
|
||||
The canonical full architecture layout has **5 horizontal tiers** inside an outer container, plus external clients and an optional summary panel below:
|
||||
|
||||
```
|
||||
Tier 0 (external): Client boxes — outside the container, top-left
|
||||
Tier 1: API Gateway / Ingress — wide box spanning the top
|
||||
Tier 2: Microservices row — 3–4 service boxes
|
||||
Tier 2.5: Message bus pills — small labeled connectors between services
|
||||
Tier 3: Data stores row — database boxes aligned under their owning services
|
||||
Tier 4 (optional): Shared infrastructure — service discovery, config, monitoring
|
||||
```
|
||||
|
||||
An auth service sits outside or beside the gateway when it's a cross-cutting concern.
|
||||
|
||||
### Color budget (4 ramps)
|
||||
|
||||
| Category | Ramp | Used on |
|
||||
|-----------------|---------|------------------------------------------------------|
|
||||
| Services | teal | Microservice boxes, auth service |
|
||||
| Databases | purple | PostgreSQL, MongoDB, Elasticsearch, Redis |
|
||||
| Gateway/Ingress | coral | API Gateway box |
|
||||
| Message bus | amber | Kafka, RabbitMQ, Event Bus connector pills |
|
||||
| Clients | gray | Web app, Mobile app (neutral — not part of the system)|
|
||||
| Infrastructure | gray | Service discovery, config & secrets (neutral) |
|
||||
|
||||
A **legend strip** is mandatory — place it inside the container at the bottom or just below it.
|
||||
|
||||
### Geometry
|
||||
|
||||
Starting-point coordinates for a typical 3-service architecture. **Recompute** per diagram using `layout-math.md` formulas — the values below assume 3 services, 3 databases, and standard 56px-tall boxes. Adding a 4th service column or extra tiers requires recalculating x/w/gap to fit within the 600px usable width.
|
||||
|
||||
Standard layout at viewBox `680 × H`:
|
||||
|
||||
```
|
||||
Element x y w h rx
|
||||
──────────────────────── ──── ──── ──── ─── ────
|
||||
Outer container 40 80 600 var 20 c-blue (or c-gray)
|
||||
Title 60 50 — — — .title
|
||||
Subtitle 60 72 — — — .ts
|
||||
|
||||
Client boxes (external)
|
||||
Web App 40 20 140 56 6 c-gray
|
||||
Mobile App 40 var 140 56 6 c-gray
|
||||
|
||||
API Gateway 200 120 280 80 6 c-coral
|
||||
(title + 2–3 subtitle lines: tech, responsibilities, port)
|
||||
|
||||
Auth Service 40 var 140 56 6 c-teal
|
||||
(sits left of gateway or below clients)
|
||||
|
||||
Services row (inside container)
|
||||
Service 1 100 250 140 56 6 c-teal
|
||||
Service 2 270 250 140 56 6 c-teal
|
||||
Service 3 440 250 140 56 6 c-teal
|
||||
(or 4 services: w=120, gap=16)
|
||||
|
||||
Message bus pills
|
||||
(small 90×24 rx=12 amber pills between services, y≈320)
|
||||
|
||||
Data stores row
|
||||
DB 1 100 370 140 56 6 c-purple
|
||||
DB 2 270 370 140 56 6 c-purple
|
||||
DB 3 440 370 140 56 6 c-purple
|
||||
|
||||
Shared infrastructure (optional)
|
||||
Service discovery 140 460 160 44 6 c-gray
|
||||
Config & secrets 380 460 160 44 6 c-gray
|
||||
|
||||
Legend strip 100 var — — — .ts + swatches
|
||||
```
|
||||
|
||||
Adjust y coordinates to maintain ≥40px vertical gaps between tiers. viewBox H is typically 560–700 depending on tier count.
|
||||
|
||||
### Label enrichment
|
||||
|
||||
Every box in a full architecture diagram carries **technology-specific subtitles**:
|
||||
|
||||
| Component | Title (th) | Subtitle (ts) |
|
||||
|-----------------|--------------------|-------------------------|
|
||||
| Client | Web App | React SPA |
|
||||
| Client | Mobile App | iOS/Android |
|
||||
| Gateway | API Gateway | Kong / Nginx |
|
||||
| | | Rate limiting |
|
||||
| | | :443 |
|
||||
| Auth | Auth Service | OAuth 2.0 / JWT |
|
||||
| Service | User Service | Go :8081 |
|
||||
| Service | Order Service | Java :8082 |
|
||||
| Message bus | Kafka / RabbitMQ | (pill label only) |
|
||||
| Database | PostgreSQL | Users DB |
|
||||
| Database | MongoDB | Orders DB |
|
||||
| Database | Redis | Cache / Queue |
|
||||
| Infrastructure | Service discovery | (single-line) |
|
||||
|
||||
The gateway box is taller (h=80) to hold 3 subtitle lines. Service and database boxes use standard 2-line layout (h=56). Message bus elements are small pills (h=24).
|
||||
|
||||
### Message bus connector pills
|
||||
|
||||
Small rounded-rect pills sitting on the arrows between services, styled with `c-amber`:
|
||||
Similar to class diagrams but:
|
||||
- Use 2-compartment boxes (entity name + attributes)
|
||||
- Mark primary keys with `PK` prefix and bold
|
||||
- Mark foreign keys with `FK` prefix
|
||||
- Relationship lines use crow's foot notation:
|
||||
|
||||
```svg
|
||||
<g class="c-amber">
|
||||
<rect x="225" y="316" width="90" height="24" rx="12"/>
|
||||
<text class="ts" x="270" y="328" text-anchor="middle" dominant-baseline="central">Event Bus</text>
|
||||
</g>
|
||||
<!-- One end (single line) -->
|
||||
<line x1="X1" y1="Y" x2="X1+15" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<!-- Many end (crow's foot) -->
|
||||
<line x1="X2-15" y1="Y-6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<line x1="X2-15" y1="Y+6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<line x1="X2-15" y1="Y" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
```
|
||||
|
||||
Place pills at the midpoint of the arrow between two services. The arrow is split into two segments with the pill in between.
|
||||
## Org Chart
|
||||
|
||||
### Summary panel (optional)
|
||||
- Top-down tree layout
|
||||
- Root at top center
|
||||
- Each level evenly spaced (100-120px vertical gap)
|
||||
- Siblings evenly distributed horizontally
|
||||
- Connection lines: vertical from parent bottom center to horizontal bar, then vertical down to each child top center
|
||||
- Use color to indicate departments or hierarchy levels
|
||||
|
||||
For diagrams with ≥10 elements, add a summary panel below the outer container. Three columns of bullet points summarizing key architecture principles:
|
||||
## Layout Tips
|
||||
|
||||
```
|
||||
┌─────────────────┬─────────────────┬──────────────────┐
|
||||
│ • Client Apps │ • Microservices │ • Infrastructure │
|
||||
│ - React SPA │ - Polyglot │ - Kubernetes │
|
||||
│ - iOS/Android │ - Independent │ - Kong Gateway │
|
||||
│ - Unified API │ - Event-driven│ - Kafka │
|
||||
│ - JWT auth │ - DB per svc │ - Prometheus │
|
||||
└─────────────────┴─────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
Each column is a `c-gray` box with a colored bullet (matching its category ramp) as the header indicator. Place at y = container_bottom + 40, spanning the full 600px usable width as three equal boxes (w=186, gap=21, total=186×3+21×2=600).
|
||||
|
||||
### Footer caption
|
||||
|
||||
Add a `.caption` footer below the summary panel: the architecture name + design philosophy in one line (e.g., "Microservices Architecture · Domain-driven design"). Centered at x=340, y = panel_bottom + 30.
|
||||
|
||||
### Worked example — Microservices + K8s + API Gateway
|
||||
|
||||
Plan (expanded from a seed prompt "Microservices architecture"):
|
||||
|
||||
```
|
||||
Clients: Web App (React SPA), Mobile App (iOS/Android)
|
||||
Gateway: API Gateway (Kong/Nginx, Rate Limiting, Auth/Routing, :443)
|
||||
Auth: Auth Service (OAuth 2.0 / JWT)
|
||||
Services: User Service (Go :8081), Order Service (Java :8082),
|
||||
Product Service (Python :8083), Notification Svc (Node.js :8084)
|
||||
Message bus: Kafka/RabbitMQ, Event Bus (×2)
|
||||
Data stores: PostgreSQL (Users DB), MongoDB (Orders DB),
|
||||
Elasticsearch (Products), Redis (Cache/Queue)
|
||||
Container: Kubernetes Cluster (dashed border)
|
||||
Legend: Service, Database, Gateway, Message Bus
|
||||
Summary: 3 columns (Client Applications, Microservices, Infrastructure)
|
||||
Footer: "Microservices Architecture · Domain-driven design"
|
||||
Color budget: teal (services), purple (databases), coral (gateway), amber (bus)
|
||||
Named elements: 18
|
||||
```
|
||||
|
||||
viewBox: `680 × 780`. The diagram is tall — that's expected for a full architecture layout.
|
||||
|
||||
## Subsystem architecture pattern
|
||||
|
||||
A variant for topics that have **two or three parallel subsystems cooperating** — the canonical shape is "two sibling dashed-border containers, each holding a short internal flow, with a labeled cross-system arrow linking them". Unlike the default structural diagram (outer container + inner regions), this pattern places the subsystems *side by side at the top level* and lets each one carry its own mini-flowchart.
|
||||
|
||||
**When to use**:
|
||||
|
||||
- "Pi session + background analyzer" — foreground conversation and a separate analyzer running in parallel
|
||||
- "Prompt engineering vs. context engineering" — two frameworks shown side by side with their internal loops
|
||||
- "Producer subsystem + consumer subsystem" — a queue-like split where each side has its own internal pipeline
|
||||
- "Foreground + background" workflows — any time you're comparing two pipelines that share data across a boundary
|
||||
|
||||
**When not to use**:
|
||||
|
||||
- Containment — one thing is *inside* another. Use the default outer-container pattern instead.
|
||||
- A single pipeline with no peer system. That's just a flowchart.
|
||||
- More than 3 siblings. Three is the hard ceiling; four already blows the horizontal budget.
|
||||
|
||||
### Sibling containers
|
||||
|
||||
Each sibling is a **dashed-border** rounded rect. Use **inline `stroke-dasharray="4 4"`** on the rect rather than a CSS class — dashed is a one-off container style, not a reusable shape property, so the template's class list stays stable. The stroke color still comes from a `c-{ramp}` class on the wrapping `<g>` so dark mode works.
|
||||
|
||||
```svg
|
||||
<g class="c-gray">
|
||||
<rect x="20" y="40" width="315" height="260" rx="20"
|
||||
fill="none" stroke-dasharray="4 4"/>
|
||||
<text class="th" x="40" y="64" dominant-baseline="central">Pi session</text>
|
||||
<text class="ts" x="40" y="82" dominant-baseline="central">Foreground conversation</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Fill is `none`.** The dashed border reads as "this is a schematic region", not "this is a solid box". A filled sibling container fights with its internal nodes for the reader's attention.
|
||||
- **Stroke comes from the `c-{ramp}` class.** Dark-mode handling is automatic.
|
||||
- **Title at top-left**, not top-center — siblings compete for the top-center axis, so pull each title to the inside corner. Title at `(container_x + 20, container_y + 24)`, `text-anchor="start"`.
|
||||
- **Optional subtitle** (`ts`) directly below the title at `y = title_y + 18`, also left-anchored. ≤5 words.
|
||||
- **Pick ramps so the siblings feel related but distinct.** Same-category (foreground + background → gray + teal). Different-category (prompt engineering + context engineering → purple + teal). Never use the same ramp on both siblings.
|
||||
|
||||
### Layout math (2-up)
|
||||
|
||||
See `layout-math.md` → "Sibling subsystem containers (2-up)" for the full formula. The standard geometry:
|
||||
|
||||
```
|
||||
container A x=20, y=40, w=315, h=260, rx=20
|
||||
container B x=345, y=40, w=315, h=260, rx=20
|
||||
gap = 10 px
|
||||
interior A x=40 to x=315 (width 275)
|
||||
interior B x=365 to x=640 (width 275)
|
||||
```
|
||||
|
||||
### Internal nodes inside a sibling
|
||||
|
||||
A sibling container may hold **up to 5 flowchart-style nodes** with arrows between them — this is the explicit exception to the "region is text only" rule in the "What goes inside a region" section above. Use the standard flowchart node templates (`c-{ramp}` rect 44 or 56 tall). Center each node at the interior x (`container_x + container_w/2`) unless there's a specific left-to-right sub-flow.
|
||||
|
||||
Keep the internal flow **vertical** inside each sibling. The 275px interior width isn't enough for a horizontal pipeline of 3+ nodes with proper breathing room, and a vertical stack gives the cross-system arrow somewhere clean to land.
|
||||
|
||||
Rules:
|
||||
- **≤5 nodes per sibling**. Hard cap. More than that and the pattern isn't the right shape.
|
||||
- **Same title color as the container** — the internal nodes inherit the sibling's ramp unless one specific node deserves a second accent color.
|
||||
- **Node width ≤ 235** (interior 275 minus 20px padding each side), typically 180.
|
||||
- **No nested sibling containers.** A sibling cannot itself be a subsystem architecture diagram.
|
||||
|
||||
### Cross-system arrows
|
||||
|
||||
The point of the pattern is usually the **labeled arrows** crossing between siblings. Draw them across the 10px container gap. Unlike the external-input arrows at the top of this file, cross-system arrows get a label above (not beside) and use the solid `.arr` class.
|
||||
|
||||
```svg
|
||||
<text class="ts" x="340" y="136" text-anchor="middle">Event</text>
|
||||
<line x1="315" y1="146" x2="345" y2="146" class="arr" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- **Label ≤3 words.** "Event", "Session context", "Summary write-back". Anything longer belongs in prose.
|
||||
- **Label position**: centered horizontally at the gap midpoint (`x=340` for the 20-345 layout), `y = arrow_y − 10`, `text-anchor="middle"`, `class="ts"`.
|
||||
- **Arrow row**: `y` matches the row of the source and target nodes so the line is flat. If the source is on row 1 and the target on row 2, pick the row closer to where the reader's eye lands (usually the target).
|
||||
- **Bidirectional exchanges**: draw two separate single-headed arrows at slightly offset y values (`y` and `y + 12`), each with its own label. Never use a double-headed marker — the asymmetry of the two labels *is* the information.
|
||||
|
||||
### Worked example — Pi session + Background analyzer
|
||||
|
||||
Two siblings: a foreground conversation (`Pi session`, gray) and a background analyzer (`Background analyzer`, teal). The session emits events to the analyzer; the analyzer writes summaries back into the session's memory.
|
||||
|
||||
Plan:
|
||||
- Container A "Pi session" at x=20, w=315, h=260
|
||||
- Container B "Background analyzer" at x=345, w=315, h=260
|
||||
- Three internal nodes in each sibling, stacked vertically
|
||||
- Two cross-system arrows: row 1 left→right labeled "Event", row 3 right→left labeled "Summary"
|
||||
- viewBox: `max_y = 40 + 260 = 300` → H = 320
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 320" font-family="...">
|
||||
<style>...</style>
|
||||
<defs><marker id="arrow" .../></defs>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="20" y="40" width="315" height="260" rx="20"
|
||||
fill="none" stroke-dasharray="4 4"/>
|
||||
<text class="th" x="40" y="64" dominant-baseline="central">Pi session</text>
|
||||
<text class="ts" x="40" y="82" dominant-baseline="central">Foreground conversation</text>
|
||||
</g>
|
||||
|
||||
<g class="c-teal">
|
||||
<rect x="345" y="40" width="315" height="260" rx="20"
|
||||
fill="none" stroke-dasharray="4 4"/>
|
||||
<text class="th" x="365" y="64" dominant-baseline="central">Background analyzer</text>
|
||||
<text class="ts" x="365" y="82" dominant-baseline="central">Runs in parallel</text>
|
||||
</g>
|
||||
|
||||
<g class="c-gray">
|
||||
<rect x="87" y="118" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="177" y="140" text-anchor="middle" dominant-baseline="central">User turn</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="87" y="180" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="177" y="202" text-anchor="middle" dominant-baseline="central">Pi responds</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="87" y="242" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="177" y="264" text-anchor="middle" dominant-baseline="central">Memory updated</text>
|
||||
</g>
|
||||
|
||||
<g class="c-teal">
|
||||
<rect x="412" y="118" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="502" y="140" text-anchor="middle" dominant-baseline="central">Analyze turn</text>
|
||||
</g>
|
||||
<g class="c-teal">
|
||||
<rect x="412" y="180" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="502" y="202" text-anchor="middle" dominant-baseline="central">Write summary</text>
|
||||
</g>
|
||||
<g class="c-teal">
|
||||
<rect x="412" y="242" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="502" y="264" text-anchor="middle" dominant-baseline="central">Push to memory</text>
|
||||
</g>
|
||||
|
||||
<line x1="177" y1="162" x2="177" y2="180" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="177" y1="224" x2="177" y2="242" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="502" y1="162" x2="502" y2="180" class="arr" marker-end="url(#arrow)"/>
|
||||
<line x1="502" y1="224" x2="502" y2="242" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<text class="ts" x="340" y="130" text-anchor="middle">Event</text>
|
||||
<line x1="267" y1="140" x2="412" y2="140" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<text class="ts" x="340" y="254" text-anchor="middle">Summary</text>
|
||||
<line x1="412" y1="264" x2="267" y2="264" class="arr" marker-end="url(#arrow)"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
Notes on this example:
|
||||
- The two sibling containers are drawn **first** in source order so every internal node paints on top of the dashed border.
|
||||
- The cross-system arrows are labeled above and span the 10px gap plus the 20px padding on each side (so the actual horizontal run is 267 → 412 = 145px).
|
||||
- The "Event" label sits above the top cross-system arrow, the "Summary" label above the bottom one — both ≤2 words, both `class="ts"`, both `text-anchor="middle"` centered at x=340 (the midpoint of the 10px gap).
|
||||
- Row-to-row internal arrows inside each sibling are standard vertical `.arr` lines between node top/bottom edges with a 10px stand-off.
|
||||
|
||||
## Bus topology sub-pattern
|
||||
|
||||
For topics where N agents all publish to and subscribe from a **shared message bus** — *"multiple workers coordinating via a central channel"*, image #7 (Message Bus) in the Anthropic reference set. A central horizontal bar represents the bus; agents sit in a row above and below it, each connected via a pair of offset arrows (see `glyphs.md` → "Publish/subscribe arrow pair").
|
||||
|
||||
**When to use.**
|
||||
|
||||
- "Message bus / event bus coordination"
|
||||
- "Pub/sub topology with N publishers and N subscribers"
|
||||
- "Shared queue everyone reads and writes"
|
||||
- The reader needs to feel that *no agent talks to another agent directly* — all coordination passes through the bus.
|
||||
|
||||
**When not to use.** If the coordination is point-to-point (agent A only talks to agent B), it's just a structural arrow. If there's a clear orchestrator controlling the workers, use the "Rich interior for subsystem containers" fan-out variant below (image #9 left half), not the bus topology.
|
||||
|
||||
### Geometry
|
||||
|
||||
The bus bar is the visual spine of the diagram. Agents fan out above and below it. See also `layout-math.md` → "Bus topology geometry".
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|-----------------------------------------------------------|
|
||||
| Bus bar | `x=40 y=280 w=600 h=40 rx=20`, class `c-amber` or `c-gray`|
|
||||
| Bus label | `(340, 304)`, class `th`, `text-anchor="middle"` |
|
||||
| Top agent row y | `80` (box top); `h=60` (two-line) |
|
||||
| Bottom agent row y | `400` (box top); `h=60` |
|
||||
| Agent row box width | 140 (three agents) or 180 (two agents) or 110 (four) |
|
||||
|
||||
For **N=3 agents per row** (6 total), center the three agents at `x = 170, 340, 510` with `w=140`. For N=4 per row (8 total), center at `x = 120, 260, 420, 560` with `w=110`. For N=2 per row (4 total), center at `x = 180, 500` with `w=180`.
|
||||
|
||||
The bus bar uses `c-amber` when it's the "shared channel" of a message-bus system (amber = attention, the central thing everyone looks at) or `c-gray` when it's just structural scaffolding. Never use a cool ramp for the bus bar — the bus should feel active.
|
||||
|
||||
### Arrow pairs
|
||||
|
||||
Each agent connects to the bus with a **pair** of offset arrows: Publish (agent → bus) and Subscribe (bus → agent). The pair uses an 8px horizontal offset so the two arrows read as parallel, not concentric. See `glyphs.md` → "Publish/subscribe arrow pair" for the exact template.
|
||||
|
||||
For the top row, each agent's bottom edge is at `y=140`, the bus top is at `y=280`, so the arrow channel is 140px tall. Place the Publish/Subscribe labels at the channel midpoint (y=210):
|
||||
|
||||
```svg
|
||||
<!-- Top agent at center x=340, publishing down and subscribing up -->
|
||||
<line class="arr" x1="332" y1="140" x2="332" y2="280" marker-end="url(#arrow)"/>
|
||||
<line class="arr" x1="348" y1="280" x2="348" y2="140" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="324" y="214" text-anchor="end">Publish</text>
|
||||
<text class="ts" x="356" y="214">Subscribe</text>
|
||||
```
|
||||
|
||||
For the bottom row, mirror the y coordinates: agent top at `y=400`, bus bottom at `y=320`. The Subscribe arrow now goes up out of the bus into the agent; Publish goes down out of the agent into the bus.
|
||||
|
||||
### Labels on the bus bar
|
||||
|
||||
The bus bar is **always labeled** — a reader who can't tell if the central bar is "a bus" or "a queue" or "a shared log" will read the diagram wrong. Put a single `th` label at the bar's center: "Message bus", "Event bus", "Shared log", "Task queue". If the bus semantics need a second word, use a `ts` subtitle below the label (still inside the 40px-tall bar, `y = bar_y + 28`).
|
||||
|
||||
### Dark mode
|
||||
|
||||
All the bus-topology elements use `c-{ramp}` classes and stock `arr` utilities, so dark mode is automatic. The only thing to watch: if you used inline `stroke="#..."` on the Publish/Subscribe arrows, they won't invert. Use the unadorned `.arr` class and let the template's dark-mode override handle color.
|
||||
|
||||
## Radial star topology sub-pattern
|
||||
|
||||
For topics where a **central hub** is surrounded by N peripheral satellites that all talk to it bidirectionally — *"shared state with four workers"*, image #8 (Shared State). The hub is a single labeled box in the center; the satellites are smaller boxes at the four corners (or at radial offsets for N≠4).
|
||||
|
||||
**When to use.**
|
||||
|
||||
- "Shared state store with multiple accessors"
|
||||
- "Central coordinator with N workers" where coordinator semantics are read/write, not command/control
|
||||
- "Hub-and-spoke" architectures
|
||||
|
||||
**When not to use.** If the hub is the orchestrator *and* issues commands (one-way), it's a fan-out, not a radial star. If the satellites talk to each other (not just to the hub), the topology is a mesh, not a radial star — that's usually a sign to switch to a bus topology instead.
|
||||
|
||||
### Geometry (N=4)
|
||||
|
||||
See also `layout-math.md` → "Radial star geometry (3 / 4 / 5 / 6 satellites)".
|
||||
|
||||
| Element | Coordinates |
|
||||
|----------------------|--------------------------------------------|
|
||||
| Hub | `x=260 y=280 w=160 h=80 rx=10` |
|
||||
| Satellite TL | `x=60 y=120 w=160 h=60` |
|
||||
| Satellite TR | `x=460 y=120 w=160 h=60` |
|
||||
| Satellite BL | `x=60 y=460 w=160 h=60` |
|
||||
| Satellite BR | `x=460 y=460 w=160 h=60` |
|
||||
| Hub center | `(340, 320)` |
|
||||
| Satellite centers | `(140, 150)`, `(540, 150)`, `(140, 490)`, `(540, 490)` |
|
||||
|
||||
Hub is 80 tall to hold a 3-line label (title + subtitle + doc-icon if used). Satellites are 60 tall for a standard two-line label. viewBox H ≈ 560.
|
||||
|
||||
### Arrow pairs (bidirectional)
|
||||
|
||||
Each satellite connects to the hub with two single-headed arrows offset by 8px **perpendicular to the arrow direction**. For the top-left satellite, the arrow runs diagonally from `(220, 180)` to `(260, 280)` (approximately); offset the two lines by `(±4, ∓4)` to get clean parallel channels:
|
||||
|
||||
```svg
|
||||
<!-- Top-left satellite to hub: outbound (satellite→hub) on top, inbound on bottom -->
|
||||
<line class="arr" x1="224" y1="176" x2="264" y2="276" marker-end="url(#arrow)"/>
|
||||
<line class="arr" x1="216" y1="184" x2="256" y2="284" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Label each pair with a single `ts` label sitting *next to* the pair, not between the two lines (the 8px offset is too narrow for a label to fit). For diagonal pairs, place the label at the satellite end:
|
||||
|
||||
```svg
|
||||
<text class="ts" x="230" y="198" text-anchor="start">Read / write</text>
|
||||
```
|
||||
|
||||
Or use one label per direction if the semantics differ (`Query` outbound, `Result` inbound).
|
||||
|
||||
### Hub content
|
||||
|
||||
The hub is the one place in a structural diagram where a **decorative icon** is allowed: drop a `doc-icon` or `terminal-icon` from `glyphs.md` into the lower half of the hub rect to reinforce what kind of hub it is. See `glyphs.md` → "Document & terminal icons" for placement math.
|
||||
|
||||
```svg
|
||||
<g class="c-amber">
|
||||
<rect x="260" y="280" width="160" height="80" rx="10"/>
|
||||
<text class="th" x="340" y="302" text-anchor="middle">Shared state</text>
|
||||
<text class="ts" x="340" y="320" text-anchor="middle">Task artifacts</text>
|
||||
<!-- doc-icon placed at bottom-center of hub -->
|
||||
<g transform="translate(328, 324)">
|
||||
<path class="arr" d="M2 2 L16 2 L22 8 L22 26 L2 26 Z" fill="none"/>
|
||||
<path class="arr" d="M16 2 L16 8 L22 8" fill="none"/>
|
||||
<line class="arr" x1="6" y1="14" x2="18" y2="14"/>
|
||||
<line class="arr" x1="6" y1="18" x2="18" y2="18"/>
|
||||
<line class="arr" x1="6" y1="22" x2="14" y2="22"/>
|
||||
</g>
|
||||
</g>
|
||||
```
|
||||
|
||||
### Color budget
|
||||
|
||||
The hub takes the accent ramp (amber is the convention for shared-state hubs — everyone looks at it). Satellites stay **neutral** (`c-gray`) unless one satellite is distinguished from the others. Mixing four different ramps on four satellites is "rainbow" — it implies four different kinds of worker, which isn't what a radial star is saying.
|
||||
|
||||
### N≠4 variants
|
||||
|
||||
See `layout-math.md` for the full coordinate table. Summary:
|
||||
|
||||
- **N=3**: satellites at `(140, 100)`, `(540, 100)`, `(340, 500)` (two on top, one bottom center).
|
||||
- **N=5**: four corners as in N=4, plus one at `(340, 100)` top center.
|
||||
- **N=6**: three on top at `x=100, 340, 580` and three on bottom at the same x's (y=100 top, y=500 bottom).
|
||||
|
||||
Beyond N=6 the pattern doesn't fit the 680 viewBox — switch to a bus topology.
|
||||
|
||||
## Rich interior for subsystem containers
|
||||
|
||||
The base subsystem-container rule from "Subsystem architecture pattern" above says each sibling holds *up to 5 flowchart-style nodes in a vertical column*. This sub-section extends that rule: a sibling may instead hold **any one** of the following interior layouts. Each has its own geometry within the 315-wide container; mix-and-match across siblings is fine (e.g., container A holds a checklist, container B holds a DAG).
|
||||
|
||||
Pick the interior type that matches the subsystem's semantics:
|
||||
|
||||
| Interior type | Use for | See |
|
||||
|---------------------------|--------------------------------------------------------|----------------------------------------------|
|
||||
| Vertical flowchart | Sequential pipeline (the default) | "Internal nodes inside a sibling" above |
|
||||
| Fan-out (hub + ≤3 branches) | Orchestrator dispatching to workers (image #9 left) | Below |
|
||||
| Queue + vertical fan-out | Worker pool with a shared queue (image #9 right) | Below |
|
||||
| Mini bus topology | Bus-coordinated workers (image #10 right) | Below |
|
||||
| Checklist | Task list using `checkbox-*` glyphs (image #4 left) | `glyphs.md` → "Checkboxes" |
|
||||
| DAG (≤6 nodes) | Task graph with dependencies (image #4 right) | Below |
|
||||
| Nested container + gadget | Thing-within-a-thing + attached tool (image #14) | "Attached gadget box" below |
|
||||
|
||||
### Fan-out interior
|
||||
|
||||
Hub box + three worker boxes in a vertical column, inside a 315-wide container at x=20 or x=345.
|
||||
|
||||
| Element | Container A (x=20) | Container B (x=345) |
|
||||
|---------------|---------------------------------|---------------------------------|
|
||||
| Hub box | `x=50 y=120 w=120 h=44 rx=6` | `x=375 y=120 w=120 h=44 rx=6` |
|
||||
| Worker 1 | `x=195 y=100 w=120 h=44` | `x=520 y=100 w=120 h=44` |
|
||||
| Worker 2 | `x=195 y=160 w=120 h=44` | `x=520 y=160 w=120 h=44` |
|
||||
| Worker 3 | `x=195 y=220 w=120 h=44` | `x=520 y=220 w=120 h=44` |
|
||||
| Bend channel | `x=185` | `x=510` |
|
||||
|
||||
Route each worker arrow as a short L-bend: hub right-edge → bend channel → worker left-edge. The same routing logic as `flowchart.md` → "Vertical fan-out", scaled to the 315-wide container.
|
||||
|
||||
### Queue + vertical fan-out interior
|
||||
|
||||
Same as the fan-out interior but the hub box carries a `queue-slot` row in its bottom half (see `flowchart.md` → "Queue glyph inside a box"). Hub box height grows to 52 to accommodate the glyph row:
|
||||
|
||||
| Element | Coordinates (container A) |
|
||||
|---------------|-----------------------------------|
|
||||
| Hub (queue) | `x=50 y=116 w=120 h=52 rx=6` |
|
||||
| Workers | same as fan-out interior |
|
||||
|
||||
Hub title `y = 134`, queue row `y = 146`, 4 slots at `x = 62, 82, 102, 122`. (Fewer slots than the full 6 because the container is narrow.)
|
||||
|
||||
### Mini bus topology interior
|
||||
|
||||
A small bus bar and 2–3 mini-agents above/below it, scaled down to fit inside a 315-wide container:
|
||||
|
||||
| Element | Container A (x=20) |
|
||||
|------------------|-----------------------------------|
|
||||
| Bus bar | `x=40 y=200 w=275 h=28 rx=14` |
|
||||
| Bus label | `(177, 218)`, `th` |
|
||||
| Top agent 1 | `x=50 y=110 w=100 h=44` |
|
||||
| Top agent 2 | `x=195 y=110 w=100 h=44` |
|
||||
| Bottom agent 1 | `x=50 y=260 w=100 h=44` |
|
||||
| Bottom agent 2 | `x=195 y=260 w=100 h=44` |
|
||||
|
||||
The mini bus drops the Publish/Subscribe text labels (no room) and just shows the offset arrow pairs — the outer container's title already names the pattern. Use `c-amber` on the bus bar.
|
||||
|
||||
### DAG interior
|
||||
|
||||
Up to 6 small boxes connected by L-bend arrows, representing a task graph with dependencies. Use this for image #4's "Tasks" right-side panel.
|
||||
|
||||
Boxes are small (80 × 32) and laid out on a 3-column × 2-row grid inside the container:
|
||||
|
||||
| Grid position | x (container A) | x (container B) |
|
||||
|-----------------|-----------------|-----------------|
|
||||
| Column 1 | 40 | 365 |
|
||||
| Column 2 | 145 | 470 |
|
||||
| Column 3 | 250 | 575 |
|
||||
|
||||
Box height: 32. Row y: `120, 200`. `rx=4` (smaller than standard 6 because the boxes are tight).
|
||||
|
||||
Dashed future-state nodes (`arr-alt` class — see "Dashed future-state node" below) intermix with solid nodes in the same grid, representing the not-yet-scheduled parts of the graph.
|
||||
|
||||
### Checklist interior
|
||||
|
||||
A column of `checkbox-*` glyphs plus `t` labels inside the container. See `glyphs.md` → "Checklist rows" for row geometry. For a 315-wide container:
|
||||
|
||||
- First row at `y = 116`
|
||||
- Row pitch: 22
|
||||
- Checkbox anchor at `x = container_x + 20`
|
||||
- Label anchor at `x = container_x + 42`
|
||||
- Label width budget: container interior width − 42 − 20 = 253 px (≈31 Latin / 16 CJK chars)
|
||||
|
||||
Up to 8 rows fit in a 260-tall container (8 × 22 = 176, plus header + padding).
|
||||
|
||||
## Mixed arrow semantics
|
||||
|
||||
Sub-pattern for diagrams — especially advisor-strategy and multi-agent architectures (image #11) — where **multiple kinds of relationships** exist on the same canvas: synchronous call, asynchronous notification, read/write data flow, and a reentry loop. Using `.arr` for all of them strips the distinction; using random colors for different line types triggers the "color rainbow" pitfall.
|
||||
|
||||
The solution is a **4-class arrow vocabulary** with a tiny legend strip inside the container.
|
||||
|
||||
### The vocabulary
|
||||
|
||||
| Arrow style | CSS class | Semantic | Typical label |
|
||||
|---------------------------------|-------------------|--------------------------------------------|---------------------|
|
||||
| Solid 1.5px | `arr` / `arr-{ramp}` | Synchronous call, required path | "calls", "invokes" |
|
||||
| Dashed 1.5px | `arr-alt` | Asynchronous, optional, conditional | "notifies", "may call" |
|
||||
| Two offset single-headed arrows | `arr` ×2 | Bidirectional read/write on shared resource | "read / write" |
|
||||
| U-shaped reentry path | `arr` | Main loop, reentry to top of flow | "loop" |
|
||||
|
||||
This is the only place in the skill that **four distinct line styles** appear in one diagram — elsewhere, two is the cap. The cost of richness is a required legend strip.
|
||||
|
||||
### Legend strip
|
||||
|
||||
Place a one-line mini-legend inside the container at the top, below the container title, using `ts`-class text and tiny 20px example line segments:
|
||||
|
||||
```svg
|
||||
<g transform="translate(40, 108)">
|
||||
<!-- Solid = calls -->
|
||||
<line class="arr" x1="0" y1="6" x2="20" y2="6" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="24" y="10">calls</text>
|
||||
<!-- Dashed = notifies -->
|
||||
<line class="arr-alt" x1="80" y1="6" x2="100" y2="6" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="104" y="10">notifies</text>
|
||||
<!-- Pair = read/write -->
|
||||
<line class="arr" x1="170" y1="2" x2="190" y2="2" marker-end="url(#arrow)"/>
|
||||
<line class="arr" x1="190" y1="10" x2="170" y2="10" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="194" y="10">read / write</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Keep the legend to 3–4 entries (the ones that actually appear in the diagram — don't document every possible edge type). The legend is always `ts` muted, never accent color — it's meta-information.
|
||||
|
||||
### U-shaped reentry loop
|
||||
|
||||
A solid arrow path that exits the right side of the final node, arcs around the bottom of the diagram, and re-enters the left side of the first node. Used for "main loop runs every turn" semantics.
|
||||
|
||||
```svg
|
||||
<path class="arr"
|
||||
d="M 560 200 L 600 200 L 600 330 L 80 330 L 80 200 L 120 200"
|
||||
fill="none" marker-end="url(#arrow)"/>
|
||||
<text class="ts" x="340" y="324" text-anchor="middle">runs every turn</text>
|
||||
```
|
||||
|
||||
The horizontal return channel (`y=330` in the example) must lie in empty space below all other boxes. If it can't, use a `↻ runs every turn` text label next to the final node instead, same as the simple-flowchart return convention.
|
||||
|
||||
### Pitfall
|
||||
|
||||
Don't invent new arrow classes (`.arr-read`, `.arr-write`, `.arr-async`). The four-style vocabulary above is comprehensive; anything else is a template change and breaks the "minimal CSS" rule. If your diagram genuinely needs a 5th semantic, you're asking one diagram to do too much — split it.
|
||||
|
||||
## Multi-line box body
|
||||
|
||||
Most structural boxes hold one or two lines of text. Advisor-strategy and agent-role diagrams (image #11) sometimes need **three-line boxes**: a title, a role/kind subtitle, and a meta line like "runs every turn" or "executor · sonnet". This sub-section documents the layout.
|
||||
|
||||
### Geometry
|
||||
|
||||
| Element | Coordinates |
|
||||
|-----------------|------------------------------------|
|
||||
| Host rect | `h = 80`, `rx=6`, `w ≥ 180` |
|
||||
| Title `th` | `y = rect_y + 22`, `text-anchor="middle"` |
|
||||
| Role `ts` | `y = rect_y + 42`, italic (see below) |
|
||||
| Meta `ts` | `y = rect_y + 62`, muted (see below) |
|
||||
|
||||
Row pitch is 20 (tight). The 80px total is the minimum — if you need more breathing room, grow to 88 or 92, but never less than 80.
|
||||
|
||||
### Style variations on the two subtitles
|
||||
|
||||
The `ts` class renders as muted 12px in both modes. For the role line (middle), apply italic via an inline `font-style="italic"` attribute. For the meta line (bottom), leave it plain `ts` — it inherits the muted color automatically.
|
||||
|
||||
```svg
|
||||
<g class="c-teal">
|
||||
<rect x="80" y="140" width="180" height="80" rx="6"/>
|
||||
<text class="th" x="170" y="162" text-anchor="middle">Advisor</text>
|
||||
<text class="ts" x="170" y="182" text-anchor="middle" font-style="italic">opus · reasoning</text>
|
||||
<text class="ts" x="170" y="202" text-anchor="middle">runs every turn</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
### Pitfall
|
||||
|
||||
Three-line boxes are dense. Don't use them when a two-line box will do. If you find yourself adding a third line *to every box* in the diagram, the diagram is carrying too much information — move the metadata out to the annotation column or a caption.
|
||||
|
||||
## Attached gadget box
|
||||
|
||||
A small "attached tool" box that hangs off a parent box via a short connector with no arrowhead. Used in image #14 (Code execution vs. Dedicated tool) to show that "Tools" is an attached capability of the Claude box, not a peer or a pipeline step.
|
||||
|
||||
### Geometry
|
||||
|
||||
| Element | Coordinates relative to parent |
|
||||
|---------------|------------------------------------------------------|
|
||||
| Connector | from `(parent_cx, parent_bottom)` to `(parent_cx, parent_bottom + 24)`, class `arr` **without** `marker-end` |
|
||||
| Child rect | `x = parent_cx − 60`, `y = parent_bottom + 24`, `w=120 h=56 rx=6` |
|
||||
| Child title | `y = child_y + 22`, `th` |
|
||||
| Child subtitle | `y = child_y + 40`, `ts` |
|
||||
|
||||
The child is centered under the parent. The short connector is a plain line (no arrow) — the gadget is attached, not called.
|
||||
|
||||
### Color
|
||||
|
||||
The gadget box inherits the parent's ramp if it's a direct extension (Claude + Tools → both `c-amber`). If the gadget is a *distinct thing attached to* the parent (Claude + External database → `c-amber` Claude + `c-gray` database), color them separately.
|
||||
|
||||
### Pitfall
|
||||
|
||||
Don't over-use this. One attached gadget per diagram is fine; three or more and the gadgets start to look like real workflow steps. If you need to attach three things to a parent, you're describing a fan-out, not gadgetry.
|
||||
|
||||
## Dashed future-state node
|
||||
|
||||
Sub-pattern for diagrams that distinguish **current state** (active, realized) from **future state** (planned, not yet scheduled) — image #4's "Tasks" panel, where Task 4 sits ghosted in the DAG because it hasn't been assigned yet.
|
||||
|
||||
### The rule
|
||||
|
||||
Use the existing `.arr-alt` class on the rect. It already provides `fill: none`, `stroke-width: 1.5`, `stroke-dasharray: 5 4`, and dark-mode handling:
|
||||
|
||||
```svg
|
||||
<g>
|
||||
<rect class="arr-alt" x="200" y="180" width="120" height="44" rx="6"/>
|
||||
<text class="ts" x="260" y="205" text-anchor="middle">Task 4</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Do not** create a new `.box-future` CSS class. The template's class list stays stable — future-state styling is fully expressible with what already exists.
|
||||
|
||||
**Do not** put a title (`th`) inside a dashed future rect. Future-state is demoted — use only `ts` (12px muted) for the label. A dashed rect with a bold title reads as contradiction: "this is a real thing but it's tentative?" — pick one.
|
||||
|
||||
### Mixing with active nodes in a DAG
|
||||
|
||||
When a DAG contains both active (solid) and future (dashed) nodes, keep the geometry identical across both types — same rect width, height, rx, y-coordinate. The only difference should be the stroke dasharray and the text weight. Any other difference (padding, shadow, label size) confuses the reader.
|
||||
|
||||
Route arrows into a dashed rect with the `.arr-alt` class too, and *out* of a dashed rect with `.arr-alt` — the "not yet scheduled" status is contagious forward in the graph. Arrows going *from a solid into a dashed* use solid `.arr` (the active task is scheduled to emit into the future task once it runs).
|
||||
|
||||
|
||||
## Network topology sub-pattern
|
||||
|
||||
See `references/structural-network.md`. **Load it when the prompt asks about network infrastructure**: "where do the wires go", "which zone is this device in", "DMZ / firewall / VPC topology", security zones, or device connectivity.
|
||||
|
||||
## Comparison matrix sub-pattern
|
||||
|
||||
See `references/structural-matrix.md`. **Load it when the content is a feature comparison**: "which of these databases supports X", side-by-side feature tables, ✓/✗ grids, or any "option × attribute" matrix.
|
||||
|
||||
## Common failure modes
|
||||
|
||||
- **Hierarchy flattens** — parent and child use the same `c-{ramp}` class. Fix: pick different ramps.
|
||||
- **Overflowing inner regions** — two 250-wide regions + 20 gap + 40 padding = 560, fits a 560-wide outer container exactly with no breathing room. Widen the outer container to 580 or shrink the inner regions.
|
||||
- **External arrow with `text-anchor="end"` at low x** — the label extends past x=0 and clips. Use `text-anchor="middle"` for centered labels or `"start"` for labels that extend right.
|
||||
- **Trying to draw literal shapes for the container** — don't draw an actual cell ellipse or a literal server tower. Rounded rects with clear labels read better than shapes that try to be literal. Save illustrative drawing for `illustrative.md` type diagrams.
|
||||
- **Subsystem pattern with 6+ internal nodes** — the ≤5-node cap per sibling is the ceiling. Beyond that, the internal flow wants to live in its own flowchart diagram, and the structural diagram should step up one level to just the containment picture.
|
||||
- **Subsystem pattern used for containment** — if one subsystem is logically *inside* the other, don't make them siblings. Nest them the normal way and skip the dashed-border trick.
|
||||
- Start by counting the widest level to determine total diagram width
|
||||
- Center the tree horizontally in the viewBox
|
||||
- For deep trees (5+ levels), consider horizontal layout instead
|
||||
|
|
|
|||
|
|
@ -1,258 +0,0 @@
|
|||
# SVG Template
|
||||
|
||||
Every diagram this skill produces starts with the same boilerplate: root `<svg>` element, an embedded `<style>` block that defines all color classes (including dark mode), and a `<defs>` block with the arrow marker. Copy this template verbatim into every diagram, then write your visual elements after the closing `</defs>`.
|
||||
|
||||
**Why we embed the styles.** In claude.ai's Imagine runtime, classes like `c-blue`, `t`, `ts`, `th` are pre-loaded by the host. A standalone SVG dropped into a WeChat article or a Notion page has no such host — it has to carry its own styles. The `<style>` block below reproduces the Imagine class system as self-contained CSS so every SVG renders correctly anywhere.
|
||||
|
||||
## The template
|
||||
|
||||
Fill in `H` with the actual viewBox height computed from your content (see `layout-math.md`). Leave everything else exactly as written.
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 680 H" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif">
|
||||
<style>
|
||||
.t { font-size: 14px; font-weight: 400; fill: #2C2C2A; }
|
||||
.ts { font-size: 12px; font-weight: 400; fill: #5F5E5A; }
|
||||
.th { font-size: 14px; font-weight: 500; fill: #2C2C2A; }
|
||||
.title { font-size: 20px; font-weight: 600; fill: #2C2C2A; }
|
||||
.eyebrow { font-size: 10px; font-weight: 500; fill: #888780; letter-spacing: 1.2px; text-transform: uppercase; }
|
||||
.caption { font-size: 12px; font-weight: 400; fill: #888780; font-style: italic; }
|
||||
.anno { font-size: 12px; font-weight: 400; fill: #888780; }
|
||||
.box { fill: #F1EFE8; stroke: #B4B2A9; stroke-width: 0.5; }
|
||||
.row-alt { fill: #FFFFFF; stroke: #D3D1C7; stroke-width: 0.5; }
|
||||
.arr { fill: none; stroke: #5F5E5A; stroke-width: 1.5; }
|
||||
.arr-alt { fill: none; stroke: #5F5E5A; stroke-width: 1.5; stroke-dasharray: 5 4; }
|
||||
.leader{ fill: none; stroke: #B4B2A9; stroke-width: 0.5; stroke-dasharray: 3 3; }
|
||||
.lifeline { fill: none; stroke: #B4B2A9; stroke-width: 1; stroke-dasharray: 4 4; }
|
||||
|
||||
.arr-gray { fill: none; stroke: #5F5E5A; stroke-width: 1.5; }
|
||||
.arr-blue { fill: none; stroke: #185FA5; stroke-width: 1.5; }
|
||||
.arr-teal { fill: none; stroke: #0F6E56; stroke-width: 1.5; }
|
||||
.arr-purple { fill: none; stroke: #534AB7; stroke-width: 1.5; }
|
||||
.arr-coral { fill: none; stroke: #993C1D; stroke-width: 1.5; }
|
||||
.arr-pink { fill: none; stroke: #993556; stroke-width: 1.5; }
|
||||
.arr-amber { fill: none; stroke: #854F0B; stroke-width: 1.5; }
|
||||
.arr-green { fill: none; stroke: #3B6D11; stroke-width: 1.5; }
|
||||
.arr-red { fill: none; stroke: #A32D2D; stroke-width: 1.5; }
|
||||
|
||||
.c-gray > rect, .c-gray > circle, .c-gray > ellipse, rect.c-gray, circle.c-gray, ellipse.c-gray { fill: #F1EFE8; stroke: #5F5E5A; stroke-width: 0.5; }
|
||||
.c-gray .th, .c-gray .t { fill: #444441; }
|
||||
.c-gray .ts { fill: #5F5E5A; }
|
||||
|
||||
.c-blue > rect, .c-blue > circle, .c-blue > ellipse, rect.c-blue, circle.c-blue, ellipse.c-blue { fill: #E6F1FB; stroke: #185FA5; stroke-width: 0.5; }
|
||||
.c-blue .th, .c-blue .t { fill: #0C447C; }
|
||||
.c-blue .ts { fill: #185FA5; }
|
||||
|
||||
.c-teal > rect, .c-teal > circle, .c-teal > ellipse, rect.c-teal, circle.c-teal, ellipse.c-teal { fill: #E1F5EE; stroke: #0F6E56; stroke-width: 0.5; }
|
||||
.c-teal .th, .c-teal .t { fill: #085041; }
|
||||
.c-teal .ts { fill: #0F6E56; }
|
||||
|
||||
.c-purple > rect, .c-purple > circle, .c-purple > ellipse, rect.c-purple, circle.c-purple, ellipse.c-purple { fill: #EEEDFE; stroke: #534AB7; stroke-width: 0.5; }
|
||||
.c-purple .th, .c-purple .t { fill: #3C3489; }
|
||||
.c-purple .ts { fill: #534AB7; }
|
||||
|
||||
.c-coral > rect, .c-coral > circle, .c-coral > ellipse, rect.c-coral, circle.c-coral, ellipse.c-coral { fill: #FAECE7; stroke: #993C1D; stroke-width: 0.5; }
|
||||
.c-coral .th, .c-coral .t { fill: #712B13; }
|
||||
.c-coral .ts { fill: #993C1D; }
|
||||
|
||||
.c-pink > rect, .c-pink > circle, .c-pink > ellipse, rect.c-pink, circle.c-pink, ellipse.c-pink { fill: #FBEAF0; stroke: #993556; stroke-width: 0.5; }
|
||||
.c-pink .th, .c-pink .t { fill: #72243E; }
|
||||
.c-pink .ts { fill: #993556; }
|
||||
|
||||
.c-amber > rect, .c-amber > circle, .c-amber > ellipse, rect.c-amber, circle.c-amber, ellipse.c-amber { fill: #FAEEDA; stroke: #854F0B; stroke-width: 0.5; }
|
||||
.c-amber .th, .c-amber .t { fill: #633806; }
|
||||
.c-amber .ts { fill: #854F0B; }
|
||||
|
||||
.c-green > rect, .c-green > circle, .c-green > ellipse, rect.c-green, circle.c-green, ellipse.c-green { fill: #EAF3DE; stroke: #3B6D11; stroke-width: 0.5; }
|
||||
.c-green .th, .c-green .t { fill: #27500A; }
|
||||
.c-green .ts { fill: #3B6D11; }
|
||||
|
||||
.c-red > rect, .c-red > circle, .c-red > ellipse, rect.c-red, circle.c-red, ellipse.c-red { fill: #FCEBEB; stroke: #A32D2D; stroke-width: 0.5; }
|
||||
.c-red .th, .c-red .t { fill: #791F1F; }
|
||||
.c-red .ts { fill: #A32D2D; }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.t, .th { fill: #F1EFE8; }
|
||||
.ts { fill: #B4B2A9; }
|
||||
.title { fill: #F1EFE8; }
|
||||
.eyebrow { fill: #888780; }
|
||||
.caption { fill: #888780; }
|
||||
.anno { fill: #888780; }
|
||||
.box { fill: #2C2C2A; stroke: #888780; }
|
||||
.row-alt { fill: #444441; stroke: #5F5E5A; }
|
||||
.arr { stroke: #B4B2A9; }
|
||||
.arr-alt { stroke: #B4B2A9; }
|
||||
.leader { stroke: #888780; }
|
||||
.lifeline { stroke: #5F5E5A; }
|
||||
|
||||
.arr-gray { stroke: #B4B2A9; }
|
||||
.arr-blue { stroke: #85B7EB; }
|
||||
.arr-teal { stroke: #5DCAA5; }
|
||||
.arr-purple { stroke: #AFA9EC; }
|
||||
.arr-coral { stroke: #F0997B; }
|
||||
.arr-pink { stroke: #ED93B1; }
|
||||
.arr-amber { stroke: #EF9F27; }
|
||||
.arr-green { stroke: #97C459; }
|
||||
.arr-red { stroke: #F09595; }
|
||||
|
||||
.c-gray > rect, .c-gray > circle, .c-gray > ellipse, rect.c-gray, circle.c-gray, ellipse.c-gray { fill: #444441; stroke: #B4B2A9; }
|
||||
.c-gray .th, .c-gray .t { fill: #F1EFE8; }
|
||||
.c-gray .ts { fill: #D3D1C7; }
|
||||
|
||||
.c-blue > rect, .c-blue > circle, .c-blue > ellipse, rect.c-blue, circle.c-blue, ellipse.c-blue { fill: #0C447C; stroke: #85B7EB; }
|
||||
.c-blue .th, .c-blue .t { fill: #B5D4F4; }
|
||||
.c-blue .ts { fill: #85B7EB; }
|
||||
|
||||
.c-teal > rect, .c-teal > circle, .c-teal > ellipse, rect.c-teal, circle.c-teal, ellipse.c-teal { fill: #085041; stroke: #5DCAA5; }
|
||||
.c-teal .th, .c-teal .t { fill: #9FE1CB; }
|
||||
.c-teal .ts { fill: #5DCAA5; }
|
||||
|
||||
.c-purple > rect, .c-purple > circle, .c-purple > ellipse, rect.c-purple, circle.c-purple, ellipse.c-purple { fill: #3C3489; stroke: #AFA9EC; }
|
||||
.c-purple .th, .c-purple .t { fill: #CECBF6; }
|
||||
.c-purple .ts { fill: #AFA9EC; }
|
||||
|
||||
.c-coral > rect, .c-coral > circle, .c-coral > ellipse, rect.c-coral, circle.c-coral, ellipse.c-coral { fill: #712B13; stroke: #F0997B; }
|
||||
.c-coral .th, .c-coral .t { fill: #F5C4B3; }
|
||||
.c-coral .ts { fill: #F0997B; }
|
||||
|
||||
.c-pink > rect, .c-pink > circle, .c-pink > ellipse, rect.c-pink, circle.c-pink, ellipse.c-pink { fill: #72243E; stroke: #ED93B1; }
|
||||
.c-pink .th, .c-pink .t { fill: #F4C0D1; }
|
||||
.c-pink .ts { fill: #ED93B1; }
|
||||
|
||||
.c-amber > rect, .c-amber > circle, .c-amber > ellipse, rect.c-amber, circle.c-amber, ellipse.c-amber { fill: #633806; stroke: #EF9F27; }
|
||||
.c-amber .th, .c-amber .t { fill: #FAC775; }
|
||||
.c-amber .ts { fill: #EF9F27; }
|
||||
|
||||
.c-green > rect, .c-green > circle, .c-green > ellipse, rect.c-green, circle.c-green, ellipse.c-green { fill: #27500A; stroke: #97C459; }
|
||||
.c-green .th, .c-green .t { fill: #C0DD97; }
|
||||
.c-green .ts { fill: #97C459; }
|
||||
|
||||
.c-red > rect, .c-red > circle, .c-red > ellipse, rect.c-red, circle.c-red, ellipse.c-red { fill: #791F1F; stroke: #F09595; }
|
||||
.c-red .th, .c-red .t { fill: #F7C1C1; }
|
||||
.c-red .ts { fill: #F09595; }
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- your visual elements go here -->
|
||||
</svg>
|
||||
```
|
||||
|
||||
## Poster-flowchart utility classes
|
||||
|
||||
Four classes are available for poster-style flowcharts (see `flowchart.md` → "Poster flowchart pattern"). These are **optional** — simple flowcharts, structural, illustrative, and sequence diagrams should ignore them and pay nothing beyond ~300 bytes of unused CSS.
|
||||
|
||||
| Class | Size | Use for |
|
||||
|-------------|--------|------------------------------------------------------------------------------------------|
|
||||
| `.title` | 20px 600 | Diagram-level title. Used ONCE at the top when the topic has a short mechanism name. |
|
||||
| `.eyebrow` | 10px 500 | Small uppercase section dividers between groups of stages. Letter-spaced, muted gray. |
|
||||
| `.caption` | 12px italic | Footer hook line at the bottom of the diagram. One line, italic, muted gray. |
|
||||
| `.anno` | 12px | Side-column annotation text — "sees: X / fresh context" notes beside a box. Muted gray. |
|
||||
|
||||
All four work in both light and dark mode via the template's `@media` block. Never use `.title`, `.eyebrow`, or `.caption` inside a regular box — they're meta-labels for the whole diagram, not cell content.
|
||||
|
||||
## Sequence-diagram utility classes
|
||||
|
||||
The `<style>` block also defines `.lifeline` (dashed vertical lifeline for sequence diagrams) and nine `.arr-{ramp}` colored-arrow utilities (`arr-gray`, `arr-blue`, `arr-teal`, `arr-purple`, `arr-coral`, `arr-pink`, `arr-amber`, `arr-green`, `arr-red`). These are only used by sequence diagrams, where each actor's messages inherit the actor's ramp color. Flowchart, structural, and illustrative diagrams ignore them and pay nothing beyond ~600 bytes of unused CSS. Arrow marker `url(#arrow)` uses `context-stroke` so arrowheads automatically match the colored stroke — never set `fill` on these paths.
|
||||
|
||||
## The `.row-alt` alternating-row utility
|
||||
|
||||
A paired companion to `.box` for **comparison-matrix tables** (see `structural.md` → "Comparison-matrix sub-pattern"). The two classes alternate to create zebra striping across rows of a matrix so the eye can follow a row across 4–5 columns without drifting into the next row.
|
||||
|
||||
- **Light mode**: `.box` = cream `#F1EFE8`, `.row-alt` = pure white `#FFFFFF` — the contrast between the two fills is deliberately low (one ramp stop) so the striping is legible but doesn't compete with the cell text.
|
||||
- **Dark mode**: `.box` = near-black `#2C2C2A`, `.row-alt` = one step lighter `#444441` — same principle, inverted.
|
||||
|
||||
Alternate rows by applying `.box` to odd rows and `.row-alt` to even rows, or vice versa. Do **not** use either class to hint at semantic status (active / inactive / warning) — that's what `c-{ramp}` is for. `.row-alt` is purely for visual rhythm.
|
||||
|
||||
## The `.arr-alt` alternative-flow utility
|
||||
|
||||
The `<style>` block also defines `.arr-alt` — a **1.5px dashed** connector for *alternative*, *optional*, or *weak* flows. Same weight as `.arr`, but the dash pattern (`5 4`) visually demotes the connector so a reader scans the solid arrows first.
|
||||
|
||||
Use it when semantics call for it, not as decoration:
|
||||
|
||||
- **Flowchart** — the Fail branch out of a Gate, the unchosen paths in a router fan-out, the "Stop" return from an agent loop, or any conditional/optional edge that only fires part of the time.
|
||||
- **Illustrative** — the spokes in a central-subject-plus-radial-attachments layout (LLM hub to Retrieval / Tools / Memory), where the attachments are *available capabilities* rather than guaranteed steps.
|
||||
|
||||
Do **not** use `.arr-alt` as a decorative stroke, and do not use it inside sequence diagrams (every sequence message is either there or not — there is no "maybe" in a protocol). `.arr-alt` is distinct from `.leader`: `.leader` is 0.5px hair-dashed for illustrative callout lines, `.arr-alt` is 1.5px mid-dashed for connectors that carry meaning.
|
||||
|
||||
## Rules for using the template
|
||||
|
||||
- **viewBox width is always 680.** Never change it. This is the container width every diagram is sized against. If your content is narrow, keep the width at 680 and center your content inside — do not shrink the viewBox to hug the drawing.
|
||||
- **viewBox height is computed from content.** See `layout-math.md` for the formula. Rule of thumb: `H = max_y + 20` where `max_y` is the lowest point of any element (bottom of the lowest rect, baseline + 4px descent of the lowest text).
|
||||
- **Arrow marker uses `context-stroke`.** This means the arrowhead automatically matches the color of whichever line it's attached to. Use `marker-end="url(#arrow)"` on any `<line>` or `<path>` connector.
|
||||
- **Add a `<clipPath>` to `<defs>` only if an illustrative diagram needs one.** Nothing else belongs in `<defs>`.
|
||||
- **Add a single `<linearGradient>` to `<defs>` only for illustrative diagrams** showing a continuous physical property (temperature stratification, pressure drop). Between two stops from the same ramp. Never more than one gradient per SVG.
|
||||
|
||||
## Using the color classes
|
||||
|
||||
Apply `c-{ramp}` to the `<g>` wrapper that contains both the shape and its text:
|
||||
|
||||
```svg
|
||||
<g class="c-blue">
|
||||
<rect x="100" y="20" width="180" height="44" rx="6"/>
|
||||
<text class="th" x="190" y="42" text-anchor="middle" dominant-baseline="central">Login service</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
Or directly to the shape itself if there's no wrapping `<g>`:
|
||||
|
||||
```svg
|
||||
<rect class="c-blue" x="100" y="20" width="180" height="44" rx="6"/>
|
||||
```
|
||||
|
||||
**Do not nest `c-*` groups.** The CSS uses direct-child selectors — `<g class="c-blue"><g>...</g></g>` won't apply the fill to the inner shapes. If you need a click handler (future), put it on the same group that carries the color class, not a wrapper.
|
||||
|
||||
## Optional background grid
|
||||
|
||||
For structural diagrams with a technical/blueprint feel, an optional background grid can reinforce the engineering aesthetic. **This is rare** — most diagrams should keep the transparent background for seamless embedding. Only consider the grid when the diagram shows infrastructure, network topology, or hardware architecture where the grid reads as "engineering paper" rather than decoration.
|
||||
|
||||
Add the pattern to `<defs>` and a full-viewport rect as the first visual element:
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<!-- arrow marker (always present) -->
|
||||
<marker id="arrow" .../>
|
||||
<!-- grid pattern (optional, structural diagrams only) -->
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-stroke, rgba(0,0,0,0.06))" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- Grid background (first visual element, behind everything) -->
|
||||
<rect width="680" height="H" fill="url(#grid)"/>
|
||||
```
|
||||
|
||||
Define the CSS variable in both modes so the value is explicit and discoverable (not buried in an inline fallback). Add these inside the existing `<style>` block:
|
||||
|
||||
```css
|
||||
svg { --grid-stroke: rgba(0,0,0,0.06); }
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg { --grid-stroke: rgba(255,255,255,0.05); }
|
||||
/* ... existing dark-mode overrides ... */
|
||||
}
|
||||
```
|
||||
|
||||
With the explicit light-mode declaration, the inline fallback in the `<pattern>` stroke is redundant but harmless — keep it as a safety net for renderers that strip `<style>` blocks.
|
||||
|
||||
**Rules:**
|
||||
- Grid stroke opacity must stay ≤0.08 in both modes — the grid is a texture, not a visual element
|
||||
- The grid rect is always the first child after `</defs>`, so every other element paints on top
|
||||
- Never use the grid on illustrative, sequence, or class diagrams — it fights with their visual language
|
||||
- If the grid competes with the diagram's lines for visual attention, remove it
|
||||
|
||||
## What to emit after the template
|
||||
|
||||
Visual elements in this order:
|
||||
|
||||
1. Background decorations (optional grid rect, dashed frame for a schematic container)
|
||||
2. Containers (outer group rectangles for structural diagrams)
|
||||
3. Connectors and arrows (so they sit behind the boxes they connect, preventing visible overlap) — both solid `.arr`/`.arr-{ramp}` primary flows and `.arr-alt` alternative/optional/weak flows belong in this layer
|
||||
4. Nodes (rects with text)
|
||||
5. Labels outside boxes (legend swatches, leader callouts)
|
||||
|
||||
When connectors and nodes fight for z-order, nodes win — draw the connectors first so the boxes paint on top of any line that crosses their edge.
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env bun
|
||||
import { existsSync, readFileSync, mkdirSync } from "fs";
|
||||
import { basename, dirname, extname, join, resolve } from "path";
|
||||
|
||||
interface Options {
|
||||
input: string;
|
||||
output?: string;
|
||||
scale: number;
|
||||
json: boolean;
|
||||
}
|
||||
|
||||
function parseViewBox(svg: string): { width: number; height: number } | null {
|
||||
const vb = svg.match(/viewBox\s*=\s*"([^"]+)"/);
|
||||
if (vb) {
|
||||
const parts = vb[1].split(/[\s,]+/).map(Number);
|
||||
if (parts.length >= 4 && parts[2] > 0 && parts[3] > 0) return { width: parts[2], height: parts[3] };
|
||||
}
|
||||
const w = svg.match(/\bwidth\s*=\s*"(\d+(?:\.\d+)?)"/);
|
||||
const h = svg.match(/\bheight\s*=\s*"(\d+(?:\.\d+)?)"/);
|
||||
if (w && h) return { width: Number(w[1]), height: Number(h[1]) };
|
||||
return null;
|
||||
}
|
||||
|
||||
function getOutputPath(input: string, scale: number, custom?: string): string {
|
||||
if (custom) return resolve(custom);
|
||||
const dir = dirname(input);
|
||||
const base = basename(input, extname(input));
|
||||
const suffix = scale === 1 ? "" : `@${scale}x`;
|
||||
return join(dir, `${base}${suffix}.png`);
|
||||
}
|
||||
|
||||
async function convert(input: string, opts: Options): Promise<{ output: string; width: number; height: number }> {
|
||||
const svg = readFileSync(input);
|
||||
const svgStr = svg.toString("utf-8");
|
||||
const dims = parseViewBox(svgStr);
|
||||
if (!dims) throw new Error("Cannot determine SVG dimensions from viewBox or width/height attributes");
|
||||
|
||||
const width = Math.round(dims.width * opts.scale);
|
||||
const height = Math.round(dims.height * opts.scale);
|
||||
|
||||
const sharp = (await import("sharp")).default;
|
||||
const output = getOutputPath(input, opts.scale, opts.output);
|
||||
mkdirSync(dirname(output), { recursive: true });
|
||||
|
||||
await sharp(svg, { density: 72 * opts.scale })
|
||||
.resize(width, height)
|
||||
.png()
|
||||
.toFile(output);
|
||||
|
||||
return { output, width, height };
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`Usage: bun main.ts <input.svg> [options]
|
||||
|
||||
Convert SVG to @2x PNG.
|
||||
|
||||
Options:
|
||||
-o, --output <path> Output path (default: <input>@2x.png)
|
||||
-s, --scale <n> Scale factor (default: 2)
|
||||
--json JSON output
|
||||
-h, --help Show help`);
|
||||
}
|
||||
|
||||
function parseArgs(args: string[]): Options | null {
|
||||
const opts: Options = { input: "", scale: 2, json: false };
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === "-h" || arg === "--help") { printHelp(); process.exit(0); }
|
||||
else if (arg === "-o" || arg === "--output") opts.output = args[++i];
|
||||
else if (arg === "-s" || arg === "--scale") {
|
||||
const s = Number(args[++i]);
|
||||
if (isNaN(s) || s <= 0) { console.error(`Invalid scale: ${args[i]}`); return null; }
|
||||
opts.scale = s;
|
||||
} else if (arg === "--json") opts.json = true;
|
||||
else if (!arg.startsWith("-") && !opts.input) opts.input = arg;
|
||||
}
|
||||
if (!opts.input) { console.error("Error: Input SVG file required"); printHelp(); return null; }
|
||||
return opts;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const opts = parseArgs(process.argv.slice(2));
|
||||
if (!opts) process.exit(1);
|
||||
|
||||
const input = resolve(opts.input);
|
||||
if (!existsSync(input)) { console.error(`Error: ${input} not found`); process.exit(1); }
|
||||
if (extname(input).toLowerCase() !== ".svg") { console.error("Error: Input must be an SVG file"); process.exit(1); }
|
||||
|
||||
try {
|
||||
const r = await convert(input, opts);
|
||||
if (opts.json) console.log(JSON.stringify({ input, ...r }, null, 2));
|
||||
else console.log(`${input} → ${r.output} (${r.width}×${r.height})`);
|
||||
} catch (e) {
|
||||
console.error(`Error: ${(e as Error).message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Loading…
Reference in New Issue