Notes & Decision Log
Format: PHASE — context — decision/finding.
Decisions
- Pre-P1 — JTBD framing: primary author is the LLM, not the human. This single reframe is the source of every downstream constraint (compact syntax, repairable errors, deterministic layout, no plugins).
- Pre-P1 — Surveyed Mermaid, PlantUML, Graphviz/DOT, D2. All are human-first. Decided not to fork or extend any of them — the failure modes are baked into the syntax design, not the implementation.
- P1 — Hand-rolled recursive descent over Tree-sitter. Tree-sitter grammar churn would leak into the LLM-facing surface (the syntax cheatsheet given to the model). Hand-rolling locks the grammar and lets us curate the error messages.
- P1 — Default direction =
LR. ~80% of architecture diagrams flow left-to-right; defaulting saves 1 line of source per diagram. - P1 — Implicit node declaration:
web -> apiauto-declares both as defaultrect. Saves tokens, matches LLM intuition. - P2 — Layout engine v0 left-to-right flow only; deferred sequence + state to P5/P6 to isolate the structural rules first.
- P2 — Integer coordinates only. Float math was tried briefly in P2 and rejected when snapshot tests showed byte-drift across runs. Integer-everything is what makes determinism a feature rather than an aspiration.
- P3 — Locked the shape set at 12. New shapes need a backward-compat policy (deferred to M2).
- P4 — Validator error model: structured
{kind, token, line, col, expected, example}. Theexamplefield is the secret — it gives the LLM a working snippet to copy from, not just a description. - P5 — Sequence diagrams added lifelines + async messages + activation bars. Kept the same
->operator family rather than introducing sequence-specific operators (constraint #1, single render path). - P6 — State machines + self-loops + initial/final pseudostates. Self-loops were the only shape that needed a dedicated layout subroutine (R26).
- P7 — R7 auto-clustering: nodes with ≥3 edges between them form an implicit group. Eliminated a whole category of “this diagram is technically right but visually messy” output.
- P7 — R12 lane width inheritance: vertical lanes inherit width from their widest child + 24px padding. Resolves the common “labels overflow the lane” failure.
- P8 — R19 edge label overlap avoidance: edge labels never overlap nodes — route around or shorten. Was the highest-impact visual rule in the LLM eval set.
- P9 — Visual style rules R31–R37: corner radius 6 / stroke 1.5 / icon padding 12 / label 14px / coherent color tokens. Picked once, never theme-able. A single aesthetic is part of the contract.
- P10 — Custom inline SVG icon support added late. Users wanted brand logos; the constraint was “don’t break determinism, don’t fetch, don’t add a plugin surface.” Solution: inline
<svg>into<defs>, reference via<use>, participate in layout via R33. - P11 — Astro over Next.js for the playground. Static-first, lighter, faster cold-start on CF Pages. Engine is a single ESM module imported by the playground’s
Previewcomponent. - P11 — Cloudflare Pages for hosting. Free tier, plays well with the existing Cloudflare zone + tunnels.
- P11 — 4-section docs: syntax / examples / layout rules / API — written in the order an LLM would consume them (grammar first, examples second, layout rules to debug visual issues, API last).
- P11 — npm publish deferred to M2. Real adoption comes from people pasting into the textarea; npm without a known consumer is over-engineering. Cross-ref: Ship over polish.
- P11 — Repo stays private through M1. M2 open-source decision (likely MIT) gated on having an external consumer to pull in.
Gotchas
- P1 — Recursive descent parser’s lookahead-by-one was insufficient for distinguishing
IDENT [shape]fromIDENT [(start of an array literal that doesn’t exist in the grammar but the LLM sometimes invents). Fixed by adding an explicit error message: “did you meanIDENT [rect]? square brackets only contain a shape name.” - P2 — Layout v0 produced overlapping nodes when two edges shared an endpoint at the same y-coordinate. Fixed by R15 (minimum 16px gap between siblings) before extending to other diagram families.
- P2 — Float coordinates drifted on snapshot tests across browsers (Chrome vs Firefox produced 1-pixel-off SVG). Root cause: subpixel text-width measurement. Killed all float math; layout now uses integer estimates for text width based on character count × glyph average.
- P3 — SVG
viewBoxinitial implementation used a fractionalwidth/height. Browsers rounded inconsistently. Fixed byMath.ceil()to the next integer and adjusting padding accordingly. - P4 — Early validator errors were terse (“token error”). LLMs couldn’t repair on them. Rewrote the error catalog to include the
expectedandexamplefields — repair rate jumped from ~40% to 88% on the same eval set. - P5 — Sequence-diagram activation bars overlapped when two messages were sent within the same “tick.” Fixed with R28 (semantic): bars on the same lifeline stack vertically with 4px gap.
- P6 — State machine self-loops always rendered above the node; looked cramped when a node had a label below. Added R26: self-loops route to the side with the most clearance.
- P7 — Auto-clustering (R7) initially triggered on 2 edges, producing spurious clusters for tiny graphs. Bumped the threshold to ≥3 edges after eval-set review.
- P8 — Edge routing’s first attempt used straight lines and detected overlaps post-hoc. Too slow for 50+ node graphs. Refactored to route edges through a grid of “lanes” between node rows; faster and produces cleaner output.
- P9 — R34 (label font-size 14px) initially rendered fine in Chrome but smaller in Safari due to default user-agent stylesheet differences. Fixed by emitting
font-familyandfont-sizeexplicitly on every<text>element. - P10 — User-pasted custom SVGs sometimes had inline
<script>tags. M1 mitigates by rendering only in the pasting user’s own browser session — not a shared-host risk. M2 web component will need a proper SVG sanitizer (DOMPurify) before exposing it on third-party sites. - P10 —
<use href="#icon-id">with aviewBox-less source SVG rendered at 0×0 in Safari. Workaround: parse the user’s SVG, inject a defaultviewBox="0 0 24 24"if missing. - P11 — Astro’s default
prefetch: truecaused the engine bundle to load on every docs page even when not used. Disabled prefetch for the engine module; saves bandwidth on docs-only sessions. - P11 — CF Pages build initially failed on
pnpmworkspaces because the default Node version was too old. Pinned viaNODE_VERSION=20env var in the Pages project settings. - P11 — URL-fragment encoding broke for source >2 KB on iOS Safari (URL length cap). Added a “source too long to share via URL” warning in the playground; M2 may add a paste-bin-style short link.
Reference links
- Mermaid: https://mermaid.js.org
- PlantUML: https://plantuml.com
- Graphviz / DOT: https://graphviz.org
- D2: https://d2lang.com
- ELK.js (considered, rejected): https://github.com/kieler/elkjs
- Astro: https://astro.build
- Cloudflare Pages: https://pages.cloudflare.com
Working-session log
| Phase | Hours | What | Outcome |
|---|---|---|---|
| Pre-P1 | ~4 h | JTBD framing + survey of Mermaid / PlantUML / Graphviz / D2 | Locked the “primary author is the LLM” constraint |
| P1 | ~10 h | Grammar lock + recursive-descent parser + AST | Parser handles all M1 grammar; source positions on every AST node |
| P2 | ~8 h | Layout engine v0 (LR flow only, integer coords) | Snapshot tests gate determinism |
| P3 | ~12 h | SVG renderer + 12 base shapes | All shapes render cleanly in Chrome/Safari/Firefox |
| P4 | ~6 h | Validator + structured error catalog | Repair-on-one-retry jumps from ~40% to 88% |
| P5 | ~8 h | Sequence diagrams (lifelines, async, activation bars) | Sequence eval subset passes |
| P6 | ~6 h | State machines + self-loops + pseudostates | State eval subset passes |
| P7 | ~6 h | R7 auto-clustering + R12 lane inheritance | Visual quality jumps on architecture eval set |
| P8 | ~8 h | Edge routing + R19 label overlap avoidance | Biggest single visual-quality win |
| P9 | ~5 h | Visual style rules R31–R37 | Single coherent aesthetic locked in |
| P10 | ~6 h | Custom inline SVG icon support | Late add; icon participates in layout via R33 |
| P11 | ~10 h | Astro playground + 4-section docs + CF Pages deploy | Public playground live; M1 done |
| Total | ~89 h | 11 phases over ~6 weeks part-time | Public playground + 94% LLM first-shot success |