agentic-mermaid 0.1.1 → 0.2.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.
Files changed (340) hide show
  1. package/AGENT_NATIVE.md +155 -91
  2. package/CHANGELOG.md +163 -4
  3. package/Instructions_for_agents.md +10 -10
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +31 -38
  6. package/THIRD_PARTY_NOTICES.md +29 -0
  7. package/assets/hero.png +0 -0
  8. package/dist/agent-core.d.ts +463 -0
  9. package/dist/agent-core.js +397 -0
  10. package/dist/agent.d.ts +73 -1868
  11. package/dist/agent.js +336 -37325
  12. package/dist/agentic-mermaid-mcp.js +7 -38399
  13. package/dist/am.js +33626 -40446
  14. package/dist/chunk-2P5FKUQC.js +3485 -0
  15. package/dist/chunk-2S3VSS6I.js +149 -0
  16. package/dist/chunk-2SILJDW2.js +37 -0
  17. package/dist/chunk-BJYMBT4I.js +61963 -0
  18. package/dist/chunk-FDM43XU7.js +615 -0
  19. package/dist/chunk-MYPDVZCO.js +4702 -0
  20. package/dist/index-DWPa2MJs.d.ts +6780 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +310 -33014
  23. package/dist/mcp-cli-5J47WEY3.js +17 -0
  24. package/docs/README.md +39 -31
  25. package/docs/agent-api-cookbook.md +44 -35
  26. package/docs/agent-mutation-policy.md +1 -1
  27. package/docs/agent-workflow-examples.md +2 -2
  28. package/docs/api.md +284 -77
  29. package/docs/ascii.md +26 -16
  30. package/docs/code-mode-impact.md +214 -0
  31. package/docs/comparison.md +23 -11
  32. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  33. package/docs/config.md +26 -10
  34. package/docs/custom-fonts.md +265 -0
  35. package/docs/custom-style-cookbook.md +130 -9
  36. package/docs/diagram-families.md +48 -23
  37. package/docs/features.md +59 -52
  38. package/docs/fork-differences.md +6 -3
  39. package/docs/getting-started.md +23 -16
  40. package/docs/issue-derived-test-cases.md +29 -22
  41. package/docs/mcp-code-mode-rationale.md +13 -6
  42. package/docs/mcp-http-transport.md +20 -13
  43. package/docs/mermaid-layout-complaints.md +66 -52
  44. package/docs/mutation-testing.md +145 -66
  45. package/docs/quality.md +23 -14
  46. package/docs/react.md +2 -2
  47. package/docs/schemas/style-spec.schema.json +1221 -30
  48. package/docs/style-authoring.md +241 -38
  49. package/docs/svg-semantic-contract.md +49 -0
  50. package/docs/testing-strategy.md +166 -58
  51. package/docs/theming.md +19 -32
  52. package/examples/styles/catalog.json +54 -0
  53. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  54. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  55. package/examples/styles/cupertino-prototype.mmd +10 -0
  56. package/examples/styles/cupertino-prototype.style.json +63 -0
  57. package/examples/styles/mid-century-report.style.json +2 -2
  58. package/examples/styles/star-chart-atlas.style.json +2 -1
  59. package/examples/styles/transit-route-map.style.json +2 -2
  60. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  61. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  62. package/package.json +85 -45
  63. package/server.json +2 -2
  64. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  65. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  66. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  67. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  68. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  69. package/TODO.md +0 -684
  70. package/dist/index-wjRjbsP-.d.ts +0 -1102
  71. package/docs/assets/improvements/box-start-connector.png +0 -0
  72. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  73. package/docs/assets/improvements/er-content-loss.png +0 -0
  74. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  75. package/docs/assets/improvements/gantt-family.png +0 -0
  76. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  77. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  78. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  79. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  80. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  81. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  82. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  83. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  84. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  85. package/docs/contributing/adding-diagram-types.md +0 -144
  86. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  87. package/docs/contributing/diagram-family-citizenship.md +0 -82
  88. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  89. package/docs/contributing/lessons-learned.md +0 -276
  90. package/docs/contributing/releasing.md +0 -82
  91. package/docs/contributing/visual-review-evidence.md +0 -53
  92. package/docs/design/families/README.md +0 -26
  93. package/docs/design/families/architecture-beta.md +0 -48
  94. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  95. package/docs/design/families/gantt-research.md +0 -137
  96. package/docs/design/families/gantt.md +0 -329
  97. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  98. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  99. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  100. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  101. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  102. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  103. package/docs/design/families/journey-migration-parity.md +0 -439
  104. package/docs/design/families/journey-section-overlap-after.png +0 -0
  105. package/docs/design/families/journey-section-overlap-before.png +0 -0
  106. package/docs/design/families/journey-usage-research.md +0 -423
  107. package/docs/design/families/journey.md +0 -153
  108. package/docs/design/families/xychart.md +0 -85
  109. package/docs/design/system/README.md +0 -45
  110. package/docs/design/system/abstraction-audit.md +0 -224
  111. package/docs/design/system/abstraction-recommendations.md +0 -655
  112. package/docs/design/system/architecture.mmd +0 -29
  113. package/docs/design/system/architecture.svg +0 -137
  114. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  115. package/docs/design/system/layout-rubric.md +0 -138
  116. package/docs/design/system/route-contracts.md +0 -848
  117. package/docs/design/system/source-preservation-ladder.md +0 -52
  118. package/docs/design/system/styles-rollout.md +0 -216
  119. package/docs/design/system/ugly-layouts.md +0 -119
  120. package/docs/layout-characterization/README.md +0 -245
  121. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  122. package/docs/layout-characterization/contact-sheet.md +0 -638
  123. package/docs/layout-characterization/properties.md +0 -281
  124. package/docs/layout-characterization/visual-quality.md +0 -160
  125. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  126. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  127. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  128. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  129. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  130. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  131. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  132. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  133. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  134. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  135. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  136. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  137. package/docs/project/divergences.md +0 -98
  138. package/docs/project/dogfooding-docs-strategy.md +0 -66
  139. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  140. package/docs/project/lessons-learned.md +0 -1019
  141. package/docs/project/remove-role-styling-plan.md +0 -176
  142. package/examples/agent-improve-auth-flow.ts +0 -206
  143. package/examples/agent-loop.ts +0 -53
  144. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  145. package/llms.txt +0 -104
  146. package/skill-evals/README.md +0 -77
  147. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  148. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  149. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  150. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  151. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  152. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  153. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  154. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  155. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  156. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  157. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  158. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  159. package/skill-evals/shared-benchmark.json +0 -510
  160. package/src/agent/analyze.ts +0 -157
  161. package/src/agent/apply.ts +0 -146
  162. package/src/agent/architecture-body.ts +0 -458
  163. package/src/agent/certificates.ts +0 -110
  164. package/src/agent/class-body.ts +0 -319
  165. package/src/agent/core.ts +0 -207
  166. package/src/agent/create.ts +0 -106
  167. package/src/agent/describe.ts +0 -368
  168. package/src/agent/er-body.ts +0 -224
  169. package/src/agent/facts.ts +0 -360
  170. package/src/agent/families-builtin.ts +0 -920
  171. package/src/agent/families.ts +0 -232
  172. package/src/agent/family-layouts.ts +0 -832
  173. package/src/agent/flowchart-body.ts +0 -372
  174. package/src/agent/flowchart-unsupported.ts +0 -243
  175. package/src/agent/font-coverage.ts +0 -288
  176. package/src/agent/gantt-body.ts +0 -495
  177. package/src/agent/index.ts +0 -10
  178. package/src/agent/journey-body.ts +0 -374
  179. package/src/agent/label-metrics.ts +0 -28
  180. package/src/agent/layout-to-rendered.ts +0 -63
  181. package/src/agent/mutate.ts +0 -97
  182. package/src/agent/mutation-ops.ts +0 -51
  183. package/src/agent/op-schema.ts +0 -443
  184. package/src/agent/parse.ts +0 -223
  185. package/src/agent/pie-body.ts +0 -270
  186. package/src/agent/png.ts +0 -171
  187. package/src/agent/quadrant-body.ts +0 -336
  188. package/src/agent/quality.ts +0 -363
  189. package/src/agent/readability-audit.ts +0 -92
  190. package/src/agent/rendered-route-audit.ts +0 -61
  191. package/src/agent/sequence-body.ts +0 -345
  192. package/src/agent/serialize.ts +0 -214
  193. package/src/agent/state-body.ts +0 -589
  194. package/src/agent/structural-count.ts +0 -150
  195. package/src/agent/timeline-body.ts +0 -280
  196. package/src/agent/trace-log.ts +0 -43
  197. package/src/agent/types.ts +0 -995
  198. package/src/agent/verify.ts +0 -603
  199. package/src/agent/xychart-body.ts +0 -472
  200. package/src/architecture/config.ts +0 -282
  201. package/src/architecture/layout.ts +0 -496
  202. package/src/architecture/parser.ts +0 -375
  203. package/src/architecture/renderer.ts +0 -611
  204. package/src/architecture/types.ts +0 -113
  205. package/src/ascii/ansi.ts +0 -423
  206. package/src/ascii/architecture.ts +0 -210
  207. package/src/ascii/canvas.ts +0 -443
  208. package/src/ascii/class-diagram.ts +0 -697
  209. package/src/ascii/converter.ts +0 -475
  210. package/src/ascii/draw.ts +0 -1693
  211. package/src/ascii/edge-bundling.ts +0 -328
  212. package/src/ascii/edge-routing.ts +0 -330
  213. package/src/ascii/er-diagram.ts +0 -439
  214. package/src/ascii/gantt.ts +0 -276
  215. package/src/ascii/grid.ts +0 -747
  216. package/src/ascii/index.ts +0 -196
  217. package/src/ascii/journey.ts +0 -169
  218. package/src/ascii/meta.ts +0 -244
  219. package/src/ascii/multiline-utils.ts +0 -80
  220. package/src/ascii/pathfinder.ts +0 -301
  221. package/src/ascii/pie.ts +0 -67
  222. package/src/ascii/quadrant.ts +0 -158
  223. package/src/ascii/reverse.ts +0 -216
  224. package/src/ascii/sequence.ts +0 -499
  225. package/src/ascii/shapes/circle.ts +0 -27
  226. package/src/ascii/shapes/corners.ts +0 -143
  227. package/src/ascii/shapes/diamond.ts +0 -27
  228. package/src/ascii/shapes/hexagon.ts +0 -27
  229. package/src/ascii/shapes/index.ts +0 -106
  230. package/src/ascii/shapes/rectangle.ts +0 -168
  231. package/src/ascii/shapes/rounded.ts +0 -27
  232. package/src/ascii/shapes/special.ts +0 -332
  233. package/src/ascii/shapes/stadium.ts +0 -110
  234. package/src/ascii/shapes/state.ts +0 -192
  235. package/src/ascii/shapes/types.ts +0 -73
  236. package/src/ascii/timeline.ts +0 -141
  237. package/src/ascii/types.ts +0 -312
  238. package/src/ascii/validate.ts +0 -120
  239. package/src/ascii/width.ts +0 -81
  240. package/src/ascii/wrap.ts +0 -131
  241. package/src/ascii/xychart.ts +0 -919
  242. package/src/browser.ts +0 -29
  243. package/src/class/layout.ts +0 -240
  244. package/src/class/parser.ts +0 -332
  245. package/src/class/renderer.ts +0 -638
  246. package/src/class/types.ts +0 -129
  247. package/src/cli/agent-instructions.ts +0 -1
  248. package/src/cli/am-bin.ts +0 -6
  249. package/src/cli/exit-codes.ts +0 -18
  250. package/src/cli/index.ts +0 -1132
  251. package/src/cli/init-agent.ts +0 -147
  252. package/src/cli/run-entrypoint.ts +0 -10
  253. package/src/color-resolver.ts +0 -152
  254. package/src/elk-instance.ts +0 -114
  255. package/src/er/layout.ts +0 -187
  256. package/src/er/parser.ts +0 -226
  257. package/src/er/renderer.ts +0 -792
  258. package/src/er/types.ts +0 -99
  259. package/src/family-rubric.ts +0 -425
  260. package/src/font-manifest.ts +0 -31
  261. package/src/gantt/layout.ts +0 -365
  262. package/src/gantt/parser.ts +0 -282
  263. package/src/gantt/pipeline.ts +0 -27
  264. package/src/gantt/renderer.ts +0 -414
  265. package/src/gantt/schedule.ts +0 -513
  266. package/src/gantt/types.ts +0 -255
  267. package/src/index.ts +0 -368
  268. package/src/journey/layout.ts +0 -554
  269. package/src/journey/parse-core.ts +0 -251
  270. package/src/journey/parser.ts +0 -101
  271. package/src/journey/renderer.ts +0 -917
  272. package/src/journey/types.ts +0 -144
  273. package/src/layout/geometry.ts +0 -182
  274. package/src/layout/pass.ts +0 -142
  275. package/src/layout/passes/index.ts +0 -3916
  276. package/src/layout-engine.ts +0 -1565
  277. package/src/layout-rubric.ts +0 -659
  278. package/src/mcp/artifacts.ts +0 -137
  279. package/src/mcp/dynamic-harness.ts +0 -31
  280. package/src/mcp/dynamic-modules.d.ts +0 -7
  281. package/src/mcp/facade.ts +0 -608
  282. package/src/mcp/harness-runtime.ts +0 -203
  283. package/src/mcp/hosted-server.ts +0 -468
  284. package/src/mcp/mcp-bin.ts +0 -3
  285. package/src/mcp/mcp-cli.ts +0 -82
  286. package/src/mcp/protocol.ts +0 -13
  287. package/src/mcp/sandbox.ts +0 -195
  288. package/src/mcp/sdk-decl.ts +0 -391
  289. package/src/mcp/server.ts +0 -362
  290. package/src/mcp/tool-surface.ts +0 -146
  291. package/src/mermaid-source.ts +0 -655
  292. package/src/multiline-utils.ts +0 -228
  293. package/src/parser.ts +0 -1031
  294. package/src/pie/layout.ts +0 -168
  295. package/src/pie/parser.ts +0 -136
  296. package/src/pie/renderer.ts +0 -234
  297. package/src/pie/types.ts +0 -82
  298. package/src/quadrant/layout.ts +0 -191
  299. package/src/quadrant/parser.ts +0 -183
  300. package/src/quadrant/renderer.ts +0 -322
  301. package/src/quadrant/types.ts +0 -113
  302. package/src/render-family-hooks.ts +0 -244
  303. package/src/renderer.ts +0 -1099
  304. package/src/route-contracts.ts +0 -2723
  305. package/src/scene/backend.ts +0 -76
  306. package/src/scene/fidelity.ts +0 -183
  307. package/src/scene/hybrid-backend.ts +0 -174
  308. package/src/scene/ir.ts +0 -181
  309. package/src/scene/marks.ts +0 -127
  310. package/src/scene/rough-backend.ts +0 -378
  311. package/src/scene/seed.ts +0 -46
  312. package/src/scene/style-registry.ts +0 -556
  313. package/src/sequence/layout.ts +0 -456
  314. package/src/sequence/parser.ts +0 -255
  315. package/src/sequence/renderer.ts +0 -604
  316. package/src/sequence/types.ts +0 -154
  317. package/src/shape-clipping.ts +0 -351
  318. package/src/shared/accessibility-directives.ts +0 -22
  319. package/src/shared/batched.ts +0 -61
  320. package/src/shared/color-math.ts +0 -93
  321. package/src/shared/svg-a11y.ts +0 -29
  322. package/src/shared/syntax-error.ts +0 -17
  323. package/src/shared/unicode-ranges.ts +0 -135
  324. package/src/styles.ts +0 -318
  325. package/src/svg-paths.ts +0 -28
  326. package/src/text-metrics.ts +0 -248
  327. package/src/theme.ts +0 -710
  328. package/src/timeline/layout.ts +0 -381
  329. package/src/timeline/parser.ts +0 -215
  330. package/src/timeline/renderer.ts +0 -538
  331. package/src/timeline/types.ts +0 -105
  332. package/src/types.ts +0 -462
  333. package/src/website-worker-modules.d.ts +0 -18
  334. package/src/xychart/axis-utils.ts +0 -109
  335. package/src/xychart/colors.ts +0 -124
  336. package/src/xychart/config.ts +0 -88
  337. package/src/xychart/layout.ts +0 -656
  338. package/src/xychart/parser.ts +0 -504
  339. package/src/xychart/renderer.ts +0 -640
  340. package/src/xychart/types.ts +0 -282
@@ -1,655 +0,0 @@
1
- # Abstraction recommendations — academic-literature review
2
-
3
- Status: recommendations from academic-literature review. Drafted 2026-06-20;
4
- reappraised and updated 2026-06-21 after the issue #71 implementation.
5
- Companion to [`abstraction-audit.md`](./abstraction-audit.md) (the historical pre-implementation
6
- snapshot and the source of issue numbers **I1–I9**, referenced throughout). Reconciled with the pre-existing
7
- design docs it overlaps — [`AGENT_NATIVE.md`](../../../AGENT_NATIVE.md) (agent stack),
8
- [`contributing/diagram-family-citizenship.md`](../../contributing/diagram-family-citizenship.md)
9
- (family×surface CI ratchet), and [`source-preservation-ladder.md`](./source-preservation-ladder.md)
10
- (structured\|opaque levels); see the audit's §5 for the agreement and the I1/I3/I7 refinements.
11
-
12
- ## 2026-06-21 reappraisal
13
-
14
- The original issue spec was rechecked against the implemented code, the
15
- `adewale/testing-best-practices` testing research, current competitor behavior, and fresh source
16
- checks on the literature. The core direction still holds, but two details were improved:
17
-
18
- 1. `FamilyPlugin.layout` should be a **source-context hook**, not a `body` hook. The stronger
19
- wording in the original roadmap ("layout the structured body directly") overfits L3/L4 bodies and
20
- is wrong for L1 opaque bodies, accessibility directives, frontmatter, and `%%init%%` preservation.
21
- The implemented contract uses `FamilyLayoutContext { source, options, renderOptions, colors }`.
22
- That still removes the duplicated public SVG/ASCII switches while keeping source-preservation
23
- semantics explicit.
24
- 2. `knownFamilies()` should not expose mutable `Map` insertion order. The reappraisal keeps the
25
- keyed registry but requires deterministic iteration: built-ins follow `BUILTIN_FAMILY_METADATA`;
26
- any external registrations sort lexically after the built-ins.
27
-
28
- ### Evidence Update
29
-
30
- - **Academic architecture:** Wadler's expression-problem framing still supports a registry of
31
- operations by family; TypeScript's discriminated-union/`never` exhaustiveness model supports the
32
- certificate split and exhaustive proof switches. MLIR's "dialects and lowering" lesson and
33
- Graphviz's attributed-graph/layout/output separation support multiple family-specific layout
34
- dialects, not one universal geometry IR.
35
- - **Competitive analysis:** Mermaid now documents many diagram families, frontmatter/config, and
36
- evolving layout/look support; it explicitly limits layout/look support to some diagram types today
37
- while expanding it. Graphviz exposes DOT, layout engines, output formats, and attributes as
38
- separate concerns. PlantUML supports several layout engines and output formats, but ASCII is only
39
- broadly documented for sequence diagrams. D2 documents layout-engine tradeoffs and engine-specific
40
- feature gaps. The pattern across competitors is **separated surfaces and explicit layout engines**,
41
- not a single geometry model for every diagram and output.
42
- - **Theming practice:** Mermaid's `themeVariables` remain a compatibility requirement. The 2026
43
- Design Tokens Community Group draft reinforces a named-token/alias direction, but it is explicitly
44
- a preview and not a W3C standard. The best current move is therefore the implemented
45
- `DiagramColors`/CSS-variable waist with semantic role slots, not importing a full design-token JSON
46
- object into the public API.
47
- - **Testing practice:** The testing-best-practices guidance maps directly to this refactor:
48
- characterization/snapshot tests for byte-stable rendering, property tests for parser/config/layout
49
- invariants, doc-sync tests for registry/docs drift, visual/pixel contracts where SVG appearance is
50
- the output, and mutation lanes as adequacy checks. The implementation added direct registry dispatch
51
- coverage (`render-family-hooks.test.ts`) and rides the existing 4k+ suite, property tests, visual
52
- contracts, and mutation configuration.
53
-
54
- ### Updated Closure Matrix
55
-
56
- | Issue | Original claim | Reappraisal | Implemented/spec outcome |
57
- |---|---|---|---|
58
- | I1 | Three dispatch mechanisms should converge on `FamilyPlugin`. | Still best. Registry iteration also needs deterministic order. | SVG and ASCII route through `layout`/`renderSvg`/`renderAscii` hooks; `knownFamilies()` is metadata-ordered. |
59
- | I2 | Renderer signatures should collapse to `RenderContext`. | Still best; keep context thin. | All SVG renderers consume `{ positioned, colors, options }`. |
60
- | I3 | Add a shared `PositionedDiagram` marker but do not invent a universal IR. | Still best; competitors and MLIR/Graphviz reinforce dialect-style geometry. | `PositionedDiagram` exists; family positioned types remain specialized. |
61
- | I4 | Push parse/layout/render leaks down. | Mostly best; architecture legitimately needs layout metrics, not whole visual config. | Pie colors are render-time, xychart frontmatter is post-parse middleware, Gantt has a named pipeline, architecture passes layout metrics and render visual separately. |
62
- | I5 | Consolidate color models onto one internal waist. | Still best; do not adopt full design-token spec yet. | `resolveDiagramColors` normalizes options/themes/themeVariables; inline style resolvers and role color slots feed families. |
63
- | I6 | Split fake certificate union by what each certificate proves. | Still best. | `EdgeRouteCertificate`, `FamilyEdgeRouteCertificate`, and `RegionContainmentCertificate` are explicit; `layoutCertificateProof` exhaustively classifies them. |
64
- | I7 | Prefer structured-body layout over reparsing `canonicalSource`. | Revised. Prefer registered **layout hooks over duplicated public dispatch**, but keep source context as the hook input. Body-only layout is future work only for families that can prove it is lossless. | `FamilyPlugin.layout(ctx)` owns source-to-positioned layout; structured render paths avoid stale `canonicalSource` where supported without breaking opaque bodies. |
65
- | I8 | Keep ASCII geometry isolated; share only family-set/dispatch. | Still best; PlantUML/D2 reinforce output-specific geometry. | ASCII uses `renderAscii` hooks but retains grid layout/A* internals. |
66
- | I9 | Delete vestigial re-export and dead parameters; normalize names opportunistically. | Still best, but broad rename churn is lower priority than behavior-preserving closure. | `src/layout.ts` is gone and unused renderer `_options` are folded into `RenderContext`; broad naming normalization remains optional churn, not issue-blocking. |
67
-
68
- > Web access note: WebSearch/WebFetch **were available** in this environment. Every citation
69
- > below was bibliographically verified against authoritative records (dblp, arXiv, official
70
- > project pages, publisher catalogs). A handful of canonical landing pages return HTTP 403/402
71
- > to automated fetches (ACM DL, IEEE Xplore, Wiley) — those are flagged inline and an open mirror
72
- > is given where one exists. No URLs or page numbers were fabricated. See **References** for the
73
- > full list with verification status.
74
-
75
- ---
76
-
77
- ## 0. The shape of the problem, in the literature's terms
78
-
79
- The audit's baseline finding — the same ~12 diagram **families** were processed by ~5 **operations**
80
- (parse, layout, render-SVG, render-ASCII, mutate, verify) across **three** stacks (SVG, ASCII,
81
- agent IR) with **three** dispatch mechanisms — is, almost exactly, two classic problems stacked
82
- on top of each other:
83
-
84
- 1. **The Expression Problem** (Wadler 1998) along the *families × operations* plane. We want to
85
- add new families *and* new operations without editing everything. The agent stack's
86
- `FamilyPlugin` registry is a partial solution on this plane; the SVG and ASCII `switch`
87
- statements are the textbook *failing* decomposition Wadler describes.
88
- 2. **Two-dimensional variability** (GoF **Bridge**) along the *families × surfaces* plane. We have
89
- M families that must each be expressed over N surfaces (SVG / PNG / ASCII / agent IR). A single
90
- inheritance/dispatch hierarchy forces an M×N explosion; Bridge's whole motivation is collapsing
91
- that product into a sum (M + N).
92
-
93
- Two framing facts constrain every recommendation:
94
-
95
- - **Determinism is a hard constraint** (CLAUDE.md: identical input → identical geometry). This is
96
- the single biggest reason several theoretically attractive solutions (multimethods, import-order
97
- registries) are *wrong here*: a mutable global registry populated at import time introduces
98
- registration-order sensitivity, which is exactly the kind of hidden nondeterminism the repo
99
- forbids. Any registry we lean on must have a **fixed, explicit** registration order.
100
- - **The status quo is not naïve.** A discriminated union + `switch` is the **ADT pole** of Cook's
101
- duality (OOPSLA 2009): operations are cheap, *variants* are costly. TypeScript's `never`-based
102
- exhaustiveness check makes "I added a family" a compile error at every unhandled
103
- site — a real, free safety net we should *preserve*, not trade away for runtime polymorphism.
104
-
105
- The literature does **not** say "unify everything." Metz ("The Wrong Abstraction") and Moseley &
106
- Marks ("Out of the Tar Pit") jointly warn that collapsing parallel stacks is a win only when the
107
- removed duplication is *accidental*, not *essential*. The ASCII stack (I8) is the clearest case
108
- where the duplication is essential and isolation is **correct as-is** (see §I8).
109
-
110
- ### Which pattern opens which axis (the cheat sheet)
111
-
112
- | Approach | New operation | New family | Fit for this repo (deterministic, union + switch, TS) |
113
- |---|---|---|---|
114
- | Discriminated union + `switch` (status quo) | easy | **hard** (edit every switch) | native, deterministic, exhaustiveness-checked |
115
- | Visitor (GoF) | easy | **hard** (GoF's own listed liability) | inverts our easy axis; adds ceremony — wrong direction |
116
- | Object Algebras (Oliveira & Cook) | easy | easy | symmetric but boilerplate-heavy; loses inspectable data |
117
- | Tagless-final (Carette/Kiselyov/Shan) | easy | easy | fights TS structural typing + breaks JSON serialization |
118
- | Multimethods (Clojure/Julia) | easy | easy | non-idiomatic; global registry → **order nondeterminism** |
119
- | **Plugin registry + record-of-functions** (the agent stack) | easy | easy | **best fit** — keeps unions internally, opens both axes at the seam |
120
-
121
- The recommendation that runs through everything below: **generalize the pattern the agent stack
122
- already proved** (`FamilyPlugin` as a record of per-family functions in an explicitly-ordered
123
- registry), rather than importing GoF class hierarchies or FP encodings that TypeScript renders
124
- awkward. This is the Microkernel/plug-in architecture (POSA1) realized in the idiom the codebase
125
- already uses, and it is the concrete form of "make the waist thin" (the hourglass principle).
126
-
127
- ---
128
-
129
- ## I1 — Three dispatch mechanisms; the best one is least used
130
-
131
- **(a) Lens.** This is the **Expression Problem** (Wadler 1998) plus the **Microkernel/plug-in**
132
- architectural pattern (POSA1, Buschmann et al. 1996). The agent stack's
133
- `FamilyPlugin` in `Map<DiagramKind, FamilyPlugin>` (`src/agent/families.ts`) *is* a microkernel: a
134
- minimal core that owns only registration + dispatch, with each family a plug-in. At audit time, the
135
- SVG and ASCII public entrypoints also had hand-written `switch(diagramType)` statements; that was the
136
- decomposition Wadler shows fails — adding a family meant editing three sites that could not
137
- drift-check each other.
138
-
139
- **(b) Prescription.** Keep one core that knows only the registration/dispatch contract; deliver all
140
- per-family behavior as registered plug-ins (Parnas 1972: hide the decision "which family is this?"
141
- behind one module). Add the missing operations to the existing plug-in interface.
142
-
143
- **(c) Recommendation.** Extend `FamilyPlugin` with the two operations it lacks:
144
-
145
- ```
146
- renderSvg?: (ctx: RenderContext) => string // see I2 for RenderContext
147
- renderAscii?: (ctx: AsciiContext) => string
148
- layout?: (ctx: FamilyLayoutContext) => FamilyLayoutResult | PositionedDiagram
149
- ```
150
-
151
- The now-implemented end state routes public SVG and ASCII rendering through keyed registry lookup
152
- (`getFamily(kind)`) and the hooks above. If this pattern is repeated in another surface, keep the same
153
- incremental migration rule used here: move one family at a time behind snapshot tests, then delete the
154
- fallback dispatch only after every family has a hook.
155
-
156
- **(d) Tradeoffs / determinism risk.** The registry is a `Map` populated at import in
157
- `families-builtin.ts`. **Risk:** if dispatch ever iterates the registry (e.g. `knownFamilies()`
158
- feeding a render order), `Map` insertion order makes output import-order-sensitive. **Mitigation:**
159
- dispatch is always *keyed* (`getFamily(kind)`), never *iterated* for geometry; keep it that way, and
160
- sort any iteration by the fixed `BUILTIN_FAMILY_METADATA` array order (`families.ts:77`), which is
161
- source-defined and stable. With keyed lookup, determinism is unaffected — the `Map` is a lookup
162
- table, not an ordering.
163
-
164
- **(e) Where theory doesn't transfer.** Wadler's full bar (separate *compilation*, open to
165
- *third-party* extension) is stronger than we need: families are in-tree and finite. The microkernel
166
- "earns its keep" argument assumes numerous or third-party plug-ins; here the registry's value is not
167
- extensibility-for-strangers but **drift elimination** (one dispatch site instead of three). That is
168
- still decisive — I1 is the audit's "highest-leverage target" and this is **mechanical, low-risk,
169
- high-value** — but justify it as *de-duplication of dispatch*, not as open extensibility we won't use.
170
-
171
- ---
172
-
173
- ## I2 — Renderer signature drift + redundant `(colors, font, transparent, options)` threading
174
-
175
- **(a) Lens.** **Lampson's "do one thing well" / simple predictable interfaces** (Hints, 1983) and
176
- **Parameter Object** discipline. The canonical `renderSvg(positioned, colors, font='Inter',
177
- transparent=false, options={})` (`src/renderer.ts:37`) has drifted into 4-, 6-, and 7-arg variants
178
- (`renderTimelineSvg` has 7; `renderArchitectureSvg`'s `visual` *replaces* `options`;
179
- `renderPie/QuadrantSvg` accept `_options` they ignore). Three positional args are already
180
- redundant: `font` is folded into `colors` (`resolveDiagramColors(options, config, font)` sets
181
- `DiagramColors.font`), and `transparent` is a lone boolean.
182
-
183
- **(b) Prescription.** Collapse the positional tail into one **capability/context object** — a single
184
- predictable interface every renderer accepts (Lampson; Strategy's "one interface, many
185
- implementations"). An unused `_options` parameter advertises a contract the function does not honor;
186
- remove it (this also closes part of I9).
187
-
188
- **(c) Recommendation.** Introduce
189
-
190
- ```
191
- interface RenderContext { positioned: PositionedDiagram; colors: DiagramColors; options: RenderOptions }
192
- ```
193
-
194
- with `font` read from `colors.font` and `transparent` moved into `options` (or `colors`). Family-specific
195
- extras become *typed fields on a per-family options slice*, not positional args:
196
- `options.timeline`, `options.xychart.interactive`, `options.architecture.visual`. Every `render*Svg`
197
- becomes `(ctx: RenderContext) => string`. This is a no-op on geometry, so it is pure refactor.
198
-
199
- **(d) Tradeoffs / determinism risk.** Essentially none for geometry — same inputs, same outputs;
200
- verify with snapshot tests. Minor risk: defaulting (`font='Inter'`, `transparent=false`) must be
201
- applied at the *boundary* (`resolveDiagramColors` plus the public render entrypoint's context
202
- construction) so no call site silently changes a default. Make the defaults explicit in one
203
- constructor, not scattered.
204
-
205
- **(e) Where theory doesn't transfer.** A Parameter Object can become a god-bag if every family dumps
206
- unrelated config into it. Keep `RenderContext` thin (3 fields) and push family-specifics into a typed,
207
- optional, **per-family** sub-slice so the shared type stays a narrow waist rather than a union of
208
- everything. This dovetails with I1: `FamilyPlugin.renderSvg(ctx)` is the natural consumer.
209
-
210
- ---
211
-
212
- ## I3 — No shared `Positioned*` contract; two parallel layout-output models
213
-
214
- **(a) Lens.** This is the **single-IR vs. many-dialects** question (LLVM, CGO 2004; MLIR, arXiv
215
- 2002.11054) crossed with the **graph-drawing layout/render boundary** (Graphviz GN99; ELK). The 11
216
- `Positioned*`/`*LayoutResult` types each re-declare `width`/`height` with no base interface
217
- (`src/types.ts:91,303,…`), and the agent stack carries a *second* normalized model, `RenderedLayout`
218
- (`agent/types.ts:908`), that the SVG stack never consumes. "A laid-out diagram" has no canonical type.
219
-
220
- **(b) Prescription.** Graphviz and ELK both prescribe a clean layout→render boundary communicating
221
- through *one* annotated-geometry structure: layout emits positions, renderers consume them. LLVM
222
- prescribes a single shared IR at the waist. **But** MLIR's caution is the more honest fit: when the
223
- levels genuinely differ, model them as distinct dialects and *lower progressively* rather than forcing
224
- one universal type.
225
-
226
- **(c) Recommendation — the calibrated version.** Do **not** attempt to merge all 11 `Positioned*`
227
- types into one universal geometry IR (see (e)). Instead, two cheap, high-value moves:
228
-
229
- 1. **Introduce a marker base interface** the 11 types extend:
230
- `interface PositionedDiagram { width: number; height: number }`. This is the *thin waist* — it
231
- gives `RenderContext`, `FamilyPlugin.layout`, and PNG/SVG entrypoints **one** type to name without
232
- forcing structural unification of the bodies. Minimal, mechanical, and it makes I2's `RenderContext`
233
- well-typed.
234
- 2. **Make the `PositionedGraph → RenderedLayout` lowering explicit.** Per `AGENT_NATIVE.md` §1,
235
- `RenderedLayout` (the layout JSON) is the *canonical determinism/equality artifact* — "two render
236
- results are equal iff their layout JSON is structurally equal," pinned by the cross-process and
237
- drift-sentinel tests — so it is **not** a debug view to demote. The two models sit at genuinely
238
- different levels (MLIR-style dialects): `PositionedDiagram` is SVG-facing geometry, `RenderedLayout`
239
- is the surface-neutral equality oracle. The fix is to make one *derive from* the other through an
240
- explicit lowering (`agent/layout-to-rendered.ts`), never built in parallel, so the two cannot diverge.
241
-
242
- **(d) Tradeoffs / determinism risk.** Adding a marker base is zero-runtime and deterministic. The real
243
- risk is over-reach: a too-rich shared base (ports, edges, groups) would force every family to satisfy
244
- fields it doesn't have, re-creating the I2 god-bag at the type level. Keep the base to
245
- `{ width, height }` and let families add their own fields — this is structurally what they already do.
246
-
247
- **(e) Where theory doesn't transfer (important).** LLVM's single-IR success depended on the IR being
248
- **genuinely universal** across real languages and targets. Diagram `Positioned*` models may be too
249
- family-specific to unify (a Gantt timeline's bars, a sequence diagram's lifelines, and a pie's wedges
250
- share little beyond a bounding box). Forcing one IR here risks Metz's "wrong abstraction": a union type
251
- that accretes conditionals. The marker-base + progressive-projection design captures the *real* shared
252
- contract (a bounding box and a render target) without paying for unification we'd immediately special-case.
253
-
254
- ---
255
-
256
- ## I4 — Layering leaks: render/config concerns pushed up into parse and layout
257
-
258
- **(a) Lens.** Direct hit for **Parnas (1972/1979)** information hiding and the **Graphviz/ELK
259
- layout-vs-render boundary**. The leaks are decisions placed in the wrong module:
260
- - Audit-time `layoutPieChart(chart, options, colors)` — layout consumed `DiagramColors` to assign slice colors.
261
- - `layoutArchitectureDiagram(diagram, options, visual)` + `resolveArchitectureVisualConfig` — visual
262
- config threaded through *both* layout and render, bypassing `options`.
263
- - Audit-time `parseXYChart(lines, frontmatter)` — theme/frontmatter was baked in during *parsing*.
264
- - Gantt is a hand-wired **4-stage** pipeline in `index.ts:404`.
265
-
266
- ELK's prescription is unambiguous: layout is a *pure geometry service*; color and visual styling are
267
- rendering concerns downstream of coordinates.
268
-
269
- **(b) Prescription.** Push each decision down to the layer that owns it (Parnas: hide the decision in
270
- one module behind a stable interface). Color/visual resolution belongs at **render**; frontmatter
271
- application belongs in a **post-parse middleware** stage, not inside the parser.
272
-
273
- **(c) Recommendation.**
274
- - **Pie:** move slice-color assignment out of `layoutPieChart` into the pie renderer. Layout emits
275
- *uncolored* wedge geometry (angles, radii); the renderer colors them from `ctx.colors`. Determinism
276
- is preserved because color assignment is a pure function of slice index/order.
277
- - **Architecture:** resolve `visual` at render (`renderArchitectureSvg` reads `ctx.options.architecture.visual`);
278
- if layout genuinely needs a *metric* (e.g. icon size affects box size), pass that **one number**
279
- explicitly, not the whole visual config. This is the legitimate exception — note it honestly.
280
- - **XY chart:** model frontmatter as **post-parse middleware**, exactly as Gantt already does
281
- (`applyGanttFrontmatterConfig`). `parseXYChart` returns the structural model; a separate
282
- `applyXYChartFrontmatter` stage folds theme in. This makes the two pipelines *consistent* (I9 too).
283
- - **Gantt:** the 4-stage pipeline is **not a leak — it is correct** (see (e)); formalize it as a named
284
- pipeline rather than inline wiring in `index.ts:404`.
285
-
286
- **(d) Tradeoffs / determinism risk.** Moving color assignment from layout to render must keep the
287
- *same deterministic ordering* (slice order, palette index). This is the highest-determinism-risk item
288
- in I4: a snapshot diff is mandatory and the palette-index function must be lifted verbatim, not
289
- re-derived. Architecture's icon-size-affects-layout coupling means you cannot fully purify its layout;
290
- document the residual.
291
-
292
- **(e) Where theory doesn't transfer.** "Parsing must be pure" is too strong. Gantt's multi-stage shape
293
- (`parse → applyFrontmatter → resolveSchedule → layout`) is the **textbook Sugiyama-style staged
294
- pipeline** (Sugiyama/Tagawa/Toda 1981) and a Parnas "uses"-hierarchy done right — each stage consumes
295
- the prior stage's output. The fix for Gantt is to *name and document* the pipeline, not flatten it.
296
- Likewise, architecture's metric coupling (icon size → box size) is *essential* layout input, not a leak.
297
-
298
- ---
299
-
300
- ## I5 — Styling: five parallel color models; N ways to set one property
301
-
302
- **(a) Lens.** **Parnas information hiding** + the **narrow-waist / hourglass** principle (Beck, CACM
303
- 2019) + **"Parse, don't validate"** (Alexis King 2019). Five color models
304
- (`RenderOptions → DiagramColors → ResolvedColors`, plus `MermaidThemeVariables` and
305
- `classDefs/nodeStyles/linkStyles`) mean **node fill is reachable 5 ways** and **group border 3 ways**
306
- (audit §I5). That is five representations of one decision — the opposite of a narrow waist.
307
-
308
- **(b) Prescription.** Pick **one internal color contract** (the waist) and *normalize every external
309
- spelling into it at the boundary* (King: parse the many input dialects into one precise internal type,
310
- once, so downstream code never re-resolves). Everything below the waist sees only the canonical model.
311
-
312
- **(c) Recommendation.** Make **`DiagramColors` (+ the CSS-custom-property layer) the single internal
313
- contract.** Treat `MermaidThemeVariables`, `classDefs/nodeStyles/linkStyles`, and `RenderOptions` as
314
- **input dialects** that a boundary function (`resolveDiagramColors`) *parses* into `DiagramColors`
315
- — after which no renderer consults the raw dialects. Concretely: the precedence rule "classDef beats
316
- themeVariables beats RenderOptions" lives in **one** resolver, not scattered across families. Keep the
317
- public custom-style surface to Style + Palette, and let built-in looks map into the internal style face
318
- so pie/quadrant/gantt/architecture reach typography, spacing, and color through the same waist.
319
-
320
- **(d) Tradeoffs / determinism risk.** Color resolution is already deterministic; the risk is *changing
321
- precedence* during consolidation. Lock current precedence with tests *before* refactoring (golden
322
- renders per input dialect), then prove the consolidated resolver reproduces them. This is **structural,
323
- medium-risk** — touch one resolver, verify byte-identical output.
324
-
325
- **(e) Where theory doesn't transfer.** Five models did not appear by accident — `MermaidThemeVariables`
326
- exists for **Mermaid compatibility**, `classDefs` for **inline-directive fidelity**. The waist must
327
- *absorb* these dialects at the boundary, not abolish them; users still author them. "One color model"
328
- is the *internal* invariant, not a user-facing reduction. Over-collapsing (dropping classDef support to
329
- simplify) would be a correctness regression, not a cleanup.
330
-
331
- ---
332
-
333
- ## I6 — "Route contracts" is two abstractions sharing one type union
334
-
335
- **(a) Lens.** **"Making illegal states unrepresentable"** (Minsky) and **ADTs + exhaustiveness checking**.
336
- At audit time, `LayoutRouteCertificate = RouteCertificate | FamilyRouteCertificate` looked unified
337
- but the arms shared neither producer, consumer, nor lifecycle:
338
- - `RouteCertificate` (flowchart) was keyed by `edgeIndex`, carried `invariant: 'straight' | …`, and
339
- was produced in **core** (`route-contracts.ts`, attached to `PositionedEdge.routeCertificate`).
340
- - The old `FamilyRouteCertificate` shape mixed edge proofs (class/ER, architecture, sequence) with
341
- element/region proofs (timeline/xychart/pie/quadrant/gantt), so some members were keyed by
342
- `edgeIndex` while others were keyed by `elementId`.
343
-
344
- A union whose arms have different *keys* (`edgeIndex` vs `elementId`) and different lifecycles is a
345
- *structural* union, not a *semantic* one — it permits illegal states (a "certificate" with no producer
346
- on a given path).
347
-
348
- **(b) Prescription.** Minsky: the type should make the real distinctions visible. Either (i) **split the
349
- type** to reflect that there are genuinely two-or-three different certificate concepts, or (ii) **unify
350
- the lifecycle** by producing family certs in the core layout path so the union becomes real.
351
-
352
- **(c) Recommendation — split, don't fake-unify.** Rename to expose reality:
353
- The implemented split names what each certificate proves:
354
- `EdgeRouteCertificate = RouteCertificate | FamilyEdgeRouteCertificate` for edge-indexed route proofs,
355
- and `RegionContainmentCertificate` for element/region containment proofs. The umbrella
356
- `LayoutRouteCertificate` remains only as a debug-sidecar sum whose members are **named by what they
357
- prove**. `layoutCertificateProof` switches exhaustively over those names so a new certificate kind is
358
- a compile error, not a silent gap.
359
-
360
- **(d) Tradeoffs / determinism risk.** This is a **type-level rename + redocument** with no geometry
361
- change — low determinism risk. The discipline risk is that the agent verify path and core route path
362
- must agree on which certs exist where; an exhaustive `switch` with a `never` default enforces it.
363
-
364
- **(e) Where theory doesn't transfer.** Option (ii) — "produce family certs in core layout so the union
365
- is real" — is the *theoretically cleaner* unification but likely the **wrong call**: family layouts live
366
- in `agent/family-layouts.ts` precisely because the core SVG render path for families doesn't need
367
- certificates to draw. Forcing the core to mint containment certs it never reads is work for symmetry's
368
- sake (Metz). **Prefer the split (i).** Honesty over false unification.
369
-
370
- ---
371
-
372
- ## I7 — The agent layer is the reference design — with two seams
373
-
374
- **(a) Lens.** **"Parse, don't validate"** (King 2019) and **"illegal states unrepresentable"** (Minsky).
375
- `ValidDiagram { kind, meta, body, source, canonicalSource }` + `DiagramBody` (12 structured bodies | the
376
- `{ kind: 'opaque'; family; source }` fallback, `agent/types.ts:501,521`) + `Result<T,E>`
377
- (`agent/types.ts:14`) is exactly King's "parse the input into a precisely-typed value at the boundary."
378
- `ValidDiagram` *is* the parsed type; the opaque arm is the honest "we recognized the family but not the
379
- syntax" state — a model worth celebrating, not fixing. The two seams are real, though:
380
-
381
- - **(seam a) Mutation ops are duck-typed:** `mutate(body, op: AnyMutationOp)` with each family
382
- narrowing via `if (body.kind !== 'class') return err(...)`.
383
- - **(seam b) Layout re-parses `canonicalSource`** through the core per-family parser instead of using
384
- the family registry as the owner of layout dispatch — a text round-trip that can couple agent layout
385
- to *serializer fidelity* for L3/L4 structured bodies.
386
-
387
- **(b) Prescription.** Minsky: push the narrowing into the *type* so an op can only reach a body it
388
- applies to. King: don't round-trip through text you already parsed — operate on the structured value.
389
-
390
- **(c) Recommendation.**
391
- - **(seam a):** keep duck-typed narrowing for now — it is *cheap and safe* (the `err(...)` is correct,
392
- just verbose). If desired, route mutation through `FamilyPlugin.mutate` (already in the interface,
393
- `families.ts:55`) so each family owns its narrowing in one place rather than re-checking at every op.
394
- This is the I1 registry paying off again. **Low priority** — it is verbosity, not a bug.
395
- - **(seam b) is the structural one — but reconciled, lower-urgency.** Give `FamilyPlugin` a
396
- source-context `layout?(ctx)` hook (I1/I3) and prefer it over public-entrypoint switch statements.
397
- This lets each family own the exact parse/config/layout pipeline it needs while preserving
398
- frontmatter, accessibility directives, and opaque-source semantics. Per `AGENT_NATIVE.md` §3,
399
- however, `canonicalSource` is the *documented* "round-trip pillar" ("structured renderers use this
400
- as input") and the coupling is already guarded by round-trip identity property tests — so direct
401
- **structured-body** layout is a future decoupling/perf option, not the closure requirement, and it
402
- applies only to **L3/L4** structured bodies. For **L1** opaque bodies, text is the representation and
403
- re-parsing is correct.
404
-
405
- **(d) Tradeoffs / determinism risk.** The source-context hook is lower-risk than the original body-only
406
- wording because it moves dispatch without changing which source dialects a family can faithfully
407
- understand. A later body-direct layout path is higher-risk: it must produce *byte-identical* geometry
408
- to the source-context path, or it is a behavior change. Gate any such migration per family with
409
- snapshot tests; migrate only families whose body is a faithful superset of what the parser
410
- reconstructs. Some families may *not* round-trip losslessly today — for those, keeping source-context
411
- layout is the honest call.
412
-
413
- **(e) Where theory doesn't transfer.** "Never round-trip through text" is too absolute. For the **opaque**
414
- body, text *is* the only representation — source-context layout is correct and unavoidable. The
415
- body-direct optimization applies strictly to **structured** bodies. And duck-typed narrowing (seam a)
416
- is a non-issue the theory would over-engineer; resist the urge to build a typed op-dispatch matrix for
417
- it.
418
-
419
- ---
420
-
421
- ## I8 — ASCII is a parallel universe — mostly justified
422
-
423
- **(a) Lens.** **Metz, "The Wrong Abstraction"** ("duplication is far cheaper than the wrong
424
- abstraction") and **Moseley & Marks, essential vs. accidental complexity**. The `ascii/` stack (~9.6k
425
- lines) shares nothing large with SVG: own `AsciiGraph`/`Canvas`/`RoleCanvas`, own A* pathfinder, own
426
- per-family layouts; it reuses only `NodeShape`, `EdgeStyle`, `RouteClass`, and `shared/unicode-ranges.ts`.
427
-
428
- **(b) Prescription.** Metz: do **not** force a shared abstraction when the back-ends differ
429
- *essentially*. The differences here are essential: **character-cell grid geometry** (integer snapping,
430
- monospace metrics, no sub-cell positioning) versus **float vector geometry**. A unified geometry model
431
- would accrete per-call special-casing — the exact tangle Metz warns about, which *raises* accidental
432
- complexity while looking DRY.
433
-
434
- **(c) Recommendation. Leave ASCII isolated.** This is the explicit "academic ideal is the wrong call
435
- here" case: the theoretically-tidy move (one geometry IR for both surfaces) is **wrong**. ASCII is
436
- *internally* well-abstracted (it already has the pluggable `ShapeRenderer` registry the SVG side lacks —
437
- note the irony: ASCII is ahead of SVG on intra-stack abstraction). The only defensible sharing is at the
438
- *topology* level, never the *geometry* level: families/`RouteClass` classification and "which families
439
- exist" could flow from the I1 registry (an ASCII renderer is just another `FamilyPlugin.renderAscii`
440
- hook), so the **family-set** stops being declared twice even though the **layout** stays separate.
441
-
442
- **(d) Tradeoffs / determinism risk.** Routing ASCII through the I1 registry's `renderAscii` hook is the
443
- *only* I8 change worth making, and it is low-risk (dispatch only, not geometry). The cost the audit
444
- names — "shape / edge-routing / fan-in-out exist twice, so parity changes need two edits" — is **real
445
- and worth accepting**: it is the price of two genuinely different geometries. Track parity with a
446
- shared *test corpus*, not shared *code*.
447
-
448
- **(e) Where theory doesn't transfer.** None needed — this is the case where the theory (Metz) *correctly*
449
- says "stop." The one nuance: "essential" is a judgment that can change. If ASCII and SVG ever converge on
450
- a shared *intermediate topology* (post-ELK, pre-geometry), revisit — but only when a forced change proves
451
- they've actually converged, never preemptively.
452
-
453
- ---
454
-
455
- ## I9 — Vestigial / cosmetic
456
-
457
- **(a) Lens.** **Lampson** ("do one thing well"; remove needless indirection) and **TypeScript exhaustiveness checking**.
458
- At audit time, `src/layout.ts` was a one-line re-export of `layoutGraphSync`
459
- (pure indirection); naming was inconsistent (`parseXDiagram` vs `parseXChart`
460
- vs `parseGanttModel`; `Sync` suffix on only 3 of 11 sync layout fns);
461
- `_options` params (pie/quadrant) advertised a contract not honored.
462
-
463
- **(b/c) Prescription / recommendation.** Pure hygiene, **mechanical, lowest-risk**, do alongside the
464
- structural work it touches:
465
- - Issue #71 inlined `src/layout.ts`'s re-export at call sites and deleted the file (Lampson: needless indirection).
466
- - Normalize naming to one convention (`parse<Family>` / `layout<Family>`); drop the `Sync` suffix
467
- (all are sync — the distinction is meaningless). Do this as a **single rename commit** so blame stays
468
- legible.
469
- - Delete unused `_options` params — folded into the I2 `RenderContext` migration (an unused param is a
470
- lie about the interface; King/Lampson both say the type should tell the truth).
471
-
472
- **(d) Determinism risk.** Zero — renames and dead-param removal don't touch geometry. The only risk is
473
- *merge churn*; sequence I9's renames **after** the structural refactors (I1–I3) so you rename the final
474
- names once, not twice.
475
-
476
- **(e) Where theory doesn't transfer.** Nothing deep here; resist gold-plating (don't invent a naming DSL).
477
-
478
- ---
479
-
480
- ## Closure Roadmap
481
-
482
- This table supersedes the original issue roadmap. It records the implementation closure criteria after
483
- the 2026-06-21 reappraisal. Every behavior-affecting row is gated by the repo's snapshot,
484
- property-based, visual-contract, and determinism tests.
485
-
486
- | # | Closure criterion | Issues | Status |
487
- |---|---|---|---|
488
- | 1 | `RenderContext` + marker `PositionedDiagram`; all SVG renderers use the context object. | I2, I3 | Done. |
489
- | 2 | Split certificate concepts by proof type; exhaustively classify every certificate variant. | I6 | Done via `EdgeRouteCertificate`, `FamilyEdgeRouteCertificate`, `RegionContainmentCertificate`, and `layoutCertificateProof`. |
490
- | 3 | Add `layout`/`renderSvg`/`renderAscii` hooks to `FamilyPlugin`; remove public SVG/ASCII switch dispatch; make registry iteration deterministic. | I1, I8 | Done; `knownFamilies()` follows metadata order. |
491
- | 4 | Move I4 leaks to their owning stages: pie colors at render, xychart config post-parse, named Gantt pipeline, architecture metrics-only layout input. | I4 | Done. |
492
- | 5 | Consolidate color resolution onto `DiagramColors` + CSS vars and semantic role color slots. | I5 | Done; Mermaid dialects remain accepted at the boundary. |
493
- | 6 | Prefer registered source-context layout hooks over duplicated public dispatch; reserve body-direct layout for future per-family optimizations that can prove lossless parity. | I7 (seam b) | Done for closure; body-direct layout is not required for issue #71. |
494
- | 7 | Delete vestigial `src/layout.ts`; remove dead renderer params; avoid broad naming churn unless it falls out naturally. | I9 | Done for blocking hygiene; global rename normalization remains optional. |
495
-
496
- Seam (a) of I7 (duck-typed mutation) remains deliberately omitted — the registry route would not make a
497
- typed op-dispatch matrix simpler, and the current public mutation surface is already narrowed by family.
498
-
499
- ---
500
-
501
- ## Where the academic ideal is the *wrong* call here
502
-
503
- A deliberate list, so reviewers can see the adversarial check was done and not skipped:
504
-
505
- 1. **Do not unify the three stacks into one geometry IR (against a naïve reading of LLVM/MLIR).**
506
- The `Positioned*` models are too family-specific; a universal IR becomes Metz's "wrong abstraction."
507
- Use a **marker base** (`{width,height}`) + progressive projection, not unification (I3).
508
- 2. **Keep ASCII isolated (against "DRY the renderers").** Grid vs. float geometry is *essential*
509
- complexity (Moseley & Marks); a shared model would accrete conditionals. Share only the *family-set*
510
- via the registry, never the layout (I8).
511
- 3. **Split the certificate union; don't force the core to mint family certs (against symmetry).**
512
- Producing containment certs the core never reads is work for tidiness' sake; split the type instead (I6).
513
- 4. **Don't build multimethods or a typed op-dispatch matrix in TypeScript (against Clojure/Julia).**
514
- Non-idiomatic; a global multimethod registry risks **import-order nondeterminism** that violates the
515
- repo's hard determinism guarantee. The keyed plug-in registry is the right amount of dynamism (I1, I7a).
516
- 5. **Don't purify Gantt's pipeline or parsing dogmatically (against "parse must be pure").**
517
- Gantt's 4-stage shape is a correct Sugiyama-style staged pipeline (Sugiyama/Tagawa/Toda 1981);
518
- *name* it, don't flatten it. The opaque body legitimately *is* text and must re-parse (I4, I7e).
519
- 6. **Don't tagless-final / object-algebra the families (against the Expression-Problem "solutions").**
520
- Both fight TypeScript's structural typing and break the JSON-serializable, inspectable data model the
521
- agent stack depends on. The registry-of-records is the pragmatic two-axis solution that survives
522
- serialization and determinism (§0).
523
-
524
- ---
525
-
526
- ## References
527
-
528
- Verification legend: **[V]** citation + URL content directly confirmed this session; **[D]** citation/DOI
529
- confirmed via authoritative index (dblp/arXiv/publisher) but the canonical landing page returned 403/402
530
- to automated fetch (open mirror given where available); **[C]** cited by author/edition/ISBN, page-level
531
- URL not independently fetched. No URLs or page numbers were fabricated.
532
-
533
- **Expression Problem & solution patterns**
534
- - Philip Wadler, "The Expression Problem," email to the Java Genericity list, 12 Nov 1998. **[V]**
535
- <https://homepages.inf.ed.ac.uk/wadler/papers/expression/expression.txt>
536
- - William R. Cook, "On Understanding Data Abstraction, Revisited," OOPSLA 2009, pp. 557–572.
537
- DOI 10.1145/1640089.1640133. **[D]** author PDF <https://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf>
538
- (ACM DOI page paywalled).
539
- - Mads Torgersen, "The Expression Problem Revisited — Four New Solutions Using Generics," ECOOP 2004,
540
- LNCS 3086, pp. 123–143. DOI 10.1007/978-3-540-24851-4_6. **[D]**
541
- <https://link.springer.com/chapter/10.1007/978-3-540-24851-4_6>
542
- - Bruno C. d. S. Oliveira & William R. Cook, "Extensibility for the Masses: Practical Extensibility with
543
- Object Algebras," ECOOP 2012, LNCS 7313, pp. 2–27. DOI 10.1007/978-3-642-31057-7_2. **[D]**
544
- author PDF <https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf>
545
- - Jacques Carette, Oleg Kiselyov, Chung-chieh Shan, "Finally Tagless, Partially Evaluated," APLAS 2007
546
- (LNCS 4807, pp. 222–238, DOI 10.1007/978-3-540-76637-7_15); journal version JFP 19(5):509–543, 2009,
547
- DOI 10.1017/S0956796809007205. **[V]** <https://okmij.org/ftp/tagless-final/> · PDF
548
- <https://okmij.org/ftp/tagless-final/JFP.pdf>
549
- - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, *Design Patterns: Elements of Reusable
550
- Object-Oriented Software*, Addison-Wesley, 1994. ISBN 0-201-63361-2. **[C]** (Visitor pp. 331–344;
551
- Bridge pp. 151–161; Strategy pp. 315–323; Abstract Factory pp. 87–95.) Bridge/Strategy/Abstract Factory
552
- reference pages: <https://refactoring.guru/design-patterns/bridge> · `/strategy` · `/abstract-factory`. **[V]**
553
- - Rich Hickey / Clojure, "Multimethods and Hierarchies," clojure.org reference. **[V]**
554
- <https://clojure.org/reference/multimethods>
555
- - Jeff Bezanson, Alan Edelman, Stefan Karpinski, Viral B. Shah, "Julia: A Fresh Approach to Numerical
556
- Computing," *SIAM Review* 59(1):65–98, 2017, DOI 10.1137/141000671. **[D]** docs **[V]**
557
- <https://docs.julialang.org/en/v1/manual/methods/> · paper PDF
558
- <https://julialang.org/assets/research/julia-fresh-approach-BEKS.pdf>
559
-
560
- **Compiler / IR / multi-backend architecture**
561
- - Andrew W. Keep & R. Kent Dybvig, "A Nanopass Framework for Commercial Compiler Development," ICFP 2013,
562
- pp. 343–350. DOI 10.1145/2500365.2500618. **[D]** author PDF <https://andykeep.com/pubs/np-preprint.pdf>
563
- - Dipanwita Sarkar, Oscar Waddell, R. Kent Dybvig, "A Nanopass Infrastructure for Compiler Education,"
564
- ICFP 2004, pp. 201–212. DOI 10.1145/1016850.1016878. **[D]**
565
- <https://www.cs.tufts.edu/comp/150FP/archive/kent-dybvig/nanopass.pdf>
566
- - Chris Lattner & Vikram Adve, "LLVM: A Compilation Framework for Lifelong Program Analysis &
567
- Transformation," CGO 2004, pp. 75–88. DOI 10.1109/CGO.2004.1281665. **[V]**
568
- <https://llvm.org/pubs/2004-01-30-CGO-LLVM.html>
569
- - Chris Lattner, Mehdi Amini, Uday Bondhugula, Albert Cohen, Andy Davis, Jacques Pienaar, River Riddle,
570
- Tatiana Shpeisman, Nicolas Vasilache, Oleksandr Zinenko, "MLIR: A Compiler Infrastructure for the End
571
- of Moore's Law," arXiv:2002.11054, 2020 (CGO 2021 as "MLIR: Scaling Compiler Infrastructure for Domain
572
- Specific Computation," DOI 10.1109/CGO51591.2021.9370308). **[V]** <https://arxiv.org/abs/2002.11054>
573
- - Micah D. Beck, "On the Hourglass Model," *Communications of the ACM* 62(7):48–57, 2019.
574
- DOI 10.1145/3274770. **[D]** open fulltext
575
- <https://m-cacm.acm.org/magazines/2019/7/237714-on-the-hourglass-model/fulltext>
576
- (Deering's 1998 "Watching the Waist of the Protocol Hourglass" keynote is the metaphor's origin but has
577
- no stable peer-reviewed URL — **URL not verified**; Beck is the citable source.)
578
-
579
- **Modularity & information hiding**
580
- - David L. Parnas, "On the Criteria To Be Used in Decomposing Systems into Modules," *CACM* 15(12):1053–1058,
581
- 1972. DOI 10.1145/361598.361623. **[D]** author copy <http://sunnyday.mit.edu/16.355/parnas-criteria.html> **[V]**
582
- - David L. Parnas, "Designing Software for Ease of Extension and Contraction," *IEEE TSE* SE-5(2):128–138,
583
- 1979. DOI 10.1109/TSE.1979.234169. **[D]** <https://ieeexplore.ieee.org/document/1702607/>
584
-
585
- **Two-dimensional variability / plugin architecture / DIP**
586
- - (Bridge / Strategy / Abstract Factory: GoF 1994, above.)
587
- - Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, Michael Stal, *Pattern-Oriented Software
588
- Architecture, Volume 1: A System of Patterns* (POSA1), Wiley, 1996. ISBN 0-471-95869-7. Microkernel
589
- pp. 171–192. **[C]** publisher
590
- <https://www.wiley.com/en-us/Pattern+Oriented+Software+Architecture,+Volume+1,+A+System+of+Patterns-p-9780471958697> **[V]**
591
- - Robert C. Martin, "The Dependency Inversion Principle," *C++ Report*, 1996; "Design Principles and
592
- Design Patterns," Object Mentor, 2000. **[C]** original host defunct; cited by
593
- <https://en.wikipedia.org/wiki/Dependency_inversion_principle> **[V]** and
594
- <https://martinfowler.com/articles/dipInTheWild.html> **[V]**; archived PDF
595
- <https://web.archive.org/web/20110714224327/http://www.objectmentor.com/resources/articles/dip.pdf>
596
- (**not independently fetched**).
597
-
598
- **Type-driven design**
599
- - Yaron Minsky, "Effective ML Revisited" (incl. "Make illegal states unrepresentable"), Jane Street Tech
600
- Blog (Effective ML talk, CUFP 2010). **[V]** <https://blog.janestreet.com/effective-ml-revisited/>
601
- - Alexis King, "Parse, don't validate," lexi-lambda.github.io, 5 Nov 2019. **[V]**
602
- <https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/>
603
- - "Narrowing — Discriminated Unions and Exhaustiveness Checking," *The TypeScript Handbook*, Microsoft. **[V]**
604
- <https://www.typescriptlang.org/docs/handbook/2/narrowing.html> · theory backing: Robert Harper,
605
- *Practical Foundations for Programming Languages*, 2nd ed., Cambridge Univ. Press, 2016 (Sum Types). **[C]**
606
-
607
- **Graph drawing / layout-vs-render**
608
- - Kozo Sugiyama, Shojiro Tagawa, Mitsuhiko Toda, "Methods for Visual Understanding of Hierarchical System
609
- Structures," *IEEE Trans. SMC* SMC-11(2):109–125, 1981. DOI 10.1109/TSMC.1981.4308636. **[D]**
610
- - Emden R. Gansner & Stephen C. North, "An open graph visualization system and its applications to
611
- software engineering," *Software: Practice and Experience* 30(11):1203–1233, 2000.
612
- DOI 10.1002/1097-024X(200009)30:11<1203::AID-SPE338>3.0.CO;2-N. **[D]** open author PDF ("GN99")
613
- <https://www.graphviz.org/documentation/GN99.pdf>
614
- - Christoph Daniel Schulze, Miro Spönemann, Reinhard von Hanxleden, "Drawing layered graphs with port
615
- constraints," *J. Visual Languages & Computing* 25(2):89–106, 2014. DOI 10.1016/j.jvlc.2013.11.005. **[D]**
616
- - Sören Domrös, Reinhard von Hanxleden, Miro Spönemann, Ulf Rüegg, Christoph Daniel Schulze, "The Eclipse
617
- Layout Kernel," arXiv:2311.00533, 2023. **[V]** <https://arxiv.org/abs/2311.00533>
618
-
619
- **2026-06-21 competitive / industry reappraisal sources**
620
- - Mermaid documentation, "Diagram Syntax" and layout/look configuration (site version 11.15.0). **[V]**
621
- <https://mermaid.js.org/intro/syntax-reference.html>
622
- - Mermaid documentation, "Theme Configuration" (`themeVariables`, derived colors, per-family variables).
623
- **[V]** <https://mermaid.js.org/config/theming.html>
624
- - Graphviz documentation, "Documentation" overview (DOT language, layout engines, output formats,
625
- attributes). **[V]** <https://graphviz.org/documentation/>
626
- - PlantUML home page (supported diagram families, layout engines, output formats) and ASCII-art page
627
- (ASCII/Unicode documented for sequence diagrams). **[V]** <https://plantuml.com/> ·
628
- <https://plantuml.com/ascii-art>
629
- - D2 documentation, "Introduction", "Layouts", and "Themes" (declarative diagramming, engine tradeoffs,
630
- theme overrides). **[V]** <https://d2lang.com/tour/intro/> ·
631
- <https://d2lang.com/tour/layouts/> · <https://d2lang.com/tour/themes/>
632
- - Design Tokens Community Group, "Design Tokens Format Module 2025.10," Draft Community Group Report,
633
- 13 June 2026. **[V]** Preview draft; explicitly not a W3C standard.
634
- <https://www.designtokens.org/tr/drafts/format/>
635
-
636
- **Testing practice sources used for reappraisal**
637
- - `adewale/testing-best-practices` skill and references (TypeScript, golden/snapshot tests, correctness by
638
- construction, mutation/testing-type decision guide). **[V]**
639
- <https://github.com/adewale/testing-best-practices>
640
- - Koen Claessen & John Hughes, "QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs,"
641
- ICFP 2000. DOI 10.1145/351240.351266. **[D]**
642
- - Yue Jia & Mark Harman, "An Analysis and Survey of the Development of Mutation Testing," *IEEE TSE*
643
- 37(5):649-678, 2011. DOI 10.1109/TSE.2010.62. **[D]**
644
- - James Somers, "What if writing tests was a joyful experience?", Jane Street Blog, 9 Jan 2023
645
- (expect/snapshot tests and readable output discipline). **[V]**
646
- <https://blog.janestreet.com/the-joy-of-expect-tests/>
647
-
648
- **Complexity & cost of duplication**
649
- - Ben Moseley & Peter Marks, "Out of the Tar Pit," 2006. **[V]**
650
- <https://curtclifton.net/papers/MoseleyMarks06a.pdf>
651
- - Butler W. Lampson, "Hints for Computer System Design," *ACM SIGOPS OSR* 17(5):33–48, SOSP 1983.
652
- DOI 10.1145/773379.806614. **[D]** open author PDF
653
- <https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/acrobat-17.pdf>
654
- - Sandi Metz, "The Wrong Abstraction," sandimetz.com, 20 Jan 2016. **[V]**
655
- <https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction>