21 KiB
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. viewBoxheight must bemax(those) + 20or 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:
<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:
<!-- 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:
- Lowest element is at y = ___ → viewBox H = ___ + 20 = ___ ✓
- Rightmost rect edge is at x = ___ → ≤ 640 ✓
- Longest label is "" ( chars) → needs width ___ → actual width ___ ✓
- Arrows checked against all boxes → no crossings ✓
- All connector paths have
fill="none"✓- All
<text>elements have a class ✓- Colors: ≤ 2 ramps → ___ and ___ → assigned by category ✓
- No hardcoded text fills ✓
- No comments in final output ✓
- No arrow bleed-through on translucent fills (mask rect if needed) ✓
- Legend sits ≥20px below all container boundaries ✓
If any of these feel fuzzy, the diagram isn't ready.