pi-visualize-code-changes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/package.json +42 -0
- package/preview.png +0 -0
- package/skills/visualize-code-changes/SKILL.md +331 -0
- package/skills/visualize-code-changes/assets/template.md +71 -0
- package/skills/visualize-code-changes/references/diagram-types.md +340 -0
- package/skills/visualize-code-changes/references/syntax-pitfalls.md +191 -0
- package/skills/visualize-code-changes/scripts/validate_mermaid.py +576 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Choosing and writing the right diagram
|
|
2
|
+
|
|
3
|
+
Read this when the change needs anything beyond a basic `flowchart TD`. Each
|
|
4
|
+
section gives the selection rationale, a working skeleton, and how to express
|
|
5
|
+
before/after/diff in that diagram type — the diff view is the awkward part for
|
|
6
|
+
non-flowchart types, and the answers differ.
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
1. [Picking a lens](#picking-a-lens)
|
|
11
|
+
2. [Control flow — flowchart](#control-flow--flowchart)
|
|
12
|
+
3. [Module & dependency graphs](#module--dependency-graphs)
|
|
13
|
+
4. [Sequence diagrams](#sequence-diagrams)
|
|
14
|
+
5. [State machines](#state-machines)
|
|
15
|
+
6. [Data flow](#data-flow)
|
|
16
|
+
7. [Class & schema diagrams](#class--schema-diagrams)
|
|
17
|
+
8. [When the merged diff view stops working](#when-the-merged-diff-view-stops-working)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Picking a lens
|
|
22
|
+
|
|
23
|
+
Ask what question the reader arrives with. The diagram type follows from the
|
|
24
|
+
question, not from the code's language or framework.
|
|
25
|
+
|
|
26
|
+
| Reader's question | Lens | Type |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| "Which path does execution take now?" | Control flow | `flowchart TD` |
|
|
29
|
+
| "What depends on what after the move?" | Dependency graph | `flowchart LR` |
|
|
30
|
+
| "Who calls whom, in what order?" | Sequence | `sequenceDiagram` |
|
|
31
|
+
| "What states can this be in?" | State machine | `stateDiagram-v2` |
|
|
32
|
+
| "How does the data get transformed?" | Data flow | `flowchart LR` |
|
|
33
|
+
| "What shape is this type/table now?" | Structure | `classDiagram` / `erDiagram` |
|
|
34
|
+
|
|
35
|
+
A change often admits two valid lenses — a new caching layer is both a control
|
|
36
|
+
flow change and a dependency change. When `AskUserQuestion` / `ask_user_question` is available, offer
|
|
37
|
+
the two strongest candidates rather than guessing. Two diagrams answering two
|
|
38
|
+
different questions is fine; two diagrams answering the same question is padding.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Control flow — flowchart
|
|
43
|
+
|
|
44
|
+
The default, and right for most logic changes. `TD` (top-down) suits branching and
|
|
45
|
+
error paths; `LR` suits pipelines.
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart TD
|
|
49
|
+
IN["handleUpload(file)"] --> V{"size < 10MB?"}
|
|
50
|
+
V -->|no| ERR["reject 413"]
|
|
51
|
+
V -->|yes| SCAN["virusScan(file)"]
|
|
52
|
+
SCAN --> S3["putObject(bucket)"]
|
|
53
|
+
S3 --> DB["insert files row"]
|
|
54
|
+
DB --> OK["201 + file id"]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Shape conventions readers already know, worth keeping consistent:
|
|
58
|
+
|
|
59
|
+
| Shape | Syntax | Means |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Rectangle | `A["doThing()"]` | a step |
|
|
62
|
+
| Diamond | `B{"ok?"}` | a decision |
|
|
63
|
+
| Rounded | `C("entry point")` | start/end |
|
|
64
|
+
| Cylinder | `D[("sessions")]` | datastore |
|
|
65
|
+
| Subroutine | `E[["renderPage()"]]` | call into another documented flow |
|
|
66
|
+
|
|
67
|
+
Label decision edges with the condition (`-->|yes|`). An unlabelled branch forces
|
|
68
|
+
the reader back into the source, which defeats the purpose.
|
|
69
|
+
|
|
70
|
+
**Diff view:** merged graph, `:::added` / `:::removed` / `:::changed` / `:::same`
|
|
71
|
+
per node, dotted red edges for dead paths. This is the case the SKILL.md example
|
|
72
|
+
covers.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Module & dependency graphs
|
|
77
|
+
|
|
78
|
+
For changes that move code rather than alter logic — extracting a module,
|
|
79
|
+
inverting a dependency, introducing a layer. `LR` reads best since dependency is
|
|
80
|
+
naturally left-to-right.
|
|
81
|
+
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart LR
|
|
84
|
+
subgraph api["api/"]
|
|
85
|
+
H["handlers.py"]
|
|
86
|
+
end
|
|
87
|
+
subgraph core["core/"]
|
|
88
|
+
S["service.py"]
|
|
89
|
+
R["repo.py"]
|
|
90
|
+
end
|
|
91
|
+
subgraph infra["infra/"]
|
|
92
|
+
DB[("postgres")]
|
|
93
|
+
end
|
|
94
|
+
H --> S
|
|
95
|
+
S --> R
|
|
96
|
+
R --> DB
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `subgraph` for the layer/package boundary — the boundary is usually the whole
|
|
100
|
+
point of the change. One node per module, not per function; if a module needs
|
|
101
|
+
internal detail, that is a second diagram.
|
|
102
|
+
|
|
103
|
+
**Diff view:** colour the *edges* as much as the nodes. Dependency changes are
|
|
104
|
+
edge changes, and a graph that only recolours boxes will look nearly identical
|
|
105
|
+
before and after. Show a removed dependency as a dotted red edge that still
|
|
106
|
+
appears in the merged view, so the reader sees what was severed.
|
|
107
|
+
|
|
108
|
+
### The fate map
|
|
109
|
+
|
|
110
|
+
When a change is mostly relocation — a module split, a package extraction, a
|
|
111
|
+
layer being carved out — plain colour coding fails. Every unit is "removed from
|
|
112
|
+
here, added over there", so everything lights up and the reader learns nothing
|
|
113
|
+
about where to spend attention.
|
|
114
|
+
|
|
115
|
+
Put the old container on one side and the new ones on the other, then draw one
|
|
116
|
+
edge per unit, labelled with what happened to it:
|
|
117
|
+
|
|
118
|
+
```mermaid
|
|
119
|
+
flowchart LR
|
|
120
|
+
subgraph OLD["billing.py — deleted"]
|
|
121
|
+
O_CS["calculate_subtotal"]:::same
|
|
122
|
+
O_GI["generate_invoice"]:::same
|
|
123
|
+
O_AT["apply_tax — flat rate"]:::removed
|
|
124
|
+
end
|
|
125
|
+
subgraph NEW["billing/ — new package"]
|
|
126
|
+
N_CS["calculator.calculate_subtotal"]:::same
|
|
127
|
+
N_GI["invoice.generate_invoice"]:::changed
|
|
128
|
+
N_AT["tax.apply_tax — brackets"]:::added
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
O_CS -->|"moved verbatim"| N_CS
|
|
132
|
+
O_GI -->|"moved, 3 lines changed"| N_GI
|
|
133
|
+
O_AT -.->|"deleted, not moved"| N_AT
|
|
134
|
+
|
|
135
|
+
classDef added fill:#d4f8d4,stroke:#2ea043,color:#1f2328
|
|
136
|
+
classDef removed fill:#ffd7d5,stroke:#cf222e,color:#1f2328
|
|
137
|
+
classDef changed fill:#fff5cc,stroke:#bf8700,color:#1f2328
|
|
138
|
+
classDef same fill:#f6f8fa,stroke:#8c959f,color:#1f2328
|
|
139
|
+
linkStyle 0 stroke:#8c959f
|
|
140
|
+
linkStyle 1 stroke:#bf8700
|
|
141
|
+
linkStyle 2 stroke:#cf222e
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The edge labels are the payload. `moved verbatim` means *skip this file*, which
|
|
145
|
+
is often the most valuable sentence in the whole document.
|
|
146
|
+
|
|
147
|
+
Earn those labels rather than guessing them. Git reports a wholesale file
|
|
148
|
+
replacement the same way whether code was relocated untouched or rewritten from
|
|
149
|
+
scratch, so compare the actual bodies:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git show main:billing.py | awk '/^def calculate_subtotal/,/^$/' > /tmp/before.txt
|
|
153
|
+
git show HEAD:billing/calculator.py | awk '/^def calculate_subtotal/,/^$/' > /tmp/after.txt
|
|
154
|
+
diff /tmp/before.txt /tmp/after.txt && echo "identical -- moved, not rewritten"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Pairing the fate map with a small table — file, what happened, review effort —
|
|
158
|
+
turns the diagram into something a reviewer can act on immediately.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Sequence diagrams
|
|
163
|
+
|
|
164
|
+
Right when ordering, round trips, or who-initiates-what is the substance —
|
|
165
|
+
auth handshakes, retries, webhook flows, anything touching multiple services.
|
|
166
|
+
A flowchart cannot express "then B replies to A" without lying about time.
|
|
167
|
+
|
|
168
|
+
```mermaid
|
|
169
|
+
sequenceDiagram
|
|
170
|
+
autonumber
|
|
171
|
+
participant C as Client
|
|
172
|
+
participant A as API
|
|
173
|
+
participant Q as Queue
|
|
174
|
+
participant W as Worker
|
|
175
|
+
|
|
176
|
+
C->>A: POST /jobs
|
|
177
|
+
A->>Q: enqueue(job)
|
|
178
|
+
A-->>C: 202 Accepted
|
|
179
|
+
Q->>W: deliver(job)
|
|
180
|
+
W-->>Q: ack
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`autonumber` earns its place — it gives the reader stable numbers to refer to.
|
|
184
|
+
Declare participants explicitly to control column order. Use `-->>` for replies
|
|
185
|
+
and `-)` for fire-and-forget.
|
|
186
|
+
|
|
187
|
+
Structural blocks, each closed by `end`:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
alt token expired
|
|
191
|
+
A-->>C: 401
|
|
192
|
+
else valid
|
|
193
|
+
A-->>C: 200
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
loop every 30s
|
|
197
|
+
W->>A: heartbeat
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
opt if cache miss
|
|
201
|
+
A->>DB: query
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Diff view:** sequence diagrams have no `classDef`, so the flowchart colouring
|
|
206
|
+
approach does not transfer. Two options that do work:
|
|
207
|
+
|
|
208
|
+
- `rect rgb(212,248,212)` blocks wrapping added exchanges, `rgb(255,215,213)` for
|
|
209
|
+
removed ones — the closest thing to the standard colour coding:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
rect rgb(212, 248, 212)
|
|
213
|
+
A->>T: verifyToken(jwt)
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- Annotate message text directly: `A->>B: [ADDED] verifyToken(jwt)` and
|
|
218
|
+
`Note over A,B: removed — checkPassword()`.
|
|
219
|
+
|
|
220
|
+
The `rect` approach is better when added/removed steps are contiguous; annotation
|
|
221
|
+
is better when changes are scattered through a long exchange.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## State machines
|
|
226
|
+
|
|
227
|
+
For lifecycle changes — order status, connection handling, job states, feature
|
|
228
|
+
flags. Use it when the change adds or removes a state or a transition, which is
|
|
229
|
+
exactly the kind of change a diff obscures badly.
|
|
230
|
+
|
|
231
|
+
```mermaid
|
|
232
|
+
stateDiagram-v2
|
|
233
|
+
[*] --> Pending
|
|
234
|
+
Pending --> Running: start()
|
|
235
|
+
Running --> Succeeded: exit 0
|
|
236
|
+
Running --> Failed: exit != 0
|
|
237
|
+
Failed --> Pending: retry (max 3)
|
|
238
|
+
Succeeded --> [*]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Label transitions with what triggers them. `[*]` marks entry and exit.
|
|
242
|
+
|
|
243
|
+
**Diff view:** `stateDiagram-v2` does support `classDef` and `:::`, so the normal
|
|
244
|
+
colour coding works:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Cancelled:::added
|
|
248
|
+
classDef added fill:#d4f8d4,stroke:#2ea043,color:#1f2328
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
A newly reachable state and a newly *unreachable* one are both easy to miss in a
|
|
252
|
+
diff and obvious in this view — call them out in prose too.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Data flow
|
|
257
|
+
|
|
258
|
+
For changes to how data is shaped and moved: a new transformation step, a changed
|
|
259
|
+
schema, a different serialisation boundary. `LR` with the payload shape in the
|
|
260
|
+
edge labels:
|
|
261
|
+
|
|
262
|
+
```mermaid
|
|
263
|
+
flowchart LR
|
|
264
|
+
SRC[("orders table")] -->|"rows"| X["normalise()"]
|
|
265
|
+
X -->|"OrderDTO[]"| AGG["aggregateByRegion()"]
|
|
266
|
+
AGG -->|"RegionSummary[]"| OUT["render CSV"]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Putting the type or shape on the edge is what distinguishes this from a control
|
|
270
|
+
flow diagram, and it is usually where the change lives — a field added to a DTO
|
|
271
|
+
shows up as an edge-label change even when every node is untouched.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Class & schema diagrams
|
|
276
|
+
|
|
277
|
+
For changes to type structure, inheritance, or database schema.
|
|
278
|
+
|
|
279
|
+
```mermaid
|
|
280
|
+
classDiagram
|
|
281
|
+
class Session {
|
|
282
|
+
+String id
|
|
283
|
+
+String userId
|
|
284
|
+
+DateTime expiresAt
|
|
285
|
+
+isValid() bool
|
|
286
|
+
}
|
|
287
|
+
class TokenSession {
|
|
288
|
+
+String jwt
|
|
289
|
+
}
|
|
290
|
+
Session <|-- TokenSession
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Relations: `<|--` inheritance, `*--` composition, `o--` aggregation, `-->`
|
|
294
|
+
association.
|
|
295
|
+
|
|
296
|
+
For database work `erDiagram` is usually the better fit:
|
|
297
|
+
|
|
298
|
+
```mermaid
|
|
299
|
+
erDiagram
|
|
300
|
+
USER ||--o{ SESSION : has
|
|
301
|
+
SESSION {
|
|
302
|
+
uuid id PK
|
|
303
|
+
uuid user_id FK
|
|
304
|
+
timestamptz expires_at
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Diff view:** neither type supports `classDef` reliably. Mark changed members in
|
|
309
|
+
the member text (`+jwt String << added >>`) or annotate with a `note`. For schema
|
|
310
|
+
changes, a small added/removed/changed table beneath the diagram is often clearer
|
|
311
|
+
than colour anyway, since column-level detail is what reviewers check.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## When the merged diff view stops working
|
|
316
|
+
|
|
317
|
+
The merged graph is the default because it puts the change in one picture. It
|
|
318
|
+
fails when before and after share almost no structure — a genuine rewrite rather
|
|
319
|
+
than an edit. Symptoms: more than about half the nodes are `added` or `removed`,
|
|
320
|
+
or the graph has two disconnected components that only meet at the entry point.
|
|
321
|
+
|
|
322
|
+
At that point the merged view is just the two diagrams overlaid with extra colour,
|
|
323
|
+
and readers do better with an explicit side-by-side:
|
|
324
|
+
|
|
325
|
+
```mermaid
|
|
326
|
+
flowchart LR
|
|
327
|
+
subgraph before["Before"]
|
|
328
|
+
direction TD
|
|
329
|
+
A1["parse()"] --> B1["validateManual()"]
|
|
330
|
+
end
|
|
331
|
+
subgraph after["After"]
|
|
332
|
+
direction TD
|
|
333
|
+
A2["parse()"] --> B2["schema.validate()"]
|
|
334
|
+
B2 --> C2["coerceTypes()"]
|
|
335
|
+
end
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Say in the prose why you switched — "this was a rewrite rather than an edit, so a
|
|
339
|
+
merged view would not be legible" tells the reader something true about the change
|
|
340
|
+
itself, which is useful information rather than an apology.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Mermaid syntax pitfalls
|
|
2
|
+
|
|
3
|
+
The failure modes that actually break generated diagrams, roughly in order of how
|
|
4
|
+
often they bite. Run `scripts/validate_mermaid.py` rather than trusting a visual
|
|
5
|
+
scan — several of these render *silently wrong* rather than erroring.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. [Unquoted characters in labels](#1-unquoted-characters-in-labels)
|
|
10
|
+
2. [Reserved words](#2-reserved-words)
|
|
11
|
+
3. [Theme-safe styling](#3-theme-safe-styling)
|
|
12
|
+
4. [Style classes](#4-style-classes)
|
|
13
|
+
5. [Edge styling by index](#5-edge-styling-by-index)
|
|
14
|
+
6. [Subgraphs](#6-subgraphs)
|
|
15
|
+
7. [Comments](#7-comments)
|
|
16
|
+
8. [Sequence-diagram specifics](#8-sequence-diagram-specifics)
|
|
17
|
+
9. [Node ids](#9-node-ids)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. Unquoted characters in labels
|
|
22
|
+
|
|
23
|
+
The number one break. `(`, `)`, `[`, `]`, `{`, `}`, and `|` terminate a label
|
|
24
|
+
early, and since code labels are full of call signatures this hits constantly.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
A[login(req)] ✗ Parse error
|
|
28
|
+
A["login(req)"] ✓
|
|
29
|
+
|
|
30
|
+
B[arr[0]] ✗
|
|
31
|
+
B["arr[0]"] ✓
|
|
32
|
+
|
|
33
|
+
C[map|filter] ✗
|
|
34
|
+
C["map|filter"] ✓
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Quote defensively — a quoted label never hurts, so wrap anything containing
|
|
38
|
+
`()[]{}|:,;`. For a literal double quote inside a label use `#quot;`, and for
|
|
39
|
+
other awkward characters use HTML entities: `#35;` is `#`, `#59;` is `;`.
|
|
40
|
+
|
|
41
|
+
Angle brackets need entities too, because Mermaid renders labels as HTML:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
D["Vec<String>"] ✗ renders as an empty tag
|
|
45
|
+
D["Vec<String>"] ✓
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 2. Reserved words
|
|
49
|
+
|
|
50
|
+
`end` breaks flowcharts when used as a node id or bare label — the parser reads it
|
|
51
|
+
as closing a `subgraph`:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
flowchart TD
|
|
55
|
+
start --> end ✗
|
|
56
|
+
start --> endNode ✓
|
|
57
|
+
start --> E["end"] ✓
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`graph`, `subgraph`, `class`, `click`, `style`, and `direction` are similarly
|
|
61
|
+
unsafe as bare ids. Capitalising (`End`) works but reads oddly; a descriptive
|
|
62
|
+
rename is better.
|
|
63
|
+
|
|
64
|
+
## 3. Theme-safe styling
|
|
65
|
+
|
|
66
|
+
Diagrams get read in both light and dark mode. `classDef` that sets only `fill`
|
|
67
|
+
leaves text colour inherited, so dark-mode readers get dark text on a light fill:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
classDef added fill:#d4f8d4 ✗ unreadable in dark mode
|
|
71
|
+
classDef added fill:#d4f8d4,stroke:#2ea043,color:#1f2328 ✓
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Always set `fill`, `stroke`, and `color` together. The palette in SKILL.md is
|
|
75
|
+
chosen to pass contrast in both themes.
|
|
76
|
+
|
|
77
|
+
Avoid `%%{init: {'theme':'dark'}}%%` — it pins one theme and looks broken for
|
|
78
|
+
half your readers.
|
|
79
|
+
|
|
80
|
+
## 4. Style classes
|
|
81
|
+
|
|
82
|
+
Referencing an undefined class is *not* an error. The diagram renders, silently
|
|
83
|
+
unstyled — so a visual check passes while the colour coding that carried your
|
|
84
|
+
meaning is gone.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
A["x"]:::added ← needs a matching classDef somewhere in the block
|
|
88
|
+
classDef added fill:#d4f8d4,stroke:#2ea043,color:#1f2328
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Two application forms:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
A["x"]:::added inline
|
|
95
|
+
class A,B,C added statement form, several nodes at once
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`classDef` must live in the same block as the nodes it styles; nothing carries
|
|
99
|
+
across fenced blocks. The validator flags this case specifically.
|
|
100
|
+
|
|
101
|
+
## 5. Edge styling by index
|
|
102
|
+
|
|
103
|
+
`linkStyle` takes 0-based indices counted in **declaration order across the whole
|
|
104
|
+
block**, including edges declared inside subgraphs:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
linkStyle 2,4 stroke:#cf222e,stroke-dasharray:5
|
|
108
|
+
linkStyle 3 stroke:#2ea043
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Two traps. An out-of-range index is a hard error (the validator catches it). A
|
|
112
|
+
merely *wrong* index is not — it silently paints the wrong edge, which is worse,
|
|
113
|
+
because the diagram then asserts something false. Recount after any edit that adds
|
|
114
|
+
or reorders edges.
|
|
115
|
+
|
|
116
|
+
Where dashed-vs-solid carries the meaning, encoding it in the arrow itself is
|
|
117
|
+
more robust than an index that drifts:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
A -.-> B dotted, survives reordering
|
|
121
|
+
A ==> B thick
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 6. Subgraphs
|
|
125
|
+
|
|
126
|
+
Give a subgraph an explicit id when the title has spaces, otherwise the title
|
|
127
|
+
becomes the id and breaks references:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
subgraph Auth layer ✗
|
|
131
|
+
subgraph auth["Auth layer"] ✓
|
|
132
|
+
...
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Every `subgraph` needs its own `end`. Edges may cross subgraph boundaries freely,
|
|
137
|
+
but declare them *outside* the subgraph blocks to keep layout predictable.
|
|
138
|
+
|
|
139
|
+
## 7. Comments
|
|
140
|
+
|
|
141
|
+
`%%` must start the line. A trailing comment after content is unreliable:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
A --> B %% explanation ✗ can swallow the edge
|
|
145
|
+
%% explanation
|
|
146
|
+
A --> B ✓
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 8. Sequence-diagram specifics
|
|
150
|
+
|
|
151
|
+
Messages use a different arrow vocabulary from flowcharts — flowchart arrows are
|
|
152
|
+
invalid here:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
A->>B: solid arrow, request
|
|
156
|
+
A-->>B: dashed arrow, response
|
|
157
|
+
A-)B: async
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Declare participants up front to fix left-to-right order; otherwise it is decided
|
|
161
|
+
by first appearance:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
sequenceDiagram
|
|
165
|
+
participant C as Client
|
|
166
|
+
participant S as Service
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Colons separate the message from its text, so a colon *inside* message text needs
|
|
170
|
+
escaping or rewording. `alt`/`else`/`opt`/`loop` blocks each close with `end`.
|
|
171
|
+
|
|
172
|
+
## 9. Node ids
|
|
173
|
+
|
|
174
|
+
Ids must start with a letter or underscore, and cannot contain spaces or hyphens.
|
|
175
|
+
The label carries the human-readable name:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
my-node["Label"] ✗ hyphen
|
|
179
|
+
order id["Label"] ✗ space
|
|
180
|
+
myNode["Label"] ✓
|
|
181
|
+
order_id["Label"] ✓
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Reusing an id re-references the same node — useful for converging paths, but a
|
|
185
|
+
silent merge if it was accidental. Declare the label once and reference the bare
|
|
186
|
+
id afterwards:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
A["parse()"] --> B
|
|
190
|
+
C --> A
|
|
191
|
+
```
|