← Back to project
● Shipped P1 Size M Developer tool

Diagram-Engine — Notes

Chronological decision log, gotchas, and working-session hours across the 11 build phases.

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.
  • P1Hand-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 -> api auto-declares both as default rect. 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.
  • P2Integer 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}. The example field 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).
  • P7R7 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.
  • P7R12 lane width inheritance: vertical lanes inherit width from their widest child + 24px padding. Resolves the common “labels overflow the lane” failure.
  • P8R19 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.
  • P10Custom 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.
  • P11Astro 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 Preview component.
  • P11Cloudflare 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).
  • P11npm 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] from IDENT [ (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 mean IDENT [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 viewBox initial implementation used a fractional width/height. Browsers rounded inconsistently. Fixed by Math.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 expected and example fields — 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-family and font-size explicitly 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 a viewBox-less source SVG rendered at 0×0 in Safari. Workaround: parse the user’s SVG, inject a default viewBox="0 0 24 24" if missing.
  • P11 — Astro’s default prefetch: true caused 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 pnpm workspaces because the default Node version was too old. Pinned via NODE_VERSION=20 env 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.

Working-session log

PhaseHoursWhatOutcome
Pre-P1~4 hJTBD framing + survey of Mermaid / PlantUML / Graphviz / D2Locked the “primary author is the LLM” constraint
P1~10 hGrammar lock + recursive-descent parser + ASTParser handles all M1 grammar; source positions on every AST node
P2~8 hLayout engine v0 (LR flow only, integer coords)Snapshot tests gate determinism
P3~12 hSVG renderer + 12 base shapesAll shapes render cleanly in Chrome/Safari/Firefox
P4~6 hValidator + structured error catalogRepair-on-one-retry jumps from ~40% to 88%
P5~8 hSequence diagrams (lifelines, async, activation bars)Sequence eval subset passes
P6~6 hState machines + self-loops + pseudostatesState eval subset passes
P7~6 hR7 auto-clustering + R12 lane inheritanceVisual quality jumps on architecture eval set
P8~8 hEdge routing + R19 label overlap avoidanceBiggest single visual-quality win
P9~5 hVisual style rules R31–R37Single coherent aesthetic locked in
P10~6 hCustom inline SVG icon supportLate add; icon participates in layout via R33
P11~10 hAstro playground + 4-section docs + CF Pages deployPublic playground live; M1 done
Total~89 h11 phases over ~6 weeks part-timePublic playground + 94% LLM first-shot success