schematex 0.6.10 → 0.8.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 (149) hide show
  1. package/dist/ai/ai-sdk.cjs +19 -19
  2. package/dist/ai/ai-sdk.d.cts +4 -4
  3. package/dist/ai/ai-sdk.d.ts +4 -4
  4. package/dist/ai/ai-sdk.js +14 -14
  5. package/dist/ai/index.cjs +25 -25
  6. package/dist/ai/index.d.cts +3 -3
  7. package/dist/ai/index.d.ts +3 -3
  8. package/dist/ai/index.js +14 -14
  9. package/dist/{api-JaBtsUT8.d.cts → api-BnJAPCwC.d.cts} +1 -1
  10. package/dist/{api-DIJM_mqp.d.ts → api-DAZJGq9r.d.ts} +1 -1
  11. package/dist/browser.cjs +20 -20
  12. package/dist/browser.d.cts +3 -3
  13. package/dist/browser.d.ts +3 -3
  14. package/dist/browser.js +14 -14
  15. package/dist/{chunk-34O3C6OC.cjs → chunk-2YBBDPOW.cjs} +4 -4
  16. package/dist/chunk-2YBBDPOW.cjs.map +1 -0
  17. package/dist/{chunk-3YRYBTLG.cjs → chunk-3CSST5GZ.cjs} +9082 -591
  18. package/dist/chunk-3CSST5GZ.cjs.map +1 -0
  19. package/dist/{chunk-JTGTWBAD.js → chunk-6DVK5M2J.js} +3 -3
  20. package/dist/chunk-6DVK5M2J.js.map +1 -0
  21. package/dist/{chunk-SHMG7BVF.cjs → chunk-6L46VIXI.cjs} +4 -4
  22. package/dist/{chunk-SHMG7BVF.cjs.map → chunk-6L46VIXI.cjs.map} +1 -1
  23. package/dist/{chunk-OFKRELZK.js → chunk-ABCMTAOZ.js} +3 -3
  24. package/dist/{chunk-OFKRELZK.js.map → chunk-ABCMTAOZ.js.map} +1 -1
  25. package/dist/{chunk-5FYPSIGD.cjs → chunk-B5AQ3CG3.cjs} +4 -4
  26. package/dist/{chunk-5FYPSIGD.cjs.map → chunk-B5AQ3CG3.cjs.map} +1 -1
  27. package/dist/{chunk-3GAPHXCE.js → chunk-CDK7KDIW.js} +3 -3
  28. package/dist/{chunk-3GAPHXCE.js.map → chunk-CDK7KDIW.js.map} +1 -1
  29. package/dist/{chunk-Z3A2UNK2.cjs → chunk-D34VGLSE.cjs} +4 -4
  30. package/dist/{chunk-Z3A2UNK2.cjs.map → chunk-D34VGLSE.cjs.map} +1 -1
  31. package/dist/{chunk-BL57NQKN.cjs → chunk-DHI7YAQJ.cjs} +326 -29
  32. package/dist/chunk-DHI7YAQJ.cjs.map +1 -0
  33. package/dist/{chunk-C7V57V6O.js → chunk-DX44TBFZ.js} +3 -3
  34. package/dist/{chunk-C7V57V6O.js.map → chunk-DX44TBFZ.js.map} +1 -1
  35. package/dist/{chunk-MPCSWRZC.js → chunk-E3CAJGJM.js} +3 -3
  36. package/dist/{chunk-MPCSWRZC.js.map → chunk-E3CAJGJM.js.map} +1 -1
  37. package/dist/{chunk-HL5PS6MG.js → chunk-E7LXMEKX.js} +325 -28
  38. package/dist/chunk-E7LXMEKX.js.map +1 -0
  39. package/dist/{chunk-P26FCZP3.js → chunk-IXRPRMHI.js} +3 -3
  40. package/dist/{chunk-P26FCZP3.js.map → chunk-IXRPRMHI.js.map} +1 -1
  41. package/dist/{chunk-WU2N6ZVM.js → chunk-JGCKW5RS.js} +3 -3
  42. package/dist/{chunk-WU2N6ZVM.js.map → chunk-JGCKW5RS.js.map} +1 -1
  43. package/dist/{chunk-J3EPFZPX.cjs → chunk-JIJWGHRN.cjs} +5 -5
  44. package/dist/{chunk-J3EPFZPX.cjs.map → chunk-JIJWGHRN.cjs.map} +1 -1
  45. package/dist/{chunk-TACTEF2N.cjs → chunk-JIUC4DRS.cjs} +4 -4
  46. package/dist/{chunk-TACTEF2N.cjs.map → chunk-JIUC4DRS.cjs.map} +1 -1
  47. package/dist/{chunk-77GPD4YQ.cjs → chunk-KUITLG4N.cjs} +4 -4
  48. package/dist/{chunk-77GPD4YQ.cjs.map → chunk-KUITLG4N.cjs.map} +1 -1
  49. package/dist/{chunk-2F45Y2ON.cjs → chunk-NXU4XKLY.cjs} +4 -4
  50. package/dist/{chunk-2F45Y2ON.cjs.map → chunk-NXU4XKLY.cjs.map} +1 -1
  51. package/dist/{chunk-UHPGWO77.cjs → chunk-Q2YRJHFB.cjs} +4 -4
  52. package/dist/{chunk-UHPGWO77.cjs.map → chunk-Q2YRJHFB.cjs.map} +1 -1
  53. package/dist/{chunk-DZGA25O5.cjs → chunk-RKN6QJ7K.cjs} +733 -10
  54. package/dist/chunk-RKN6QJ7K.cjs.map +1 -0
  55. package/dist/{chunk-2TUZ3QJA.js → chunk-T3GV7OVF.js} +3 -3
  56. package/dist/{chunk-2TUZ3QJA.js.map → chunk-T3GV7OVF.js.map} +1 -1
  57. package/dist/{chunk-5IKOLUWK.js → chunk-TO6PNBT3.js} +3 -3
  58. package/dist/chunk-TO6PNBT3.js.map +1 -0
  59. package/dist/{chunk-X4P4HKHP.js → chunk-TXMT4XLE.js} +731 -8
  60. package/dist/chunk-TXMT4XLE.js.map +1 -0
  61. package/dist/{chunk-T3FV73LM.js → chunk-UOM3EDE6.js} +3 -3
  62. package/dist/{chunk-T3FV73LM.js.map → chunk-UOM3EDE6.js.map} +1 -1
  63. package/dist/{chunk-NAGUZFXX.cjs → chunk-VHDSPI6A.cjs} +3 -2
  64. package/dist/chunk-VHDSPI6A.cjs.map +1 -0
  65. package/dist/{chunk-YMZTXOUG.js → chunk-WNGOG4CI.js} +3 -3
  66. package/dist/{chunk-YMZTXOUG.js.map → chunk-WNGOG4CI.js.map} +1 -1
  67. package/dist/{chunk-6AWASOFO.js → chunk-XI5QP7LM.js} +9054 -571
  68. package/dist/chunk-XI5QP7LM.js.map +1 -0
  69. package/dist/{chunk-LPAWZYDU.cjs → chunk-YMFYPB5Y.cjs} +14 -14
  70. package/dist/{chunk-LPAWZYDU.cjs.map → chunk-YMFYPB5Y.cjs.map} +1 -1
  71. package/dist/{diagnostics-BKRow1ur.d.cts → diagnostics-B-jOt6Ua.d.cts} +1 -1
  72. package/dist/{diagnostics-BKRow1ur.d.ts → diagnostics-B-jOt6Ua.d.ts} +1 -1
  73. package/dist/diagrams/blockdiagram/index.cjs +6 -6
  74. package/dist/diagrams/blockdiagram/index.d.cts +1 -1
  75. package/dist/diagrams/blockdiagram/index.d.ts +1 -1
  76. package/dist/diagrams/blockdiagram/index.js +2 -2
  77. package/dist/diagrams/circuit/index.cjs +9 -9
  78. package/dist/diagrams/circuit/index.d.cts +1 -1
  79. package/dist/diagrams/circuit/index.d.ts +1 -1
  80. package/dist/diagrams/circuit/index.js +2 -2
  81. package/dist/diagrams/ecomap/index.d.cts +1 -1
  82. package/dist/diagrams/ecomap/index.d.ts +1 -1
  83. package/dist/diagrams/entity/index.cjs +6 -6
  84. package/dist/diagrams/entity/index.d.cts +1 -1
  85. package/dist/diagrams/entity/index.d.ts +1 -1
  86. package/dist/diagrams/entity/index.js +2 -2
  87. package/dist/diagrams/fishbone/index.d.cts +1 -1
  88. package/dist/diagrams/fishbone/index.d.ts +1 -1
  89. package/dist/diagrams/flowchart/index.d.cts +2 -2
  90. package/dist/diagrams/flowchart/index.d.ts +2 -2
  91. package/dist/diagrams/genogram/index.d.cts +1 -1
  92. package/dist/diagrams/genogram/index.d.ts +1 -1
  93. package/dist/diagrams/ladder/index.cjs +6 -6
  94. package/dist/diagrams/ladder/index.d.cts +1 -1
  95. package/dist/diagrams/ladder/index.d.ts +1 -1
  96. package/dist/diagrams/ladder/index.js +2 -2
  97. package/dist/diagrams/logic/index.cjs +8 -8
  98. package/dist/diagrams/logic/index.d.cts +1 -1
  99. package/dist/diagrams/logic/index.d.ts +1 -1
  100. package/dist/diagrams/logic/index.js +2 -2
  101. package/dist/diagrams/orgchart/index.cjs +8 -8
  102. package/dist/diagrams/orgchart/index.d.cts +1 -1
  103. package/dist/diagrams/orgchart/index.d.ts +1 -1
  104. package/dist/diagrams/orgchart/index.js +2 -2
  105. package/dist/diagrams/pedigree/index.d.cts +1 -1
  106. package/dist/diagrams/pedigree/index.d.ts +1 -1
  107. package/dist/diagrams/phylo/index.cjs +7 -7
  108. package/dist/diagrams/phylo/index.d.cts +22 -1
  109. package/dist/diagrams/phylo/index.d.ts +22 -1
  110. package/dist/diagrams/phylo/index.js +2 -2
  111. package/dist/diagrams/sld/index.cjs +8 -8
  112. package/dist/diagrams/sld/index.d.cts +1 -1
  113. package/dist/diagrams/sld/index.d.ts +1 -1
  114. package/dist/diagrams/sld/index.js +2 -2
  115. package/dist/diagrams/sociogram/index.cjs +6 -6
  116. package/dist/diagrams/sociogram/index.d.cts +1 -1
  117. package/dist/diagrams/sociogram/index.d.ts +1 -1
  118. package/dist/diagrams/sociogram/index.js +2 -2
  119. package/dist/diagrams/timing/index.cjs +5 -5
  120. package/dist/diagrams/timing/index.d.cts +1 -1
  121. package/dist/diagrams/timing/index.d.ts +1 -1
  122. package/dist/diagrams/timing/index.js +2 -2
  123. package/dist/diagrams/venn/index.cjs +9 -9
  124. package/dist/diagrams/venn/index.d.cts +1 -1
  125. package/dist/diagrams/venn/index.d.ts +1 -1
  126. package/dist/diagrams/venn/index.js +2 -2
  127. package/dist/{index-Dq3Mfxay.d.ts → index-8MaUFTMN.d.ts} +1 -1
  128. package/dist/{index-DU2h1Y-a.d.cts → index-D-MAB6CH.d.cts} +1 -1
  129. package/dist/index.cjs +83 -51
  130. package/dist/index.d.cts +33 -5
  131. package/dist/index.d.ts +33 -5
  132. package/dist/index.js +17 -17
  133. package/dist/react.cjs +14 -14
  134. package/dist/react.d.cts +2 -2
  135. package/dist/react.d.ts +2 -2
  136. package/dist/react.js +13 -13
  137. package/dist/{tools-OzOQnlnV.d.cts → tools-CV1JZ75-.d.cts} +2 -2
  138. package/dist/{tools-A0HRZ8jj.d.ts → tools-CcPysgAD.d.ts} +2 -2
  139. package/package.json +1 -1
  140. package/dist/chunk-34O3C6OC.cjs.map +0 -1
  141. package/dist/chunk-3YRYBTLG.cjs.map +0 -1
  142. package/dist/chunk-5IKOLUWK.js.map +0 -1
  143. package/dist/chunk-6AWASOFO.js.map +0 -1
  144. package/dist/chunk-BL57NQKN.cjs.map +0 -1
  145. package/dist/chunk-DZGA25O5.cjs.map +0 -1
  146. package/dist/chunk-HL5PS6MG.js.map +0 -1
  147. package/dist/chunk-JTGTWBAD.js.map +0 -1
  148. package/dist/chunk-NAGUZFXX.cjs.map +0 -1
  149. package/dist/chunk-X4P4HKHP.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk3YRYBTLG_cjs = require('./chunk-3YRYBTLG.cjs');
3
+ var chunk3CSST5GZ_cjs = require('./chunk-3CSST5GZ.cjs');
4
4
 
5
5
  // src/ai/registry.ts
6
6
  var DIAGRAM_REGISTRY = [
@@ -340,6 +340,81 @@ var DIAGRAM_REGISTRY = [
340
340
  cluster: "generic",
341
341
  standard: "Timeline convention with era bands",
342
342
  syntaxKey: "timeline"
343
+ },
344
+ // ── Risk & reliability (Bucket B) ────────────────────────────
345
+ {
346
+ type: "eventtree",
347
+ name: "Event Tree Analysis",
348
+ tagline: "Forward-looking risk: one initiating event branches through each safety function into outcome sequences, with computed path frequencies.",
349
+ useWhen: "Use to propagate the consequences of an initiating event through a chain of barriers/safety functions and quantify each outcome's frequency \u2014 the inductive complement to a fault tree and the right wing of a bowtie. Header `eventtree`/`eta`; declare the initiating event with a frequency, the ordered functions with success/failure branch probabilities, and outcome rows with `s`/`f`/`*` patterns. The engine computes path frequency = f_initiating \xD7 \u03A0 branch-probabilities and flags the dominant sequence.",
350
+ cluster: "risk-reliability",
351
+ standard: "IEC 62502:2010 \xB7 NUREG/CR-2300 (PRA) \xB7 ISO 31010 Annex B; see 39-EVENT-TREE-STANDARD.md",
352
+ syntaxKey: "eventtree"
353
+ },
354
+ {
355
+ type: "fmea",
356
+ name: "FMEA (Failure Mode and Effects Analysis)",
357
+ tagline: "The reliability worksheet that computes its own risk \u2014 RPN = S\xD7O\xD7D plus AIAG-VDA Action Priority, ranked and colour-coded.",
358
+ useWhen: "Use to score and prioritise how each component/process step can fail \u2014 severity, occurrence, detection \u2014 and decide what to fix first. Header `fmea`; declare item/function \u2192 failure mode \u2192 effect (with `sev`) \u2192 cause (with `occ`) \u2192 controls (with `det`). The engine computes RPN = S\xD7O\xD7D and the AIAG-VDA Action Priority (High/Medium/Low), sorts the sheet, and colour-fills the RPN/AP cells by risk. Schematex's first table-shaped diagram.",
359
+ cluster: "risk-reliability",
360
+ standard: "AIAG-VDA FMEA Handbook (2019) \xB7 IEC 60812:2018 \xB7 SAE J1739 \xB7 MIL-STD-1629A; see 40-FMEA-STANDARD.md",
361
+ syntaxKey: "fmea"
362
+ },
363
+ // ── Systems thinking / stochastic ────────────────────────────
364
+ {
365
+ type: "causalloop",
366
+ name: "Causal Loop Diagram",
367
+ tagline: "System-dynamics feedback map \u2014 signed causal links the engine reads to classify each loop as reinforcing (R) or balancing (B).",
368
+ useWhen: 'Use for systems thinking / system dynamics: variables connected by `+`/`\u2212` causal links, where the engine detects feedback loops and labels each R (even number of negative links) or B (odd). Header `causalloop`/`cld`; write `A -> B : +` links and optional `loop R1 "name"` annotations and `delay` marks. Distinct from `sociogram` (social ties, no polarity) and `flowchart` (process steps).',
369
+ cluster: "causality-analysis",
370
+ standard: "Sterman, Business Dynamics (2000) \xB7 Meadows, Thinking in Systems; see 41-CAUSAL-LOOP-STANDARD.md",
371
+ syntaxKey: "causalloop"
372
+ },
373
+ {
374
+ type: "markov",
375
+ name: "Markov chain",
376
+ tagline: "Discrete-time Markov chain \u2014 circles + probability arcs, with the stationary distribution and recurrent/transient/absorbing classification computed for you.",
377
+ useWhen: "Use to model a probabilistic state process (reliability/availability, queueing, regime models) where you want the long-run distribution or absorption answer, not just the picture. Header `markov`/`markovchain`; write `S1 -> S2 : 0.3` transitions (each state's out-edges sum to 1). The engine computes the stationary distribution, classifies states, and for absorbing chains the fundamental matrix. Sibling of `state` and `petri`.",
378
+ cluster: "behavior-modeling",
379
+ standard: "Norris, Markov Chains (1997) \xB7 Kemeny & Snell, Finite Markov Chains; see 42-MARKOV-CHAIN-STANDARD.md",
380
+ syntaxKey: "markov"
381
+ },
382
+ // ── Software / process engineering ───────────────────────────
383
+ {
384
+ type: "gitgraph",
385
+ name: "Git commit graph",
386
+ tagline: "Branch-and-merge commit history on per-branch swimlanes \u2014 Mermaid gitGraph compatible.",
387
+ useWhen: 'Use to visualise a git branching/merging history. Header `gitGraph`; ordered `commit`, `branch <name>`, `checkout <name>`, `merge <name>`, `cherry-pick id: "\u2026"`, with `commit id:/tag:/type: HIGHLIGHT|REVERSE`. Mermaid `gitGraph` syntax parity so LLM output is drop-in compatible. Commits sit on per-branch lanes ordered chronologically; merges join lanes.',
388
+ cluster: "software-uml",
389
+ standard: "Mermaid gitGraph syntax \xB7 git DAG model; see 43-GIT-GRAPH-STANDARD.md",
390
+ syntaxKey: "gitgraph"
391
+ },
392
+ {
393
+ type: "epc",
394
+ name: "Event-driven Process Chain (EPC)",
395
+ tagline: "ARIS business-process notation \u2014 alternating events (red hexagons) and functions (green rounded rects) joined by AND/OR/XOR connectors, with the alternation rule validated.",
396
+ useWhen: "Use for ARIS-style business process modelling (SAP / enterprise BPM). Header `epc`; declare `event`, `function`, connectors `and`/`or`/`xor`, and the control flow between them. The engine validates strict event\u2194function alternation and connector legality (an event cannot be the source of an OR/XOR split). Distinct from `bpmn` and `flowchart` \u2014 a separate published standard with stricter rules.",
397
+ cluster: "corporate-legal",
398
+ standard: "ARIS / Keller, N\xFCttgens & Scheer (1992); see 44-EPC-STANDARD.md",
399
+ syntaxKey: "epc"
400
+ },
401
+ {
402
+ type: "idef0",
403
+ name: "IDEF0 function model",
404
+ tagline: "Federal function-modelling standard \u2014 boxes are activities, arrows are positional (the ICOM rule: Input-left, Control-top, Output-right, Mechanism-bottom).",
405
+ useWhen: "Use to model what a system/process does and its inputs/controls/outputs/mechanisms \u2014 systems engineering, defence/government process docs, enterprise architecture. Header `idef0`; declare `function` boxes and ICOM arrows (`input`/`control`/`output`/`mechanism`) plus box\u2192box flows that name the target ICOM side. The engine enforces ICOM placement and assigns node numbers, in a diagonal box staircase.",
406
+ cluster: "project-management",
407
+ standard: "FIPS PUB 183 (1993) \xB7 SADT (Ross); see 45-IDEF0-STANDARD.md",
408
+ syntaxKey: "idef0"
409
+ },
410
+ {
411
+ type: "threatmodel",
412
+ name: "Threat model (DFD + STRIDE)",
413
+ tagline: "Security data-flow diagram where the engine annotates each element with its applicable STRIDE threats and flags every flow that crosses a trust boundary.",
414
+ useWhen: "Use for security threat modelling (Microsoft SDL / OWASP Threat Dragon workflow): DFD shapes (external entity, process, data store), labelled data flows, and `boundary` trust zones. Header `threatmodel`/`stride`. The engine maps each element type to its STRIDE categories (external = S,R; process = all six; store = T,I,D + conditional R for logs; flow = T,I,D) and accents flows crossing a trust boundary. Includes the DFD base notation (no separate `dfd` engine).",
415
+ cluster: "network-infrastructure",
416
+ standard: "Shostack, Threat Modeling (2014) STRIDE-per-element \xB7 Microsoft SDL \xB7 base DFD DeMarco/Yourdon; see 46-THREAT-MODEL-STRIDE-STANDARD.md + 31-DFD-STANDARD.md",
417
+ syntaxKey: "threatmodel"
343
418
  }
344
419
  ];
345
420
  var DIAGRAM_SINCE = {
@@ -390,7 +465,16 @@ var DIAGRAM_SINCE = {
390
465
  // 0.6.5
391
466
  faulttree: "0.6.5",
392
467
  // 0.6.6
393
- bowtie: "0.6.6"
468
+ bowtie: "0.6.6",
469
+ // 0.8.0 — Bucket B (event tree, FMEA, causal loop, Markov, git graph, EPC, IDEF0, threat model)
470
+ eventtree: "0.8.0",
471
+ fmea: "0.8.0",
472
+ causalloop: "0.8.0",
473
+ markov: "0.8.0",
474
+ gitgraph: "0.8.0",
475
+ epc: "0.8.0",
476
+ idef0: "0.8.0",
477
+ threatmodel: "0.8.0"
394
478
  };
395
479
  function getDiagramSince(type) {
396
480
  const resolved = resolveDiagramType(type);
@@ -672,6 +756,44 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
672
756
  "dsl": 'breadboard\nboard: half\ntitle: "HC-SR04 distance sensor + Arduino Uno"\n\nparts\n uno: mcu uno @beside-left\n s1: sensor hcsr04 @8a\n\nwires\n s1:VCC --red-- @+t1\n s1:GND --black-- @-t1\n s1:TRIG --yellow-- @9c\n s1:ECHO --green-- @10c\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D9 --yellow-- @9c\n uno:D10 --green-- @10c',
673
757
  "notes": "The HC-SR04 is the most common ultrasonic distance sensor in beginner Arduino kits. Four pins, four wires, no driver IC needed. The sensor module is rendered as a blue PCB tile with its four pin labels (VCC / TRIG / ECHO / GND) sitting above the breadboard rows where they plug in.\n\nThe TRIG line is a digital output from the Arduino \u2014 pulse it high for 10 \xB5s and the sensor fires an ultrasonic chirp. The ECHO line is a digital input \u2014 its high-time, in microseconds, is twice the round-trip distance divided by the speed of sound. Conventional wiring uses yellow for TRIG and green for ECHO so the two signals are visually distinguishable, though the colors carry no electrical meaning."
674
758
  },
759
+ {
760
+ "slug": "causalloop-growth-engine",
761
+ "diagram": "causalloop",
762
+ "title": "Startup growth engine (R and B loops)",
763
+ "description": "A system-dynamics model of a viral product whose growth is capped by scaling strain. The engine enumerates every feedback loop and classifies each reinforcing (R) or balancing (B) by Sterman's even/odd rule.",
764
+ "standard": "Sterman, Business Dynamics (2000)",
765
+ "tags": [
766
+ "causalloop",
767
+ "cld",
768
+ "system-dynamics",
769
+ "feedback",
770
+ "reinforcing",
771
+ "balancing"
772
+ ],
773
+ "complexity": 3,
774
+ "featured": false,
775
+ "dsl": 'causalloop "Startup growth engine"\n "Active users" -> "Word of mouth" : +\n "Word of mouth" -> "New signups" : +\n "New signups" -> "Active users" : +\n "Active users" -> "Server load" : +\n "Server load" -> "App performance" : -\n "App performance" -> "Active users" : + delay\n "Active users" -> Revenue : +\n Revenue -> "Infra investment" : +\n "Infra investment" -> "App performance" : + delay\n loop R1 "Viral flywheel"\n loop B1 "Scaling strain"',
776
+ "notes": '## What this shows\n\nThe two-sided story of every viral product. One reinforcing loop drives growth: more active users produce more word of mouth, which drives signups, which adds users \u2014 an amplifying cycle. A second, balancing loop pushes back: more users raise server load, which degrades app performance, which (after a delay) loses users. The `delay` markers put the system-dynamics hash mark on the slow legs, where the lag between cause and effect is what makes the system overshoot.\n\nThe engine finds and labels the loops for you, which a drawing tool cannot. It enumerates every elementary cycle in the signed graph with Johnson\'s algorithm, then applies Sterman\'s rule \u2014 count the negative links, even means reinforcing (R), odd means balancing (B) \u2014 and numbers them R1, B1\u2026 The growth cycle comes back R (zero negatives), the load cycle B (one negative). The named `loop` declarations attach the human phrasing \u2014 "Viral flywheel", "Scaling strain" \u2014 onto the loops the engine detects.'
777
+ },
778
+ {
779
+ "slug": "causalloop-traffic-congestion",
780
+ "diagram": "causalloop",
781
+ "title": "Urban traffic congestion (two balancing loops)",
782
+ "description": "The classic system-dynamics counterintuitive result \u2014 widening roads induces demand. The engine enumerates the feedback loops and classifies each reinforcing or balancing by link polarity.",
783
+ "standard": "Sterman, Business Dynamics (2000)",
784
+ "tags": [
785
+ "causalloop",
786
+ "cld",
787
+ "system-dynamics",
788
+ "induced-demand",
789
+ "balancing",
790
+ "transit"
791
+ ],
792
+ "complexity": 2,
793
+ "featured": false,
794
+ "dsl": 'causalloop "Urban traffic congestion"\n "Road capacity" -> "Travel speed" : +\n "Travel speed" -> "Driving attractiveness" : +\n "Driving attractiveness" -> "Number of cars" : +\n "Number of cars" -> Congestion : +\n Congestion -> "Travel speed" : -\n Congestion -> "Public transit use" : +\n "Public transit use" -> "Number of cars" : -\n loop B1 "Congestion brake"\n loop B2 "Transit substitution"',
795
+ "notes": "## What this shows\n\nThe system-dynamics model behind one of the most counterintuitive results in transport policy: widening a road does not durably cut congestion, because faster travel makes driving more attractive, which pulls more cars onto the road until it clogs again. Two balancing loops fight the build-up \u2014 congestion itself slows travel and dampens demand, and congestion pushes commuters onto public transit, which removes cars.\n\nThe engine classifies the loops by polarity rather than asking you to. It walks the signed graph, enumerates each elementary cycle, and counts the negative links: both feedback loops here carry an odd number of negative links, so both come back **balancing (B)** and are numbered B1, B2. That even/odd classification \u2014 Sterman's textbook rule \u2014 is the analysis a drawing tool skips, and it is what explains why the system settles back to congestion no matter how much capacity is added."
796
+ },
675
797
  {
676
798
  "slug": "circuit-bridge-rectifier-supply",
677
799
  "diagram": "circuit",
@@ -745,6 +867,44 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
745
867
  "dsl": 'circuit "Pull-up + push button" netlist\nV1 vcc 0 5V\nR1 vcc sig 10k dir=down\nSW1 sig 0 type=switch\nC1 sig 0 100n',
746
868
  "notes": "## Scenario\n\nAn embedded engineer documents a classic active-low input: a pull-up resistor\nholds the signal high, a push button pulls it to ground, and a small capacitor\ndebounces it. The connectivity is written as a SPICE-style netlist \u2014 the engine\nplaces everything from the node names \u2014 and one optional hint refines the look.\n\n## Annotation key\n\n- **netlist line** \u2014 `id node-A node-B value`; components that share a node name\n are wired together. `0` is ground.\n- **`dir=down`** \u2014 the optional orientation hint. `R1` connects `vcc` to `sig`;\n by default the engine would lay it horizontally, but a pull-up reads best drawn\n vertically from the supply rail down to the signal node, so `dir=down` rotates\n just that symbol. Connectivity is unchanged \u2014 `dir=` only rotates the glyph.\n- **`type=switch`** \u2014 the `SW1` id prefix is ambiguous, so the component type is\n made explicit.\n\n## How to read\n\n`R1` ties `sig` up to `vcc` (drawn vertically thanks to `dir=down`). `SW1` and the\ndebounce cap `C1` both go from `sig` to ground, so the engine recognises them as\nshunt legs and drops them beneath the node. Pressing the button shorts `sig` to\nground, pulling the input low."
747
869
  },
870
+ {
871
+ "slug": "decisiontree-influence-market-entry",
872
+ "diagram": "decisiontree",
873
+ "title": "Market entry (influence diagram)",
874
+ "description": "A go/no-go market-entry decision as a compact influence diagram \u2014 demand observed before entering, a competitor response, and the profit objective \u2014 using the inline mode directive form.",
875
+ "standard": "Howard & Matheson (1981) influence diagram",
876
+ "tags": [
877
+ "decisiontree",
878
+ "influence",
879
+ "decision-analysis",
880
+ "dag",
881
+ "strategy",
882
+ "go-no-go"
883
+ ],
884
+ "complexity": 2,
885
+ "featured": false,
886
+ "dsl": 'decisiontree "Market Entry"\n mode: influence\n decision Enter "Enter market?"\n chance Demand "Market demand"\n chance Competition "Competitor response"\n value V "Profit" utility=120\n Demand -> Enter\n Demand -> V\n Competition -> V\n Enter -> V',
887
+ "notes": "## What this shows\n\nA classic go/no-go framed as an **influence diagram**, written with the inline `mode: influence` directive (the alternative to the `decisiontree:influence` header). The decision-maker controls one **rectangle** \u2014 whether to enter \u2014 against two uncertain **ovals**, market demand and the competitor's response, all feeding a single profit **octagon**.\n\nTwo arcs land on the decision and the value node from demand. `Demand -> Enter` is a **dashed informational arc**: demand is read before committing to entry, so the choice can react to it. `Demand -> V` and `Competition -> V` are functional arcs \u2014 both directly shape profit \u2014 while `Enter -> V` ties the chosen action into the payoff. The graph stays acyclic and carries exactly one value node, the two structural rules the engine enforces; it captures the shape of the bet without unrolling every demand-by-competitor branch into a tree."
888
+ },
889
+ {
890
+ "slug": "decisiontree-influence-oil-wildcatter",
891
+ "diagram": "decisiontree",
892
+ "title": "Oil wildcatter (influence diagram)",
893
+ "description": "The canonical decision-analysis teaching problem as a compact influence diagram \u2014 a drill decision informed by a seismic test, the uncertain oil state, and the profit objective, wired as a clean acyclic graph instead of an unrolled tree.",
894
+ "standard": "Howard & Matheson (1981) influence diagram",
895
+ "tags": [
896
+ "decisiontree",
897
+ "influence",
898
+ "decision-analysis",
899
+ "dag",
900
+ "oil-and-gas",
901
+ "howard-matheson"
902
+ ],
903
+ "complexity": 2,
904
+ "featured": false,
905
+ "dsl": 'decisiontree:influence "Oil Wildcatter"\n decision Drill "Drill?"\n chance Oil "Oil present"\n chance Seismic "Seismic test"\n value Profit "Net profit" utility=42\n Seismic -> Oil\n Seismic -> Drill\n Oil -> Profit\n Drill -> Profit',
906
+ "notes": "## What this shows\n\nThe textbook oil-wildcatter problem drawn the way a decision analyst frames it first \u2014 as an **influence diagram** rather than a fully unrolled decision tree. One **rectangle** for the drill decision, two **ovals** for the uncertainties (whether oil is present, what the seismic test reads), and one **octagon** for the profit objective. The whole problem is four nodes and four arcs, no matter how many states each variable could take.\n\nThe arcs carry the structure. `Seismic -> Drill` is a **dashed informational arc**: the seismic result is observed *before* the drill choice is made \u2014 that's the entire reason the test is worth running. `Seismic -> Oil` is a relevance arc (the reading is conditioned on the true oil state), and `Oil -> Profit` plus `Drill -> Profit` are functional arcs feeding the payoff. This mode is deliberately structural \u2014 it shows what informs what, without solving for expected value; reach for decision-analysis mode when you want the numbers folded back."
907
+ },
748
908
  {
749
909
  "slug": "decisiontree-investment-analysis",
750
910
  "diagram": "decisiontree",
@@ -923,6 +1083,25 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
923
1083
  "dsl": 'entity-structure "Acme Inc. \u2014 post Series A"\nentity acme "Acme Inc." corp@DE\nentity founders "Founders (2)" individual\nentity seed "Seed Fund I" lp@DE\nentity lead "Sequoia Series A" lp@DE\nentity angels "Angel group" individual\nentity esop "Employee Option Pool" trust@DE\nfounders -> acme : 45%\nseed -> acme : 12%\nlead -> acme : 22%\nangels -> acme : 6%\nesop -> acme : 15%',
924
1084
  "notes": "## Scenario\n\nA startup attorney or CFO documents the post-Series A ownership table for a 409A valuation, board consent, or investor report. The cap table diagram makes the dilution story visual \u2014 founders can immediately see their post-money percentage, and the VC can verify their ownership stake before signing the term sheet.\n\n## Annotation key\n\n- `-> acme : 45%` \u2014 ownership arrow with percentage label; all percentages should sum to 100%\n- `individual` \u2014 natural person (founder, angel)\n- `lp` \u2014 institutional investor entity (fund/LP)\n- `trust` \u2014 the ESOP/option pool (typically a Delaware trust or reserved pool)\n- `corp@DE` \u2014 the issuer (Delaware C-corp)\n\n## How to read\n\nAcme Inc. (Delaware C-corp) sits at the bottom as the issuer. Five shareholder classes flow down with their ownership arrows: founders at 45%, the lead Series A investor at 22%, the employee option pool at 15%, the seed fund at 12%, and angels at 6%. Percentages sum to 100%, representing a clean fully-diluted cap table on the day of Series A close."
925
1085
  },
1086
+ {
1087
+ "slug": "epc-procurement",
1088
+ "diagram": "epc",
1089
+ "title": "Procurement EPC with XOR split",
1090
+ "description": "An ARIS event-driven process chain for purchase requisitions, branching on an XOR connector and re-joining on an AND. The engine validates EPC well-formedness \u2014 event/function alternation and the event-cannot-decide rule.",
1091
+ "standard": "ARIS EPC (Scheer)",
1092
+ "tags": [
1093
+ "epc",
1094
+ "aris",
1095
+ "business-process",
1096
+ "xor",
1097
+ "and",
1098
+ "control-flow"
1099
+ ],
1100
+ "complexity": 3,
1101
+ "featured": false,
1102
+ "dsl": 'epc "Purchase requisition"\n layout: tb\n event E1 "Material need identified"\n function F1 "Create requisition"\n function F2 "Determine source of supply"\n xor X1\n event E2 "Stock supplier exists"\n event E3 "New supplier needed"\n function F3 "Issue purchase order"\n function F4 "Run tender process"\n and A1\n event E4 "Order dispatched"\n event E5 "Supplier qualified"\n E1 -> F1 -> F2 -> X1\n X1 -> E2\n X1 -> E3\n E2 -> F3 -> A1\n E3 -> F4 -> A1\n A1 -> E4\n A1 -> E5',
1103
+ "notes": '## What this shows\n\nA SAP-style procurement process modelled as an ARIS event-driven process chain (EPC), the notation that strictly alternates passive **events** ("Material need identified") and active **functions** ("Create requisition"). After sourcing, an **XOR** connector splits the flow on a real decision \u2014 an existing stock supplier versus a new supplier needing a tender \u2014 and the two paths re-converge on an **AND** connector that fans back out to the dispatched order and the qualified-supplier outcome.\n\nEPC\'s value here is structural validation, not a computed number. The engine checks the well-formedness rules and flags violations rather than silently drawing a broken model: event/function alternation along every path, single-in/single-out multiplicity carried by the connectors, reachability from start to end, and the **event-cannot-decide signature rule** \u2014 a passive event may not be the source of an XOR/OR split. That\'s why the decision routes *through* function `F2` before reaching the XOR, which is the correct ARIS form.'
1104
+ },
926
1105
  {
927
1106
  "slug": "erd-billing-ledger-audit-trail",
928
1107
  "diagram": "erd",
@@ -997,6 +1176,44 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
997
1176
  "dsl": 'erd\ntitle: "University Schema"\n\ntable Student {\n student_id int PK\n name varchar\n email varchar UK\n major_id int FK -> Major.major_id\n}\n\ntable Major {\n major_id int PK\n name varchar\n}\n\ntable Course {\n course_id int PK\n title varchar\n credits int\n}\n\ntable Enrollment {\n student_id int PK FK -> Student.student_id\n course_id int PK FK -> Course.course_id\n grade char\n}\n\nref Student.major_id many-mandatory -- one-mandatory Major.major_id : "majors in"\nref Enrollment.student_id many-mandatory -- one-mandatory Student.student_id\nref Enrollment.course_id many-mandatory -- one-mandatory Course.course_id',
998
1177
  "notes": "The university schema is the canonical introduction to associative entities. The M:N relationship between Student and Course is resolved through Enrollment, which carries the relationship attribute `grade`. Both Student and Course join Enrollment via composite primary keys (each FK column doubles as part of the PK)."
999
1178
  },
1179
+ {
1180
+ "slug": "eventtree-flammable-release",
1181
+ "diagram": "eventtree",
1182
+ "title": "Flammable release event tree (process QRA)",
1183
+ "description": "A loss-of-containment release branched through detection, isolation, and ignition. The engine computes each outcome frequency \u2014 safe shutdown, jet fire, dispersion, vapour-cloud explosion \u2014 and flags the dominant one.",
1184
+ "standard": "IEC 62502 / process QRA",
1185
+ "tags": [
1186
+ "eventtree",
1187
+ "eta",
1188
+ "qra",
1189
+ "loss-of-containment",
1190
+ "ignition",
1191
+ "explosion"
1192
+ ],
1193
+ "complexity": 2,
1194
+ "featured": false,
1195
+ "dsl": 'eventtree "Flammable release"\n initiating REL "Loss of containment" freq: 0.02\n function DET "Gas detection alarms" p: 0.05\n function ISO "Emergency isolation" p: 0.1\n function IGN "No ignition source" p: 0.3\n outcome s s s -> "Safe shutdown"\n outcome s s f -> "Jet fire"\n outcome s f * -> "Large dispersion"\n outcome f * * -> "Vapour cloud explosion"',
1196
+ "notes": "## What this shows\n\nThe quantitative risk assessment (QRA) event tree a process-safety study runs after a HAZOP flags a credible leak. The initiating event \u2014 a flammable release at `0.02` per year \u2014 is challenged by three safety functions in order: gas detection, emergency isolation, and the chance of no ignition source. The `IGN` function reads as a *success* when there is no ignition (`s`), so its failure leg `f` is where the fire or explosion lives.\n\nThe value is the computed end-state frequencies. The engine propagates the release frequency through each branch's success/failure probability to size each consequence \u2014 safe shutdown, jet fire, large dispersion, vapour-cloud explosion \u2014 and accents the dominant sequence in red, the same role the single point of failure plays in a fault tree. That ranked, numeric reading is what turns a forking ladder into an analysis."
1197
+ },
1198
+ {
1199
+ "slug": "eventtree-loca",
1200
+ "diagram": "eventtree",
1201
+ "title": "Large LOCA event tree (reactor PRA)",
1202
+ "description": "A large-break loss-of-coolant accident branched through four reactor safety functions. The engine computes every sequence frequency from the initiating rate and per-function failure probabilities, then highlights the dominant accident sequence.",
1203
+ "standard": "IEC 62502 / NUREG (WASH-1400)",
1204
+ "tags": [
1205
+ "eventtree",
1206
+ "eta",
1207
+ "pra",
1208
+ "nuclear",
1209
+ "sequence-frequency",
1210
+ "core-damage"
1211
+ ],
1212
+ "complexity": 3,
1213
+ "featured": false,
1214
+ "dsl": 'eventtree "Large LOCA"\n initiating LOCA "Large-break LOCA" freq: 1e-4\n function RT "Reactor trips" p: 0.001\n function ECCS "ECCS injection" p: 0.01\n function CHR "Containment heat removal" p: 0.02\n function CI "Containment integrity" p: 0.005\n outcome s s s s -> "OK"\n outcome s s s f -> "Late release"\n outcome s s f * -> "Late release"\n outcome s f * * -> "Early release"\n outcome f * * * -> "Core damage"',
1215
+ "notes": '## What this shows\n\nThe textbook nuclear probabilistic-risk-assessment (PRA) tree. One initiating event \u2014 a large pipe break draining the coolant at a frequency of `1e-4` per year \u2014 is asked, left to right, whether each safety function holds: reactor trip, emergency core cooling (ECCS), containment heat removal, then containment integrity. Each `*` prunes a path that has already terminated, so the tree stays compact instead of ballooning to a full 2\u2074 ladder.\n\nThe engine does the arithmetic an event tree exists to give. It multiplies the initiating frequency by the success leg (`1 \u2212 p`) or failure leg (`p`) at each branch to get every sequence frequency, sums the two `"Late release"` leaves into one rolled-up outcome, and paints the largest-frequency path \u2014 the dominant accident sequence \u2014 in red. That computed answer, not the forking picture, is the deliverable a drawing tool can\'t produce.'
1216
+ },
1000
1217
  {
1001
1218
  "slug": "faulttree-pump-redundancy",
1002
1219
  "diagram": "faulttree",
@@ -1222,6 +1439,44 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1222
1439
  "dsl": "flowchart LR\n start([New order received])\n start --> validate{Inventory available?}\n validate -->|Yes| reserve[Reserve items]\n validate -->|No| notify[Notify customer]\n notify --> done([End])\n reserve --> payment{Payment authorized?}\n payment -->|Yes| ship[Ship order]\n payment -->|No| cancel[Cancel & release]\n ship --> confirm[Send confirmation email]\n confirm --> done\n cancel --> done",
1223
1440
  "notes": '## Scenario\n\nA product ops lead circulates this flowchart during an ops-review meeting to align engineering, customer support, and fulfillment on the single source of truth for what happens when a new order comes in. It surfaces the two decision gates (inventory, payment) and the three exception paths (out-of-stock notification, payment failure with released hold, successful ship with confirmation).\n\n## Annotation key\n\n- `([\u2026])` \u2014 stadium / terminal; used for Start and End\n- `{\u2026}` \u2014 diamond; decision node\n- `[\u2026]` \u2014 rectangle; process step\n- `-->|label|` \u2014 edge with a branch label (`Yes` / `No`)\n\n## How to read\n\nStart at the top-left terminal. Inventory check gates the first branch \u2014 a "No" routes straight to the End after notification. A "Yes" reserves stock then hits the payment gate. Payment failure releases the reservation and goes to End; success ships and emails the customer. Every path terminates at the same End node, so nothing dangles.'
1224
1441
  },
1442
+ {
1443
+ "slug": "fmea-ev-battery-dfmea",
1444
+ "diagram": "fmea",
1445
+ "title": "EV battery pack DFMEA",
1446
+ "description": "A design FMEA for an electric-vehicle battery pack. The engine flattens the failure chain, computes RPN = S \xD7 O \xD7 D, and derives the AIAG-VDA Action Priority \u2014 keeping a S10 safety failure High even when its RPN looks low.",
1447
+ "standard": "AIAG-VDA FMEA Handbook (2019) / IEC 60812",
1448
+ "tags": [
1449
+ "fmea",
1450
+ "dfmea",
1451
+ "rpn",
1452
+ "action-priority",
1453
+ "severity",
1454
+ "automotive"
1455
+ ],
1456
+ "complexity": 3,
1457
+ "featured": false,
1458
+ "dsl": 'fmea "EV battery pack DFMEA"\n type: design\n rank: ap\n flag: ap >= High\n number: DFMEA-2026-014\n item "Cell module" fn "Store and deliver energy"\n mode "Thermal runaway"\n effect "Pack fire / occupant injury" sev: 10\n cause "Internal short from dendrite growth" occ: 3\n controls prevention: "Cell qualification", detection: "In-line CT scan" det: 4\n cause "Overcharge past cutoff" occ: 2\n controls prevention: "BMS voltage clamp", detection: "Redundant voltage sense" det: 3\n mode "Capacity fade"\n effect "Reduced range" sev: 6\n cause "Electrolyte depletion" occ: 5\n controls detection: "Periodic SOH estimate" det: 6\n item "Busbar joint" fn "Conduct current between modules"\n mode "High-resistance connection"\n effect "Local overheating" sev: 8\n cause "Loose torque on weld" occ: 4\n controls detection: "End-of-line resistance test" det: 4',
1459
+ "notes": "## What this shows\n\nA design FMEA (DFMEA) on the highest-stakes subsystem in an electric vehicle. The nested chain reads item \u2192 mode \u2192 effect/cause/controls: a cell module that can suffer thermal runaway or capacity fade, and a busbar joint that can go high-resistance. Each effect carries a Severity, each cause an Occurrence, each control the Detection it earns \u2014 the three AIAG-VDA 1\u201310 scales.\n\nThe engine computes the priority rather than just tabling it. It flattens to one worksheet row per (item, mode, cause), multiplies RPN = S \xD7 O \xD7 D, and \u2014 the part that matters \u2014 derives the AIAG-VDA Action Priority, which is severity-primary. The thermal-runaway row sits at S10\xB7O3\xB7D4 with an RPN of 120; a naive RPN sort would rank it below a noisier low-severity defect, but Action Priority keeps every safety failure (S = 9\u201310) at **High** regardless. The `flag: ap >= High` directive highlights exactly those rows so the safety-critical work rises to the top."
1460
+ },
1461
+ {
1462
+ "slug": "fmea-injection-molding-pfmea",
1463
+ "diagram": "fmea",
1464
+ "title": "Injection-moulding PFMEA",
1465
+ "description": "A process FMEA for an injection-moulding line, ranked the legacy way by RPN. The engine computes RPN = S \xD7 O \xD7 D for every (step, mode, cause) row and flags the ones that breach the 100 threshold.",
1466
+ "standard": "AIAG-VDA FMEA Handbook (2019) / IEC 60812",
1467
+ "tags": [
1468
+ "fmea",
1469
+ "pfmea",
1470
+ "rpn",
1471
+ "process",
1472
+ "manufacturing",
1473
+ "scrap"
1474
+ ],
1475
+ "complexity": 2,
1476
+ "featured": false,
1477
+ "dsl": 'fmea "Injection moulding PFMEA"\n type: process\n rank: rpn\n flag: rpn > 100\n item "Mould fill step" fn "Fill cavity with melt"\n mode "Short shot"\n effect "Incomplete part scrapped" sev: 7\n cause "Injection pressure too low" occ: 5\n controls prevention: "Pressure setpoint lock", detection: "Vision check" det: 4\n cause "Blocked gate" occ: 3\n controls detection: "Cycle-time monitor" det: 6\n item "Cooling step" fn "Solidify part to spec"\n mode "Warpage"\n effect "Out-of-tolerance dimension" sev: 6\n cause "Uneven cooling channel flow" occ: 6\n controls detection: "CMM sampling" det: 7',
1478
+ "notes": "## What this shows\n\nA process FMEA (PFMEA) following the steps of an injection-moulding line \u2014 the fill step and the cooling step \u2014 rather than the parts of a product. Each process step gets its failure modes (short shot, warpage), the effect each has on the part, the process causes behind it, and the in-line controls that prevent or detect it.\n\nThis worksheet is ranked the legacy way with `rank: rpn`, so the engine sorts purely on RPN = S \xD7 O \xD7 D and the `flag: rpn > 100` directive lights up every row above 100 \u2014 here the uneven-cooling warpage row at S6\xB7O6\xB7D7 (RPN 252) and the short-shot pressure row at S7\xB7O5\xB7D4 (RPN 140). Each rendered row carries `data-rpn` and `data-ap`, so even on a legacy RPN worksheet the AIAG-VDA Action Priority is still computed and inspectable underneath."
1479
+ },
1225
1480
  {
1226
1481
  "slug": "genogram-brca-cancer",
1227
1482
  "diagram": "genogram",
@@ -1311,6 +1566,44 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1311
1566
  "dsl": 'genogram "The Potter Family"\n fleamont [male, 1909, 1979, deceased]\n euphemia [female, 1920, 1979, deceased]\n fleamont -- euphemia\n james [male, 1960, 1981, deceased]\n mr_evans [male, 1925, deceased]\n mrs_evans [female, 1928, deceased]\n mr_evans -- mrs_evans\n lily [female, 1960, 1981, deceased]\n petunia [female, 1958]\n james -- lily "m. 1978"\n harry [male, 1980, index]\n petunia -- vernon [male, 1951]\n dudley [male, 1980]\n harry -cutoff- petunia\n harry -hostile- dudley\n harry -close- lily',
1312
1567
  "notes": '## Scenario\n\nA teaching example for social work students learning genogram notation. The Potter family is fictional but emotionally rich \u2014 death years, a marriage date, cross-family emotional relationships, and three distinct relational patterns (cutoff, hostile, close) all in one diagram.\n\n## Annotation key\n\n- `[male/female, birth_year, death_year, deceased]` \u2014 person with death marker\n- `"m. 1978"` \u2014 marriage date label on the union line\n- `index` \u2014 marks Harry as the identified patient (proband)\n- `-cutoff-` \u2014 estrangement; drawn as two parallel bars across the relationship line\n- `-hostile-` \u2014 conflict; drawn as zigzag line\n- `-close-` \u2014 enmeshment/closeness; drawn as double parallel line\n\n## How to read\n\nRead each indented block as a family unit. James and Lily (index generation) both died in 1981. Harry\'s emotional world is defined by three relational lines: cutoff from Aunt Petunia, hostility toward cousin Dudley, and closeness to his deceased mother.'
1313
1568
  },
1569
+ {
1570
+ "slug": "gitgraph-release-flow",
1571
+ "diagram": "gitgraph",
1572
+ "title": "Git Flow release history",
1573
+ "description": "A realistic Git Flow branch/merge/tag history \u2014 develop, a feature branch, a tagged release, and a hotfix merged back to both lines. Mermaid gitGraph syntax, rendered through Schematex's zero-dependency engine.",
1574
+ "standard": "Mermaid gitGraph dialect",
1575
+ "tags": [
1576
+ "gitgraph",
1577
+ "git-flow",
1578
+ "branch",
1579
+ "merge",
1580
+ "tag",
1581
+ "hotfix"
1582
+ ],
1583
+ "complexity": 3,
1584
+ "featured": false,
1585
+ "dsl": 'gitGraph\n commit id: "init"\n commit id: "scaffold"\n branch develop\n checkout develop\n commit id: "feature scaffold"\n branch feature/login\n checkout feature/login\n commit id: "login UI"\n commit id: "login API"\n checkout develop\n merge feature/login\n commit id: "polish"\n checkout main\n merge develop tag: "v1.0"\n branch hotfix\n checkout hotfix\n commit id: "patch CVE" type: HIGHLIGHT\n checkout main\n merge hotfix tag: "v1.0.1"\n checkout develop\n merge main',
1586
+ "notes": "## What this shows\n\nA full Git Flow release cycle, the branching strategy teams adopt to keep a clean trunk. Work forks off `main` into a long-lived `develop`, then into a short-lived `feature/login` branch; the feature merges back to develop, develop merges to `main` as the tagged `v1.0` release. Then a security `hotfix` branches straight off `main`, lands a HIGHLIGHT commit, ships as `v1.0.1`, and is merged back down into develop so the fix isn't lost \u2014 the discipline that keeps the two lines from diverging.\n\nThe DSL is the Mermaid `gitGraph` dialect verbatim, so existing Mermaid sources port directly. The engine replays the operation list in order to assign each commit to a lane, routes the merge connectors from each branch tip without crossings, renders the patch commit emphasised as a HIGHLIGHT, and draws the version tags as flags \u2014 all from a KB-scale, dependency-free bundle."
1587
+ },
1588
+ {
1589
+ "slug": "idef0-order-fulfilment",
1590
+ "diagram": "idef0",
1591
+ "title": "IDEF0 A0 \u2014 fulfil customer order",
1592
+ "description": "An IDEF0 A0 function decomposition with all four ICOM arrow types. Inputs, controls, mechanisms and outputs each pin to a fixed box edge, so the model is correct by construction per FIPS PUB 183.",
1593
+ "standard": "FIPS PUB 183 (IDEF0)",
1594
+ "tags": [
1595
+ "idef0",
1596
+ "icom",
1597
+ "function-model",
1598
+ "decomposition",
1599
+ "fips-183",
1600
+ "structured-analysis"
1601
+ ],
1602
+ "complexity": 3,
1603
+ "featured": false,
1604
+ "dsl": 'idef0 "Fulfil customer order"\nnode A0\nfunction A1 "Validate order"\nfunction A2 "Pick and pack"\nfunction A3 "Ship and invoice"\ninput A1 "Customer order"\ncontrol A1 "Credit policy"\nmechanism A1 "Order management system"\nA1 -> A2 "Validated order"\ninput A2 "Inventory"\ncontrol A2 "Picking rules"\nmechanism A2 "Warehouse staff"\nA2 -> A3 "Packed shipment"\ncontrol A3 "Carrier contract"\nmechanism A3 "Shipping carrier"\noutput A3 "Delivered order"\noutput A3 "Invoice"',
1605
+ "notes": '## What this shows\n\nAn IDEF0 A0 diagram \u2014 the top-level functional decomposition of an order-fulfilment system \u2014 showing all four ICOM arrow roles. Reading the box edges by convention: **I**nputs enter on the left (the customer order, inventory), **C**ontrols govern from the top (credit policy, picking rules, carrier contract), **M**echanisms supply resources from the bottom (the order system, warehouse staff, carrier), and **O**utputs leave on the right (delivered order, invoice). The box-to-box flows (`A1 -> A2 "Validated order"`) chain the three activities.\n\nThe differentiator is that the model is correct by construction, not just drawn. Because the arrow keyword *is* the box edge it attaches to, the engine enforces ICOM placement \u2014 you cannot accidentally draw a control as an input. It resolves every box reference, assigns decomposition numbers (A0 \u2192 A1..A3), codes the boundary arrows down each edge (I1/C1/O1/M1\u2026), and applies the FIPS 3-to-6-box guideline. A flow that tried to *enter* a box via its `.output` edge would be rejected, because an output leaves a box \u2014 the standard is enforced, not suggested.'
1606
+ },
1314
1607
  {
1315
1608
  "slug": "ladder-conveyor-interlock",
1316
1609
  "diagram": "ladder",
@@ -1421,6 +1714,58 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1421
1714
  "dsl": 'logic "1-bit Full Adder"\ninput A, B, Cin\noutput Sum, Cout\ns1 = XOR(A, B)\nSum = XOR(s1, Cin)\nc1 = AND(A, B)\nc2 = AND(s1, Cin)\nCout = OR(c1, c2)',
1422
1715
  "notes": "## Scenario\n\nThe 1-bit full adder is the foundational building block of every arithmetic logic unit. Digital logic students derive it in lecture; FPGA engineers instantiate it in RTL. Schematex renders it from a purely functional description \u2014 no manual gate placement, no wire routing \u2014 making it easy to embed in textbooks, datasheets, or AI-generated hardware documentation.\n\n## Annotation key\n\n- `input A, B, Cin` \u2014 declare named input ports\n- `output Sum, Cout` \u2014 declare named output ports\n- `s1 = XOR(A, B)` \u2014 intermediate signal `s1` is the XOR of inputs A and B\n- `Sum = XOR(s1, Cin)` \u2014 the sum bit is the XOR of the partial sum and carry-in\n- `c1 = AND(A, B)` \u2014 carry generated when both A and B are 1\n- `c2 = AND(s1, Cin)` \u2014 carry propagated when partial sum is 1 and Cin is 1\n- `Cout = OR(c1, c2)` \u2014 carry-out is 1 if either generate or propagate carry is active\n\n## How to read\n\nThe diagram renders two XOR gates for the sum path (A\u2295B, then \u2295Cin) and two AND gates feeding an OR for the carry-out (the standard generate/propagate structure). The layout is automatically ranked so data flows left to right, inputs on the left edge, outputs on the right. Every 4-bit or 8-bit ripple-carry adder in textbooks is just this circuit chained together."
1423
1716
  },
1717
+ {
1718
+ "slug": "markov-customer-lifecycle",
1719
+ "diagram": "markov",
1720
+ "title": "Customer lifecycle (ergodic chain, stationary \u03C0)",
1721
+ "description": "A SaaS trial \u2192 active \u2192 churned chain. The engine validates the row-stochastic matrix, confirms the chain is ergodic, and computes the long-run stationary distribution \u03C0 \u2014 the steady-state share of customers in each state.",
1722
+ "standard": "Kemeny & Snell, Finite Markov Chains",
1723
+ "tags": [
1724
+ "markov",
1725
+ "stationary",
1726
+ "ergodic",
1727
+ "lifecycle",
1728
+ "churn",
1729
+ "steady-state"
1730
+ ],
1731
+ "complexity": 2,
1732
+ "featured": false,
1733
+ "dsl": 'markov "Customer lifecycle"\n analysis: classify, stationary\n Trial -> Trial : 0.4\n Trial -> Active : 0.5\n Trial -> Churned : 0.1\n Active -> Active : 0.8\n Active -> Trial : 0.05\n Active -> Churned : 0.15\n Churned -> Churned : 0.7\n Churned -> Trial : 0.3',
1734
+ "notes": "## What this shows\n\nA SaaS customer modelled as a memoryless hop between three states \u2014 Trial, Active, Churned \u2014 each month. Every row of probabilities leaving a state sums to 1 (the row-stochastic rule), and because churned customers can re-enter via Trial (`Churned -> Trial : 0.3`), no state is a dead end: the chain is **ergodic**, every state reachable from every other.\n\nThe engine does the linear algebra. It validates the matrix, runs SCC analysis to confirm a single recurrent class with no absorbing sink, then computes the **stationary distribution \u03C0** \u2014 the long-run share of your customer base sitting in each state once the system settles, independent of where it started. That steady-state answer (and the per-state classification) is carried in `data-*`, and it is the number a churn model exists to produce, not the circles-and-arrows."
1735
+ },
1736
+ {
1737
+ "slug": "markov-gamblers-ruin",
1738
+ "diagram": "markov",
1739
+ "title": "Gambler's ruin (absorbing chain)",
1740
+ "description": "The classic absorbing Markov chain with two sinks. The engine classifies the transient and absorbing states and computes the fundamental matrix \u2014 the probability of ending broke vs rich and the expected number of steps to absorption.",
1741
+ "standard": "Kemeny & Snell, Finite Markov Chains",
1742
+ "tags": [
1743
+ "markov",
1744
+ "absorbing",
1745
+ "fundamental-matrix",
1746
+ "gamblers-ruin",
1747
+ "transient",
1748
+ "expected-steps"
1749
+ ],
1750
+ "complexity": 3,
1751
+ "featured": false,
1752
+ "dsl": `markov "Gambler's ruin"
1753
+ analysis: classify, absorbing
1754
+ state Broke "$0" absorbing
1755
+ state Rich "$4" absorbing
1756
+ state S1
1757
+ state S2
1758
+ state S3
1759
+ Broke -> Broke : 1
1760
+ Rich -> Rich : 1
1761
+ S1 -> Broke : 0.5
1762
+ S1 -> S2 : 0.5
1763
+ S2 -> S1 : 0.5
1764
+ S2 -> S3 : 0.5
1765
+ S3 -> S2 : 0.5
1766
+ S3 -> Rich : 0.5`,
1767
+ "notes": "## What this shows\n\nThe canonical absorbing Markov chain. A gambler with \\$1, \\$2, or \\$3 (states S1\u2013S3) bets on a fair coin, moving up or down \\$1 each round, and stops only at ruin (\\$0) or the target (\\$4). The two endpoints are declared `absorbing` \u2014 once entered, never left (a self-loop of probability 1) \u2014 and the engine cross-checks that assertion against the matrix.\n\nThis is where the engine's linear algebra pays off. It classifies S1\u2013S3 as **transient** and Broke/Rich as **absorbing**, then forms the fundamental matrix N = (I \u2212 Q)\u207B\xB9 to compute the two answers an absorbing chain is built for: the **absorption probabilities** (starting from each state, the chance of ending broke vs rich \u2014 for a fair game, proportional to the distance to each barrier) and the **expected number of steps to absorption**. Those computed values, carried in `data-*`, are the result no drawing tool can give."
1768
+ },
1424
1769
  {
1425
1770
  "slug": "matrix-9-box-talent",
1426
1771
  "diagram": "matrix",
@@ -1516,6 +1861,78 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1516
1861
  "dsl": 'matrix johari "Self vs. Team \u2014 Q2 Reflection"\nstyle: table\nQ2: "Strong technical instincts"\nQ2: "Direct in code review"\nQ2: "Patient with juniors"\nQ1: "Interrupts in meetings"\nQ1: "Hard to read when stressed"\nQ3: "Imposter syndrome about leadership"\nQ3: "Anxiety about cross-team politics"\nQ4: "Capacity for difficult conversations under pressure"',
1517
1862
  "notes": "## Scenario\n\nA newly-promoted engineering manager runs a Johari exercise with her team during a 1:1 retro. She populates the **Open** cell (things both she and the team see); the team adds to **Blind** (things they see that she doesn't); she fills **Hidden** privately; **Unknown** is the open hypothesis space \u2014 capabilities and limitations that haven't surfaced yet.\n\nThe table form is the canonical Johari output. Coaches print it on a single page and walk through it with the coachee \u2014 a scatter plot of dots would defeat the entire purpose.\n\n## Annotation key\n\n- `matrix johari` \u2014 preset axes (Known to Self \xD7 Known to Others) with the four window panes\n- `style: table` \u2014 flips off axes/grid, places each pane title as a cell header, lists items as bullets\n- `Q1` = Blind (top-right: not known to self, known to others)\n- `Q2` = Open / Arena (top-left: known to self + others)\n- `Q3` = Hidden / Fa\xE7ade (bottom-left: known to self, not to others)\n- `Q4` = Unknown (bottom-right: not known to either \u2014 the growth hypothesis space)\n\n## How to read\n\nThe classic Johari coaching prompt: **how do you move items from Hidden \u2192 Open** (vulnerability work) **and from Blind \u2192 Open** (feedback-acceptance work)? An overstuffed Hidden pane signals psychological-safety debt; an empty Blind pane usually means the team hasn't been asked."
1518
1863
  },
1864
+ {
1865
+ "slug": "matrix-qfd-coffee-maker",
1866
+ "diagram": "matrix",
1867
+ "title": "Coffee maker House of Quality",
1868
+ "description": "A QFD House of Quality where the engine computes the technical-importance row (45 / 39 / 51) \u2014 the ranked answer to which engineering characteristic moves the most customer value \u2014 plus the diamond-cell roof that flags trade-offs between characteristics.",
1869
+ "standard": "Akao Quality Function Deployment",
1870
+ "tags": [
1871
+ "matrix",
1872
+ "qfd",
1873
+ "house-of-quality",
1874
+ "akao",
1875
+ "voice-of-customer"
1876
+ ],
1877
+ "complexity": 3,
1878
+ "featured": false,
1879
+ "dsl": 'matrix qfd "Coffee maker"\nwhat: "Quiet operation" weight: 5\nwhat: "Brews fast" weight: 3\nwhat: "Energy efficient" weight: 4\nhow: "Fan RPM" dir: down\nhow: "Heater watts" dir: up\nhow: "Insulation" dir: up\nrel (0,0): 9\nrel (0,2): 3\nrel (1,1): 9\nrel (2,1): 3\nrel (2,2): 9\nroof (0,1): --\nroof (1,2): +',
1880
+ "notes": "## What this shows\n\nThe **House of Quality** \u2014 the core matrix of Akao's Quality Function Deployment \u2014 translates what customers want into the engineering characteristics that deliver it. Customer requirements (**WHATs**) are the rows, each with an importance weight; engineering characteristics (**HOWs**) are the columns; the body cells record how strongly each HOW serves each WHAT on the 9 / 3 / 1 strong-medium-weak scale.\n\nThe differentiator is the computed row at the foot of the house: each column's **technical importance** is the sum of `weight \xD7 strength` down that column, here **45 / 39 / 51** \u2014 so Insulation (51) is the highest-leverage characteristic to invest in and Heater watts (39) the lowest. (Add `normalize: true` to read these as 33% / 29% / 38% instead.) Above the columns, the **roof** is a half-matrix of diamond cells recording HOW-to-HOW correlations: `roof (0,1): --` flags that lowering Fan RPM while raising Heater watts is a trade-off, while `roof (1,2): +` flags that Heater watts and Insulation reinforce each other."
1881
+ },
1882
+ {
1883
+ "slug": "matrix-sipoc-order-fulfilment",
1884
+ "diagram": "matrix",
1885
+ "title": "Order fulfilment SIPOC",
1886
+ "description": "The one-page scoping table that opens a Six Sigma DMAIC project \u2014 Suppliers, Inputs, Process, Outputs, Customers in five fixed columns, pinning down exactly where the process starts and ends before anyone improves it.",
1887
+ "standard": "Six Sigma DMAIC",
1888
+ "tags": [
1889
+ "matrix",
1890
+ "sipoc",
1891
+ "six-sigma",
1892
+ "dmaic",
1893
+ "process-scoping"
1894
+ ],
1895
+ "complexity": 2,
1896
+ "featured": false,
1897
+ "dsl": 'matrix sipoc "Order fulfilment"\nsuppliers: "Vendor", "Warehouse"\ninputs: "PO", "Stock levels"\nprocess: "Receive order", "Pick", "Pack", "Ship"\noutputs: "Shipped package", "Invoice"\ncustomers: "End customer", "Finance"',
1898
+ "notes": "## What this shows\n\nA **SIPOC** is the first artifact a Six Sigma team builds in the *Define* phase of DMAIC. It names \u2014 in five columns read left to right \u2014 everyone and everything the process touches: **S**uppliers hand in **I**nputs, the **P**rocess turns them into **O**utputs, and **C**ustomers receive them. Here the order-fulfilment process runs `Receive order \u2192 Pick \u2192 Pack \u2192 Ship`, fed by purchase orders and stock levels from the vendor and warehouse, and producing a shipped package for the end customer and an invoice for finance.\n\nThe point of a SIPOC is boundary-setting before measurement: it forces the team to agree where the process starts, where it ends, and who hands work in and out of it. The five columns always render in canonical S-I-P-O-C order, so the diagram reads correctly even when the blocks are authored out of sequence."
1899
+ },
1900
+ {
1901
+ "slug": "mindmap-driver-readmissions",
1902
+ "diagram": "mindmap",
1903
+ "title": "Driver diagram \u2014 reduce 30-day readmissions",
1904
+ "description": "An IHI driver diagram that traces a quality-improvement aim left to right through its primary drivers and the concrete change ideas meant to move them.",
1905
+ "standard": "Driver Diagram: IHI improvement model",
1906
+ "tags": [
1907
+ "mindmap",
1908
+ "driver",
1909
+ "quality-improvement",
1910
+ "healthcare",
1911
+ "aim-driver-change"
1912
+ ],
1913
+ "complexity": 2,
1914
+ "featured": false,
1915
+ "dsl": "%% style: driver\n# Reduce 30-day readmissions\n## Reliable discharge process\n- Teach-back at bedside\n- Med reconciliation\n## Timely follow-up\n- Appointment within 7 days\n- Post-discharge phone call",
1916
+ "notes": '## What this shows\n\nA **driver diagram** \u2014 the planning artifact from the IHI (Institute for Healthcare Improvement) model for improvement. It reads left to right as a tidy tree: the **aim** ("reduce 30-day readmissions") on the far left, the **primary drivers** that move it ("reliable discharge process", "timely follow-up") in the next column, and the concrete **change ideas** \u2014 teach-back at the bedside, medication reconciliation, an appointment within 7 days \u2014 branching out to the right.\n\nThe value is the line of sight it forces. Every change idea on the right traces back through a driver to the aim, so a team can defend *why* each intervention is on the board and spot which drivers still have no ideas attached. It is the ordinary `#` / `##` / `-` mindmap input, rendered as an aim \u2192 drivers \u2192 change-ideas tree by adding `%% style: driver`.'
1917
+ },
1918
+ {
1919
+ "slug": "mindmap-futureswheel-remote-work",
1920
+ "diagram": "mindmap",
1921
+ "title": "Futures wheel \u2014 remote work becomes default",
1922
+ "description": "A structured-brainstorming futures wheel that ripples a single trend outward into first- and second-order consequences across concentric, color-coded rings.",
1923
+ "standard": "Futures Wheel: Glenn (1972)",
1924
+ "tags": [
1925
+ "mindmap",
1926
+ "futureswheel",
1927
+ "foresight",
1928
+ "consequence-mapping",
1929
+ "scenario-planning"
1930
+ ],
1931
+ "complexity": 2,
1932
+ "featured": false,
1933
+ "dsl": "%% style: futureswheel\n# Remote work becomes default\n## Less commuting\n- Lower carbon emissions\n- Cheaper city living\n## Distributed teams\n- Async communication norms\n- Global hiring pools\n## Empty offices\n- Commercial real estate slump\n- Repurposed to housing",
1934
+ "notes": "## What this shows\n\nA **futures wheel** \u2014 Jerome Glenn's 1971/72 technique for thinking past a trend's obvious effects. A single event sits at the hub (\"remote work becomes default\"), its first-order consequences land on the inner ring, and each of those fans out to its own second-order consequences on the next ring. Reading outward is reading forward in causal time: less commuting *leads to* lower emissions and cheaper city living; empty offices *lead to* a commercial-real-estate slump and housing conversions.\n\nThe layout does the discipline for you. Every child is kept inside its parent's angular sector, so a branch never tangles with its neighbors, and each ring is color-coded by order \u2014 the visual cue that tells a workshop room how many steps removed a given consequence is from the original trend. It is the same `#` / `##` / `-` mindmap input you already write, switched on with `%% style: futureswheel`."
1935
+ },
1519
1936
  {
1520
1937
  "slug": "mindmap-product-launch",
1521
1938
  "diagram": "mindmap",
@@ -2015,6 +2432,43 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
2015
2432
  "dsl": 'phylo "Bacterial Diversity"\n newick: "((((Ecoli:0.1,Salmonella:0.12):0.05[&&NHX:B=98],Vibrio:0.2):0.08[&&NHX:B=85],((Bacillus:0.15,Staph:0.18):0.06[&&NHX:B=92],Listeria:0.22):0.1):0.15,((Myco_tb:0.3,Myco_leprae:0.28):0.12[&&NHX:B=100],(Strepto:0.25,Lactobacillus:0.2):0.08[&&NHX:B=78]):0.2);"\n clade Gamma = (Ecoli, Salmonella, Vibrio) [color: "#1E88E5", label: "\u03B3-Proteobacteria"]\n clade Firmi = (Bacillus, Staph, Listeria, Strepto, Lactobacillus) [color: "#E53935", label: "Firmicutes"]\n clade Actino = (Myco_tb, Myco_leprae) [color: "#43A047", label: "Actinobacteria"]\n scale "substitutions/site"',
2016
2433
  "notes": '## Scenario\n\nA microbiologist or bioinformatician pastes a Newick tree string exported from RAxML, IQ-TREE, or MEGA and immediately gets a publication-ready SVG with clade highlights and a branch-length scale bar \u2014 no manual layout required.\n\n## Annotation key\n\n- `newick: "..."` \u2014 standard Newick format tree string; branch lengths follow `:` after each taxon name\n- `[&&NHX:B=98]` \u2014 NHX annotation; `B=` is the bootstrap support value (0\u2013100), rendered on internal nodes\n- `clade id = (taxon, ...)` \u2014 defines a named clade by listing its leaf members\n- `[color: "#hex", label: "..."]` \u2014 colors the clade\'s subtree and adds a labeled arc\n- `scale "..."` \u2014 draws a calibrated scale bar with the given unit label\n\n## How to read\n\nThe tree shows three major bacterial clades. Blue (\u03B3-Proteobacteria): *E. coli*, *Salmonella*, and *Vibrio* cluster with 98% bootstrap support. Red (Firmicutes): *Bacillus*, *Staph*, *Listeria*, *Streptococcus*, and *Lactobacillus*. Green (Actinobacteria): the two *Mycobacterium* species form a highly supported clade (bootstrap 100). Branch lengths represent substitutions per site \u2014 longer branches indicate faster evolutionary rates.'
2017
2434
  },
2435
+ {
2436
+ "slug": "phylo-dendrogram-gene-expression",
2437
+ "diagram": "phylo",
2438
+ "title": "Gene expression clusters (cut into flat groups)",
2439
+ "description": "A hierarchical-clustering dendrogram of gene-expression samples, sliced at a chosen height so the continuous tree collapses into distinctly colored flat clusters with a dashed threshold line.",
2440
+ "standard": "Hierarchical clustering (cophenetic height)",
2441
+ "tags": [
2442
+ "phylo",
2443
+ "dendrogram",
2444
+ "clustering",
2445
+ "cut",
2446
+ "cophenetic",
2447
+ "fcluster"
2448
+ ],
2449
+ "complexity": 2,
2450
+ "featured": false,
2451
+ "dsl": 'phylo "Gene expression clusters" [mode: dendrogram]\n newick: "(((A:1,B:1):2,C:3):2,(D:2,E:2):3);"\n cut 4\n scale "cluster distance"',
2452
+ "notes": "## What this shows\n\nThe same tree the `phylo` engine draws for evolution, read instead as the output of **hierarchical agglomerative clustering** (Sokal & Michener 1958 \u2014 the form `scipy.cluster.hierarchy.dendrogram` produces). Each internal node sits at its **merge height**: the cophenetic distance at which its two child clusters fuse. Leaves align on a common baseline, branches are rectangular elbows, and a height axis lets you read off exactly how far apart any two leaves are before they first share a cluster \u2014 `(A, B)` join low and tight, while `(D, E)` only merge with the rest near the top.\n\nThe `cut 4` line is what makes this actionable. It slices the tree at height 4: every subtree that fuses below that line becomes one **flat cluster**, each painted a distinct color, with a dashed threshold line drawn across the chart. This is the dendrogram counterpart of choosing `k` clusters in `fcluster` \u2014 turning a continuous similarity tree into the discrete groups you actually act on."
2453
+ },
2454
+ {
2455
+ "slug": "phylo-dendrogram-sample-clustering",
2456
+ "diagram": "phylo",
2457
+ "title": "Sample clustering dendrogram (no cut)",
2458
+ "description": "A bare hierarchical-clustering dendrogram with internal nodes placed at their merge height and a cluster-distance axis \u2014 the plain similarity tree before any flat-cluster threshold is applied.",
2459
+ "standard": "Hierarchical clustering (cophenetic height)",
2460
+ "tags": [
2461
+ "phylo",
2462
+ "dendrogram",
2463
+ "clustering",
2464
+ "cophenetic",
2465
+ "similarity"
2466
+ ],
2467
+ "complexity": 1,
2468
+ "featured": false,
2469
+ "dsl": 'phylo "Sample clustering" [mode: dendrogram]\n newick: "(((A:1,B:1):2,C:3):2,(D:2,E:2):3);"\n scale "cluster distance"',
2470
+ "notes": "## What this shows\n\nThe plainest reading of a **hierarchical-clustering dendrogram**: five samples joined bottom-up, with each internal node placed at its **merge height** \u2014 the cophenetic distance at which its two child clusters fuse. Leaves align on a shared baseline and a cluster-distance axis runs down the side, so the vertical position of every join tells you how similar the samples beneath it are.\n\nUnlike a cladogram (topology only) or a phylogram (branch length = evolutionary distance), here the height of the merge *is* the message: samples that fuse low are alike, samples that only fuse near the top are distant. This example omits the `cut` directive on purpose \u2014 it is the raw similarity tree, before you commit to a threshold that would carve it into flat clusters."
2471
+ },
2018
2472
  {
2019
2473
  "slug": "phylo-sars-cov-2-chronogram",
2020
2474
  "diagram": "phylo",
@@ -2564,6 +3018,25 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
2564
3018
  "dsl": 'state "Traffic Light"\n\ninitial i\nfinal f\n\ni -> Red\nRed -> Green : timer\nGreen -> Yellow : timer\nYellow -> Red : timer\nRed -> f : power_off',
2565
3019
  "notes": "The traffic light is to state diagrams what the pump loop is to P&IDs: every textbook uses it, every engineer has drawn it, and if you understand it you understand the grammar of every more complex model.\n\n**Why a state diagram and not a flowchart.** A flowchart for a traffic light would show a loop: start \u2192 Red \u2192 (timer fires?) \u2192 Green \u2192 (timer fires?) \u2192 Yellow \u2192 back to Red. That works for describing an algorithm, but it misses the essential question: *what is the system doing right now?* A state machine makes the current state a first-class concept. The traffic light is not executing a loop \u2014 it *is* Red, or it *is* Green. Transitions are events that change what it is. This distinction matters the moment you add complexity: \"what happens if a pedestrian button is pressed while we're in Green?\" You answer that by looking at the transitions out of Green, not by tracing a flowchart path.\n\n**UML pseudo-states.** The filled black circle (`initial i`) and the bull's-eye circle (`final f`) are pseudo-states \u2014 they are not real states the system can dwell in, just notational entry and exit points. The initial pseudo-state shows where the machine starts; the arrow from `i` to Red tells you Red is the first real state. The final pseudo-state shows where the machine terminates. In the traffic light, termination is the `power_off` event from Red \u2014 the system shuts down from the Red state only (it wouldn't be safe to power off mid-Green).\n\n**Transition labels.** Each arrow is labeled with the trigger event that causes the transition. `timer` means \"the countdown for this phase has elapsed.\" In a real embedded implementation, this would be a hardware timer interrupt or a software watchdog expiry. Schematex doesn't execute the state machine \u2014 it renders the model. The labels are free text; you write exactly what your system calls the event.\n\n**Cyclic structure.** The three main states form a cycle: Red \u2192 Green \u2192 Yellow \u2192 Red. Most state diagrams describing continuous systems have cycles \u2014 the system runs until something external stops it. The `power_off` transition is the only way to reach the final state, and it is only modeled on Red because that is the safe state to stop in. If you wanted to model an emergency override (traffic officer stops the light mid-cycle), you would add `power_off` transitions from Green and Yellow too.\n\n**From model to code.** A UML state diagram maps directly to an enum + switch statement, a state table, or a state-machine framework (XState, Boost.MSM, Qt State Machine). The diagram is the specification; the implementation strategy is separate. Generating this diagram from a DSL means you can keep the spec in version control alongside the code, diff it in PRs, and regenerate it from an LLM prompt when requirements change."
2566
3020
  },
3021
+ {
3022
+ "slug": "threatmodel-ecommerce-checkout",
3023
+ "diagram": "threatmodel",
3024
+ "title": "E-commerce checkout threat model (STRIDE)",
3025
+ "description": "A security data-flow diagram for a checkout flow with three trust boundaries. The engine applies the STRIDE-per-element mapping and flags every flow that crosses a trust boundary \u2014 where spoofing and tampering concentrate.",
3026
+ "standard": "Microsoft STRIDE / Shostack (2014)",
3027
+ "tags": [
3028
+ "threatmodel",
3029
+ "stride",
3030
+ "dfd",
3031
+ "trust-boundary",
3032
+ "security",
3033
+ "data-flow"
3034
+ ],
3035
+ "complexity": 3,
3036
+ "featured": false,
3037
+ "dsl": 'threatmodel "E-commerce checkout"\nexternal: Customer\nexternal: Payment Gateway\nprocess 1.0: Web App\nprocess 2.0: Order Service\ndatastore D1: Orders DB\ndatastore D2: Audit Log\nCustomer -> 1.0 : "HTTPS Checkout"\n1.0 -> 2.0 : "Place order"\n2.0 -> D1 : "Write order"\n2.0 -> Payment Gateway : "Charge card"\n2.0 -> D2 : "Order event"\nboundary "Internet" { Customer, Payment_Gateway }\nboundary "DMZ" { 1.0 }\nboundary "Internal" { 2.0, D1, D2 }',
3038
+ "notes": "## What this shows\n\nA STRIDE threat model of an e-commerce checkout drawn as a data-flow diagram (DFD): two external entities (the customer and a third-party payment gateway), two processes (the web app in the DMZ, the order service internally), and two data stores (the orders database and an audit log). Three trust boundaries partition the system \u2014 Internet, DMZ, Internal \u2014 and the labelled flows carry the data crossing between them.\n\nThe engine does the STRIDE-per-element analysis, not just the boxes. It applies the canonical mapping \u2014 externals get Spoofing/Repudiation, processes get the full S-T-R-I-D-E, stores get Tampering/Information-disclosure/DoS, and the **audit log additionally gets Repudiation** because it matches the log/journal pattern. Most usefully, it **flags every flow that crosses a trust boundary** \u2014 the customer\u2192web-app HTTPS request (Internet\u2192DMZ), the order-service\u2192payment-gateway charge (Internal\u2192Internet) \u2014 because boundary crossings are where spoofing, tampering, and information disclosure concentrate. Each element and flow carries its applicable STRIDE categories in `data-*`."
3039
+ },
2567
3040
  {
2568
3041
  "slug": "timeline-company-milestones",
2569
3042
  "diagram": "timeline",
@@ -2862,7 +3335,7 @@ var SYNTAX = {
2862
3335
  },
2863
3336
  "phylo": {
2864
3337
  "title": "Phylogenetic tree",
2865
- "content": '## 1. Your first phylogenetic tree\n\nThe smallest useful tree: four taxa, two clades.\n\n```\nphylo "Vertebrates"\n newick: "((Human:0.1,Chimp:0.08):0.03,(Dog:0.35,Cat:0.30):0.2);"\n```\n\nThree rules cover 80% of usage:\n\n1. Start with `phylo`, optionally followed by a quoted title and bracket props.\n2. Provide the tree topology in `newick:` format \u2014 the standard Newick string, quoted, on one line. The trailing `;` is optional.\n3. Optionally define **clade** highlight groups and a **scale** label below the newick line.\n\n> Comments must start with `#` on their own line. Inline trailing comments are not supported.\n\n---\n\n## 2. Input formats\n\n### 2.1 Newick format\n\nNewick is the primary input. The full grammar is:\n\n```\n(A,B,(C,D)); # topology only\n(A:0.1,B:0.2,(C:0.3,D:0.4):0.5); # with branch lengths\n((A:0.1,B:0.2):0.05[&&NHX:B=98],(C,D):0.08); # NHX bootstrap\n(\'Homo sapiens\':0.1,\'Mus musculus\':0.2); # quoted names with spaces\n```\n\nBranch lengths follow the node name after a colon. Internal node support values can appear as plain brackets `[95]` or as NHX `[&&NHX:B=95]`.\n\n```\nphylo "Newick examples"\n newick: "((A:0.1,B:0.2):0.05[&&NHX:B=98],(C:0.3,D:0.4):0.08[&&NHX:B=87]);"\n```\n\n**Newick rules the parser accepts:**\n\n| Feature | Syntax | Notes |\n|---|---|---|\n| Leaf name | `A`, `Homo_sapiens` | No spaces \u2014 use `_` or quote |\n| Quoted leaf name | `\'Homo sapiens\'` | Single quotes; `\'\'` is a literal quote inside |\n| Branch length | `:0.035` after name | Float; optional |\n| Internal node name | `(A,B)ancestor` | After closing `)` |\n| Bootstrap (plain) | `(A,B)[95]` | Integer or float in brackets |\n| Bootstrap (NHX) | `(A,B)[&&NHX:B=95]` | `B=` field; other NHX fields stored but not rendered |\n| Semicolon | `;` at end | Optional \u2014 parser strips it |\n| Polytomy | `(A,B,C)` | More than 2 children |\n\n### 2.2 Indent DSL\n\nFor hand-written or small trees, Schematex offers an indentation-based alternative that is easier to read and edit than raw Newick:\n\n```\nphylo "Vertebrates (indent DSL)" [mode: phylogram]\nroot:\n :0.03\n Human: 0.1\n Chimp: 0.08\n :0.2\n Dog: 0.35\n Cat: 0.30\nscale "substitutions/site"\n```\n\n**Indent DSL rules:**\n\n| Syntax | Meaning |\n|---|---|\n| `Name: length` | Leaf node with branch length |\n| `: length` | Unnamed internal node with branch length |\n| `Name` | Leaf node, no branch length (cladogram) |\n| `Name [N]` | Node with support value N |\n| Deeper indent | Child of the node above at a shallower indent |\n| `#` line | Comment, ignored |\n\nThe first line that ends with `:` and has no spaces triggers indent-tree mode (e.g. `root:`). The name before the colon becomes the root label; all indented lines below become its children.\n\n---\n\n## 3. Layout\n\nSet the layout in the header brackets: `phylo "Title" [layout: rectangular]`.\n\n| Layout | Value | Description |\n|---|---|---|\n| Rectangular | `rectangular` | Default. L-shaped branches; root on left, tips on right |\n| Slanted | `slanted` | Diagonal lines from parent to child; more compact |\n| Circular | `circular` | Root at center, tips around the circumference |\n| Unrooted | `unrooted` | Equal-angle radial; emphasizes distance, not ancestry |\n\n`[unrooted]` as a bare flag is equivalent to `[layout: unrooted]`.\n\n**Circular** \u2014 root at center, tips fanning outward. Most visually striking for many-taxa trees with clade highlights.\n\n```\nphylo "Vertebrates \u2014 circular" [layout: circular]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates", highlight: both]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora", highlight: both]\n```\n\n**Rectangular** \u2014 L-shaped branches; root on the left, tips on the right. The classic phylogram form for published figures.\n\n```\nphylo "Bacterial Diversity" [layout: rectangular, mode: phylogram]\n newick: "((((Ecoli:0.1,Salmonella:0.12):0.05[&&NHX:B=98],Vibrio:0.2):0.08,((Bacillus:0.15,Staph:0.18):0.06[&&NHX:B=92],Listeria:0.22):0.1):0.15,((Myco_tb:0.3,Myco_leprae:0.28):0.12[&&NHX:B=100],(Strepto:0.25,Lactobacillus:0.2):0.08):0.2);"\n clade Gamma = (Ecoli, Salmonella, Vibrio) [color: "#1E88E5", label: "\u03B3-Proteobacteria"]\n clade Firmi = (Bacillus, Staph, Listeria) [color: "#E53935", label: "Firmicutes"]\n scale "substitutions/site"\n```\n\n**Slanted** \u2014 diagonal lines from parent to child; more compact than rectangular, same left-to-right reading direction.\n\n```\nphylo "Vertebrates \u2014 slanted" [layout: slanted]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates"]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora"]\n scale "substitutions/site"\n```\n\n**Unrooted** \u2014 equal-angle radial layout; de-emphasizes the root, emphasizes pairwise distance between all taxa.\n\n```\nphylo "Vertebrates \u2014 unrooted" [unrooted]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates"]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora"]\n```\n\n---\n\n## 4. Mode\n\nSet with `[mode: \u2026]` in the header (or in a `style [mode: \u2026]` line).\n\n| Mode | Value | Branch length meaning |\n|---|---|---|\n| Phylogram | `phylogram` | Default. Proportional to evolutionary distance (substitutions/site) |\n| Cladogram | `cladogram` | Ignored \u2014 tips align; only topology matters |\n| Chronogram | `chronogram` | Proportional to divergence time; all tips align to "present" |\n\nChronogram requires branch lengths in units of time plus `[mrsd: "YYYY"]` (most-recent sampling date) in the header so the renderer can align tips to present.\n\n```\nphylo "SARS-CoV-2 variants" [mode: chronogram, mrsd: "2023"]\n newick: "((Alpha:0.5,Delta:0.4):0.3,Omicron:0.8);"\n scale "years"\n```\n\n---\n\n## 5. Clade highlighting\n\nA `clade` line marks a monophyletic group with a color, an optional label, and an optional highlight mode.\n\n```\nclade ID = (member1, member2, ...) [color: "#hex", label: "text", highlight: mode]\n```\n\n| Prop | Values | Effect |\n|---|---|---|\n| `color:` | hex string e.g. `"#1E88E5"` | Branch and/or background color |\n| `label:` | quoted string | Clade label shown at right margin |\n| `highlight:` | `branch`, `background`, `both` | `branch` colors lines; `background` shades the region; `both` does both |\n\nMembers are tip (leaf) IDs from the Newick string. The renderer computes the MRCA of the listed tips and highlights the entire subtree rooted there.\n\n```\nphylo "Mammal clades" [layout: rectangular]\n newick: "(((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,Mouse:0.45):0.05,(Dog:0.35,(Cat:0.30,Tiger:0.32):0.1):0.2);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates", highlight: both]\n clade Carnivora = (Dog, Cat, Tiger) [color: "#E53935", label: "Carnivora", highlight: branch]\n```\n\n---\n\n## 6. Scale bar and outgroup\n\n**Scale bar:** `scale "label"` \u2014 adds a bar at the bottom. The label describes the unit (e.g. `"substitutions/site"`, `"Mya"`). Omit for cladogram mode where branch lengths have no meaning.\n\n**Outgroup:** `outgroup: taxonId` \u2014 records the outgroup for documentation; the renderer may use it to visually mark the outgroup taxon.\n\n```\nphylo "Vertebrates"\n newick: "((Human:0.1,Chimp:0.08):0.03,Lamprey:0.8);"\n outgroup: Lamprey\n scale "substitutions/site"\n```\n\n---\n\n## 7. Header props reference\n\nAll options go inside `[\u2026]` on the `phylo` header line, or in a `style [\u2026]` line anywhere in the body.\n\n| Prop | Values | Default | Effect |\n|---|---|---|---|\n| `layout:` | `rectangular`, `slanted`, `circular`, `unrooted` | `rectangular` | Tree layout |\n| `mode:` | `phylogram`, `cladogram`, `chronogram` | `phylogram` | Branch length semantics |\n| `unrooted` | (flag) | \u2014 | Equivalent to `layout: unrooted` |\n| `branch-width:` | number | `1.5` | Stroke width of branches |\n| `openAngle:` | number (degrees) | `0` | Fan gap for circular layout (0 = full 360\xB0) |\n| `mrsd:` | quoted year string | \u2014 | Most-recent sampling date for chronograms |\n\n---\n\n## 8. Labels & comments\n\n- **Title:** `phylo "Tree of Life"` \u2014 first line only.\n- **Scale label:** `scale "substitutions/site"` \u2014 one per document.\n- **Clade label:** `[label: "Primates"]` inside a `clade` line.\n- **Comments:** `#` at the start of a line (after leading whitespace). Inline trailing comments are not supported.\n\n---\n\n## 9. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `newick: (A,B,C);` (unquoted) | `PhyloParseError: Phylo document must start with \'phylo\'` | Quote the Newick string: `newick: "(A,B,C);"` |\n| Tip name with a space: `Homo sapiens:0.1` | Parsed as `Homo` \u2014 space terminates an unquoted name | Use underscore (`Homo_sapiens`) or single-quote (`\'Homo sapiens\'`) |\n| Leaf ID in `clade` doesn\'t match Newick name | Clade silently has 0 members; no highlight | Copy names exactly as they appear in the Newick string |\n| `clade X = (A, B)` with no `newick:` or indent tree | `PhyloParseError: No tree definition found` | Add a `newick:` line or an indent tree block |\n| `mode: chronogram` with no branch lengths | Renderer treats all lengths as 0; tips overlap at root | Add `:length` to every edge in the Newick string |\n| `root:` line not detected | If the `root:` line has a space in the name (e.g. `My root:`) the indent tree is not triggered | Use a single-word root label or `root:` |\n| Newick with internal node names: `(A,B)ancestor:0.5` | Parses fine \u2014 `ancestor` is the internal node label | Supported; internal names appear on internal nodes |\n\n---\n\n## 10. Grammar (EBNF)\n\n```text\ndocument = header (blank | comment | newick-line | scale-line\n | outgroup-line | clade-line | style-line | indent-line)*\n\nheader = "phylo" ( WS quoted-string )? ( WS "[" props "]" )? NEWLINE\nquoted-string = \'"\' any-char-but-quote* \'"\'\n\nnewick-line = "newick:" WS quoted-newick NEWLINE\nscale-line = "scale" ( WS quoted-string )? NEWLINE\noutgroup-line = "outgroup:" WS id NEWLINE\nclade-line = "clade" WS id WS "=" WS "(" id ("," id)* ")"\n ( WS "[" clade-props "]" )? NEWLINE\nstyle-line = "style" WS "[" props "]" NEWLINE\n\n// Indent tree \u2014 triggered by a line ending in ":" with no spaces\nindent-tree = root-line indent-node*\nroot-line = id ":" NEWLINE\nindent-node = INDENT ( id ":" length | ":" length | id ) ( WS "[" number "]" )? NEWLINE\n\nprops = prop ("," prop)*\nprop = "layout:" layout-value\n | "mode:" mode-value\n | "unrooted"\n | "branch-width:" number\n | "openAngle:" number\n | "mrsd:" quoted-string\n\nclade-props = clade-prop ("," clade-prop)*\nclade-prop = "color:" quoted-string\n | "label:" quoted-string\n | "highlight:" ( "branch" | "background" | "both" )\n\nlayout-value = "rectangular" | "slanted" | "circular" | "unrooted"\nmode-value = "phylogram" | "cladogram" | "chronogram"\n\n// Newick grammar (embedded, parsed separately)\nnewick = subtree ";"?\nsubtree = leaf | internal\ninternal = "(" subtree ("," subtree)* ")" name? nhx? length?\nleaf = name nhx? length?\nname = unquoted-name | "\'" single-quoted "\'")\nlength = ":" number\nnhx = "[" number "]" // plain bootstrap\n | "[&&NHX:" nhx-pair (":" nhx-pair)* "]"\nnhx-pair = key "=" value\n\nid = [a-zA-Z] [a-zA-Z0-9_-]*\nnumber = /[+-]?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?/\ncomment = INDENT "#" any NEWLINE\n```\n\nAuthoritative source: `src/diagrams/phylo/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
3338
+ "content": '## 1. Your first phylogenetic tree\n\nThe smallest useful tree: four taxa, two clades.\n\n```\nphylo "Vertebrates"\n newick: "((Human:0.1,Chimp:0.08):0.03,(Dog:0.35,Cat:0.30):0.2);"\n```\n\nThree rules cover 80% of usage:\n\n1. Start with `phylo`, optionally followed by a quoted title and bracket props.\n2. Provide the tree topology in `newick:` format \u2014 the standard Newick string, quoted, on one line. The trailing `;` is optional.\n3. Optionally define **clade** highlight groups and a **scale** label below the newick line.\n\n> Comments must start with `#` on their own line. Inline trailing comments are not supported.\n\n---\n\n## 2. Input formats\n\n### 2.1 Newick format\n\nNewick is the primary input. The full grammar is:\n\n```\n(A,B,(C,D)); # topology only\n(A:0.1,B:0.2,(C:0.3,D:0.4):0.5); # with branch lengths\n((A:0.1,B:0.2):0.05[&&NHX:B=98],(C,D):0.08); # NHX bootstrap\n(\'Homo sapiens\':0.1,\'Mus musculus\':0.2); # quoted names with spaces\n```\n\nBranch lengths follow the node name after a colon. Internal node support values can appear as plain brackets `[95]` or as NHX `[&&NHX:B=95]`.\n\n```\nphylo "Newick examples"\n newick: "((A:0.1,B:0.2):0.05[&&NHX:B=98],(C:0.3,D:0.4):0.08[&&NHX:B=87]);"\n```\n\n**Newick rules the parser accepts:**\n\n| Feature | Syntax | Notes |\n|---|---|---|\n| Leaf name | `A`, `Homo_sapiens` | No spaces \u2014 use `_` or quote |\n| Quoted leaf name | `\'Homo sapiens\'` | Single quotes; `\'\'` is a literal quote inside |\n| Branch length | `:0.035` after name | Float; optional |\n| Internal node name | `(A,B)ancestor` | After closing `)` |\n| Bootstrap (plain) | `(A,B)[95]` | Integer or float in brackets |\n| Bootstrap (NHX) | `(A,B)[&&NHX:B=95]` | `B=` field; other NHX fields stored but not rendered |\n| Semicolon | `;` at end | Optional \u2014 parser strips it |\n| Polytomy | `(A,B,C)` | More than 2 children |\n\n### 2.2 Indent DSL\n\nFor hand-written or small trees, Schematex offers an indentation-based alternative that is easier to read and edit than raw Newick:\n\n```\nphylo "Vertebrates (indent DSL)" [mode: phylogram]\nroot:\n :0.03\n Human: 0.1\n Chimp: 0.08\n :0.2\n Dog: 0.35\n Cat: 0.30\nscale "substitutions/site"\n```\n\n**Indent DSL rules:**\n\n| Syntax | Meaning |\n|---|---|\n| `Name: length` | Leaf node with branch length |\n| `: length` | Unnamed internal node with branch length |\n| `Name` | Leaf node, no branch length (cladogram) |\n| `Name [N]` | Node with support value N |\n| Deeper indent | Child of the node above at a shallower indent |\n| `#` line | Comment, ignored |\n\nThe first line that ends with `:` and has no spaces triggers indent-tree mode (e.g. `root:`). The name before the colon becomes the root label; all indented lines below become its children.\n\n---\n\n## 3. Layout\n\nSet the layout in the header brackets: `phylo "Title" [layout: rectangular]`.\n\n| Layout | Value | Description |\n|---|---|---|\n| Rectangular | `rectangular` | Default. L-shaped branches; root on left, tips on right |\n| Slanted | `slanted` | Diagonal lines from parent to child; more compact |\n| Circular | `circular` | Root at center, tips around the circumference |\n| Unrooted | `unrooted` | Equal-angle radial; emphasizes distance, not ancestry |\n\n`[unrooted]` as a bare flag is equivalent to `[layout: unrooted]`.\n\n**Circular** \u2014 root at center, tips fanning outward. Most visually striking for many-taxa trees with clade highlights.\n\n```\nphylo "Vertebrates \u2014 circular" [layout: circular]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates", highlight: both]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora", highlight: both]\n```\n\n**Rectangular** \u2014 L-shaped branches; root on the left, tips on the right. The classic phylogram form for published figures.\n\n```\nphylo "Bacterial Diversity" [layout: rectangular, mode: phylogram]\n newick: "((((Ecoli:0.1,Salmonella:0.12):0.05[&&NHX:B=98],Vibrio:0.2):0.08,((Bacillus:0.15,Staph:0.18):0.06[&&NHX:B=92],Listeria:0.22):0.1):0.15,((Myco_tb:0.3,Myco_leprae:0.28):0.12[&&NHX:B=100],(Strepto:0.25,Lactobacillus:0.2):0.08):0.2);"\n clade Gamma = (Ecoli, Salmonella, Vibrio) [color: "#1E88E5", label: "\u03B3-Proteobacteria"]\n clade Firmi = (Bacillus, Staph, Listeria) [color: "#E53935", label: "Firmicutes"]\n scale "substitutions/site"\n```\n\n**Slanted** \u2014 diagonal lines from parent to child; more compact than rectangular, same left-to-right reading direction.\n\n```\nphylo "Vertebrates \u2014 slanted" [layout: slanted]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates"]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora"]\n scale "substitutions/site"\n```\n\n**Unrooted** \u2014 equal-angle radial layout; de-emphasizes the root, emphasizes pairwise distance between all taxa.\n\n```\nphylo "Vertebrates \u2014 unrooted" [unrooted]\n newick: "((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,(Dog:0.35,Cat:0.30,Wolf:0.32):0.2,(Salmon:0.5,Zebrafish:0.45):0.3);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates"]\n clade Carnivora = (Dog, Cat, Wolf) [color: "#E53935", label: "Carnivora"]\n```\n\n---\n\n## 4. Mode\n\nSet with `[mode: \u2026]` in the header (or in a `style [mode: \u2026]` line).\n\n| Mode | Value | Branch length meaning |\n|---|---|---|\n| Phylogram | `phylogram` | Default. Proportional to evolutionary distance (substitutions/site) |\n| Cladogram | `cladogram` | Ignored \u2014 tips align; only topology matters |\n| Chronogram | `chronogram` | Proportional to divergence time; all tips align to "present" |\n| Dendrogram | `dendrogram` | Branch length is **merge height** \u2014 the distance at which two clusters join |\n\nChronogram requires branch lengths in units of time plus `[mrsd: "YYYY"]` (most-recent sampling date) in the header so the renderer can align tips to present.\n\n```\nphylo "SARS-CoV-2 variants" [mode: chronogram, mrsd: "2023"]\n newick: "((Alpha:0.5,Delta:0.4):0.3,Omicron:0.8);"\n scale "years"\n```\n\n**Dendrogram** \u2014 the standard output of hierarchical agglomerative clustering, not evolution. Each internal node is placed at its **merge height** (the cophenetic distance at which its two child clusters fuse), all leaves align at a common baseline, and the branches are rectangular elbow connectors. A height axis is drawn so you can read off the distance at which any two leaves first share a cluster. Reach for this mode when the same Newick/indent tree describes a clustering result \u2014 gene-expression clusters, sample similarity, survey-response groups \u2014 rather than a phylogeny.\n\nAdd a `cut <value>` line to slice the tree at a chosen height: every subtree whose merge height falls below the threshold becomes one flat cluster, each colored distinctly, and a dashed threshold line is drawn across the tree at that height. This is the dendrogram equivalent of `fcluster` in scipy \u2014 turning a continuous tree into a discrete set of groups.\n\n```\nphylo "Gene expression clusters" [mode: dendrogram]\n newick: "(((A:1,B:1):2,C:3):2,(D:2,E:2):3);"\n cut 4\n scale "cluster distance"\n```\n\nOmit `cut` to show the bare dendrogram with no flat-cluster coloring:\n\n```\nphylo "Sample clustering" [mode: dendrogram]\n newick: "(((A:1,B:1):2,C:3):2,(D:2,E:2):3);"\n scale "cluster distance"\n```\n\n---\n\n## 5. Clade highlighting\n\nA `clade` line marks a monophyletic group with a color, an optional label, and an optional highlight mode.\n\n```\nclade ID = (member1, member2, ...) [color: "#hex", label: "text", highlight: mode]\n```\n\n| Prop | Values | Effect |\n|---|---|---|\n| `color:` | hex string e.g. `"#1E88E5"` | Branch and/or background color |\n| `label:` | quoted string | Clade label shown at right margin |\n| `highlight:` | `branch`, `background`, `both` | `branch` colors lines; `background` shades the region; `both` does both |\n\nMembers are tip (leaf) IDs from the Newick string. The renderer computes the MRCA of the listed tips and highlights the entire subtree rooted there.\n\n```\nphylo "Mammal clades" [layout: rectangular]\n newick: "(((Human:0.1,Chimp:0.08,Gorilla:0.12):0.15,Mouse:0.45):0.05,(Dog:0.35,(Cat:0.30,Tiger:0.32):0.1):0.2);"\n clade Primates = (Human, Chimp, Gorilla) [color: "#1E88E5", label: "Primates", highlight: both]\n clade Carnivora = (Dog, Cat, Tiger) [color: "#E53935", label: "Carnivora", highlight: branch]\n```\n\n---\n\n## 6. Scale bar and outgroup\n\n**Scale bar:** `scale "label"` \u2014 adds a bar at the bottom. The label describes the unit (e.g. `"substitutions/site"`, `"Mya"`). Omit for cladogram mode where branch lengths have no meaning.\n\n**Outgroup:** `outgroup: taxonId` \u2014 records the outgroup for documentation; the renderer may use it to visually mark the outgroup taxon.\n\n```\nphylo "Vertebrates"\n newick: "((Human:0.1,Chimp:0.08):0.03,Lamprey:0.8);"\n outgroup: Lamprey\n scale "substitutions/site"\n```\n\n---\n\n## 7. Header props reference\n\nAll options go inside `[\u2026]` on the `phylo` header line, or in a `style [\u2026]` line anywhere in the body.\n\n| Prop | Values | Default | Effect |\n|---|---|---|---|\n| `layout:` | `rectangular`, `slanted`, `circular`, `unrooted` | `rectangular` | Tree layout |\n| `mode:` | `phylogram`, `cladogram`, `chronogram`, `dendrogram` | `phylogram` | Branch length semantics |\n| `unrooted` | (flag) | \u2014 | Equivalent to `layout: unrooted` |\n| `branch-width:` | number | `1.5` | Stroke width of branches |\n| `openAngle:` | number (degrees) | `0` | Fan gap for circular layout (0 = full 360\xB0) |\n| `mrsd:` | quoted year string | \u2014 | Most-recent sampling date for chronograms |\n\n---\n\n## 8. Labels & comments\n\n- **Title:** `phylo "Tree of Life"` \u2014 first line only.\n- **Scale label:** `scale "substitutions/site"` \u2014 one per document.\n- **Clade label:** `[label: "Primates"]` inside a `clade` line.\n- **Comments:** `#` at the start of a line (after leading whitespace). Inline trailing comments are not supported.\n\n---\n\n## 9. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `newick: (A,B,C);` (unquoted) | `PhyloParseError: Phylo document must start with \'phylo\'` | Quote the Newick string: `newick: "(A,B,C);"` |\n| Tip name with a space: `Homo sapiens:0.1` | Parsed as `Homo` \u2014 space terminates an unquoted name | Use underscore (`Homo_sapiens`) or single-quote (`\'Homo sapiens\'`) |\n| Leaf ID in `clade` doesn\'t match Newick name | Clade silently has 0 members; no highlight | Copy names exactly as they appear in the Newick string |\n| `clade X = (A, B)` with no `newick:` or indent tree | `PhyloParseError: No tree definition found` | Add a `newick:` line or an indent tree block |\n| `mode: chronogram` with no branch lengths | Renderer treats all lengths as 0; tips overlap at root | Add `:length` to every edge in the Newick string |\n| `root:` line not detected | If the `root:` line has a space in the name (e.g. `My root:`) the indent tree is not triggered | Use a single-word root label or `root:` |\n| Newick with internal node names: `(A,B)ancestor:0.5` | Parses fine \u2014 `ancestor` is the internal node label | Supported; internal names appear on internal nodes |\n\n---\n\n## 10. Grammar (EBNF)\n\n```text\ndocument = header (blank | comment | newick-line | scale-line\n | outgroup-line | clade-line | style-line | cut-line | indent-line)*\n\nheader = "phylo" ( WS quoted-string )? ( WS "[" props "]" )? NEWLINE\nquoted-string = \'"\' any-char-but-quote* \'"\'\n\nnewick-line = "newick:" WS quoted-newick NEWLINE\nscale-line = "scale" ( WS quoted-string )? NEWLINE\noutgroup-line = "outgroup:" WS id NEWLINE\ncut-line = "cut" WS number NEWLINE // dendrogram mode: flat-cluster threshold height\nclade-line = "clade" WS id WS "=" WS "(" id ("," id)* ")"\n ( WS "[" clade-props "]" )? NEWLINE\nstyle-line = "style" WS "[" props "]" NEWLINE\n\n// Indent tree \u2014 triggered by a line ending in ":" with no spaces\nindent-tree = root-line indent-node*\nroot-line = id ":" NEWLINE\nindent-node = INDENT ( id ":" length | ":" length | id ) ( WS "[" number "]" )? NEWLINE\n\nprops = prop ("," prop)*\nprop = "layout:" layout-value\n | "mode:" mode-value\n | "unrooted"\n | "branch-width:" number\n | "openAngle:" number\n | "mrsd:" quoted-string\n\nclade-props = clade-prop ("," clade-prop)*\nclade-prop = "color:" quoted-string\n | "label:" quoted-string\n | "highlight:" ( "branch" | "background" | "both" )\n\nlayout-value = "rectangular" | "slanted" | "circular" | "unrooted"\nmode-value = "phylogram" | "cladogram" | "chronogram" | "dendrogram"\n\n// Newick grammar (embedded, parsed separately)\nnewick = subtree ";"?\nsubtree = leaf | internal\ninternal = "(" subtree ("," subtree)* ")" name? nhx? length?\nleaf = name nhx? length?\nname = unquoted-name | "\'" single-quoted "\'")\nlength = ":" number\nnhx = "[" number "]" // plain bootstrap\n | "[&&NHX:" nhx-pair (":" nhx-pair)* "]"\nnhx-pair = key "=" value\n\nid = [a-zA-Z] [a-zA-Z0-9_-]*\nnumber = /[+-]?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?/\ncomment = INDENT "#" any NEWLINE\n```\n\nAuthoritative source: `src/diagrams/phylo/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
2866
3339
  },
2867
3340
  "sociogram": {
2868
3341
  "title": "Sociogram",
@@ -2906,7 +3379,7 @@ var SYNTAX = {
2906
3379
  },
2907
3380
  "decisiontree": {
2908
3381
  "title": "Decision tree diagram",
2909
- "content": '## 1. Your first decision tree\n\nThe smallest useful decision tree: a root question with two branches.\n\n```\n\n decisiontree "Laptop troubleshoot"\n\n question "Does it power on?"\n yes: answer "Check display \u2014 connect external monitor"\n no: question "Is the charger light on?"\n yes: answer "Hold power button 10 s \u2014 try again"\n no: answer "Check outlet and charging cable"\n```\n\nFour rules cover 80% of usage:\n\n1. Start with `decisiontree`, optionally with `:mode` and a quoted title.\n2. Each question node uses `question "text"` (or shorthand `q "text"`).\n3. Each answer/leaf uses `answer "text"` (or `a "text"` or `leaf "text"`).\n4. Branch labels \u2014 `yes:`, `no:`, or a custom `label "X":` \u2014 prefix the child node on the same line.\n\nIndentation controls nesting: each level adds 2 spaces. The parser computes parent-child relationships from indent depth.\n\n> Comments must start with `#` or `//` on their own line.\n\n---\n\n## 2. Modes\n\nThe mode is set in the header line:\n\n| Header | Mode | Used for |\n|---|---|---|\n| `decisiontree` | taxonomy | Yes/no question flows, troubleshooting guides, clinical decision support |\n| `decisiontree:decision` (or `decisiontree:da`) | decision analysis | Investment decisions, risk analysis, expected value calculation |\n| `decisiontree:ml` | machine learning | Visualizing trained CART classifiers (scikit-learn, XGBoost, etc.) |\n\nDefault direction is `top-down` for taxonomy and ML, `left-right` for decision analysis.\n\n---\n\n## 3. Taxonomy mode\n\nBest for: troubleshooting guides, FAQs, clinical protocols, product recommendation flows.\n\n### Node keywords\n\n| Keyword | Aliases | Meaning |\n|---|---|---|\n| `question "\u2026"` | `q "\u2026"` | Internal node \u2014 a question with children |\n| `answer "\u2026"` | `a "\u2026"`, `leaf "\u2026"` | Leaf node \u2014 a terminal outcome |\n\n### Branch labels\n\n| Syntax | Meaning |\n|---|---|\n| `yes: question "\u2026"` | Branch labeled "yes" |\n| `no: answer "\u2026"` | Branch labeled "no" |\n| `label "Custom text": answer "\u2026"` | Branch with any custom label |\n\nCustom labels let you go beyond yes/no for multi-way decisions from one question.\n\n```\n\n decisiontree "Triage \u2014 chest pain onset"\n\n q "Onset sudden?"\n yes: q "ECG changes present?"\n yes: a "ACS protocol \u2014 cardiology consult"\n no: q "D-dimer elevated?"\n yes: a "PE workup \u2014 CT pulmonary angiography"\n no: a "Aortic dissection \u2014 CT angiography"\n no: q "Pain reproducible on palpation?"\n yes: a "Musculoskeletal \u2014 NSAIDs, follow-up PCP"\n no: a "GI / anxiety \u2014 further history"\n```\n\n```\n\n decisiontree "Pain level triage"\n\n question "Reported pain level?"\n label "Severe (8-10)": answer "Emergency \u2014 send to ER immediately"\n label "Moderate (4-7)": answer "Urgent care \u2014 within 2 hours"\n label "Mild (1-3)": answer "Schedule next available \u2014 OTC care"\n label "None": answer "Monitor \u2014 patient may be post-medication"\n```\n\n---\n\n## 4. Decision analysis mode\n\nBest for: investment decisions, build-vs-buy analysis, risk-weighted strategy evaluation.\n\n### Node keywords\n\n| Keyword | Aliases | Meaning |\n|---|---|---|\n| `decision "\u2026"` | \u2014 | Decision node \u2014 the actor chooses a branch |\n| `chance "\u2026"` | \u2014 | Chance node \u2014 an uncertain outcome |\n| `end "\u2026"` | `outcome "\u2026"` | Terminal node \u2014 final payoff |\n\n### Branch keywords\n\n| Keyword | Meaning |\n|---|---|\n| `choice "label"` | Names the incoming branch from a decision node |\n| `prob N` | Sets the probability (0\u20131) on the incoming branch from a chance node |\n\n### Payoff attribute\n\n`payoff=N` on any node sets the payoff value. On `end` / `outcome` nodes it defines the terminal value. The parser runs expected-value rollback automatically: each `chance` node\'s EV is the probability-weighted sum of its children\'s EVs; each `decision` node\'s EV is the maximum child EV, and the optimal branch is flagged.\n\n**Constraint:** probabilities on all direct children of a `chance` node must sum to 1.0 (\xB10.01). The parser throws a `DTreeParseError` if they do not.\n\n```\n\n decisiontree:decision "Cloud vendor selection"\n\n decision "Which vendor?"\n choice "Build in-house"\n chance "Project outcome"\n prob 0.6 end "On-time delivery" payoff=900000\n prob 0.4 end "Over budget / delayed" payoff=150000\n choice "Managed SaaS vendor"\n end "Predictable cost" payoff=500000\n choice "Hybrid approach"\n chance "Integration complexity"\n prob 0.5 end "Smooth integration" payoff=700000\n prob 0.5 end "Integration rework" payoff=300000\n```\n\n---\n\n## 5. Machine learning mode\n\nBest for: explaining trained CART classifiers, model transparency reports, feature importance analysis.\n\n### Node keywords\n\n| Keyword | Meaning |\n|---|---|\n| `split "\u2026"` | Internal split node \u2014 contains a feature test |\n| `leaf "\u2026"` | Leaf node \u2014 class or regression value |\n\n### Branch prefixes\n\n`true` and `false` prefix child nodes to mark which branch each child represents.\n\n### Properties (key=value, no colon, no quotes around values)\n\n| Property | Applies to | Meaning |\n|---|---|---|\n| `feature=name` | split | Feature name used at the split |\n| `op="<="` | split | Comparison operator (quote if contains special chars) |\n| `threshold=5.9` | split | Split threshold value |\n| `samples=150` | split, leaf | Sample count at this node |\n| `gini=0.5` | split, leaf | Gini impurity |\n| `entropy=0.5` | split, leaf | Entropy impurity |\n| `mse=0.3` | split, leaf | Mean squared error (regression) |\n| `gain=0.2` | split, leaf | Information gain |\n| `class=name` | leaf | Predicted class name |\n| `value=50` | leaf | Sample count; use `value=[50,30,20]` for class distribution |\n\n```\n\n decisiontree:ml "Iris classification (CART)"\n direction: top-down\n impurity: gini\n\n split "Petal length \u2264 2.45" feature=petal_length op="<=" threshold=2.45 samples=150 gini=0.667\n true leaf "Setosa" class=Iris-setosa value=50 gini=0.0\n false split "Petal width \u2264 1.75" feature=petal_width op="<=" threshold=1.75 samples=100 gini=0.5\n true leaf "Versicolor" class=Iris-versicolor value=50 gini=0.0\n false leaf "Virginica" class=Iris-virginica value=50 gini=0.0\n```\n\n---\n\n## 6. Config options\n\nConfig lines appear between the header and the first node. Each is `key: value` (colon, no `config` keyword).\n\n### Shared config (all modes)\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `direction:` | `top-down`, `left-right` | `top-down` (taxonomy/ML), `left-right` (decision) | Layout direction |\n| `edgeStyle:` (or `edge-style:`) | `diagonal`, `orthogonal`, `bracket` | mode-dependent | Edge drawing style |\n\n### Taxonomy config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `branchLabels:` (or `branch-labels:`) | `boolean`, `relation` | `boolean` | Branch label style |\n\n### Decision analysis config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `branchLength:` (or `branch-length:`) | `probability` | off | Scale branch length proportional to probability |\n\n### ML config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `impurity:` | `gini`, `entropy`, `mse`, `gain` | `gini` | Impurity metric shown on nodes |\n| `classes:` | comma-separated list | \u2014 | Class label names for display |\n\n```\ndecisiontree:ml "Loan classifier"\ndirection: top-down\nimpurity: gini\nclasses: Approved, Denied, Review\n```\n\n---\n\n## 7. Labels & comments\n\n- **Diagram title:** `decisiontree "Title"` \u2014 the quoted string after the header keyword.\n- **Node label:** the quoted string immediately after the node keyword \u2014 `question "Is the fee waived?"`.\n- **Branch label:** `yes:`, `no:`, or `label "Custom":` before the child node \u2014 on the same line as the child.\n- **Payoff:** `payoff=250000` at the end of a decision/end node line.\n- **ML properties:** `key=value` tokens after the node\'s label string (no `[\u2026]` brackets, no colons).\n- **Comments:** `#` or `//` at the start of a line (after optional leading whitespace). Only full-line comments are supported \u2014 inline trailing comments are not.\n\n---\n\n## 8. Reserved words & escaping\n\n**Reserved node keywords:** `decision`, `chance`, `end`, `outcome`, `choice`, `prob`, `split`, `leaf`, `question`, `q`, `answer`, `a`.\n\n**Reserved branch prefixes:** `yes:`, `no:`, `true`, `false`, `label`.\n\n**Reserved header forms:** `decisiontree`, `decisiontree:decision`, `decisiontree:da`, `decisiontree:ml`.\n\n**Strings with spaces** must be double-quoted: `question "Annual revenue > $1M?"`. Node labels, branch labels from `label "\u2026":` syntax, and the diagram title all require double quotes.\n\n| Reserved token | Context | Notes |\n|---|---|---|\n| `yes:` / `no:` | Line start in taxonomy | Cannot be used as a label \u2014 use `label "yes":` if you need literal text "yes" |\n| `true` / `false` | Line start in ML mode | Cannot be a node label |\n| `choice` | Line start in decision mode | Acts as branch wrapper, not a node |\n| `prob` | Line start in decision mode | Must be followed by a number |\n\n---\n\n## 9. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `yes: "Approve"` (no node keyword) | `DTreeParseError: Missing taxonomy node kind` | `yes: answer "Approve"` |\n| Probabilities on `chance` children summing to 0.8 | `DTreeParseError: probabilities do not sum to 1.0` | Adjust so all `prob` values sum to exactly 1.0 (\xB10.01) |\n| `question "text"` with a child at the same indent level | Child not parsed as a child \u2014 becomes a sibling | Indent children by 2 more spaces than the parent |\n| `config direction = top-down` (using `config` keyword) | `config` is a fishbone keyword \u2014 not recognized here | Use `direction: top-down` (no `config` prefix) |\n| `feature = petal_length` (spaces around `=`) | Parsed as separate tokens; property not recognized | No spaces: `feature=petal_length` |\n| `[payoff: 500000]` bracket syntax | Not recognized \u2014 parser ignores brackets for payoff | Use `payoff=500000` (no brackets, no spaces around `=`) |\n| `decisiontree:taxonomy` | `DTreeParseError: Invalid header` | Use `decisiontree` (no mode suffix for taxonomy) |\n\n---\n\n## 10. Grammar (EBNF)\n\n```text\ndocument = header ( config-line )* node\n\nheader = "decisiontree" ( ":" mode )? ( WS quoted-string )? NEWLINE\nmode = "decision" | "da" | "ml"\n // omitted \u2192 taxonomy\n\nconfig-line = config-key ":" WS config-value NEWLINE\nconfig-key = "direction" | "edgeStyle" | "edge-style"\n | "branchLabels" | "branch-labels"\n | "branchLength" | "branch-length"\n | "impurity" | "classes"\n\n// \u2500\u2500 Taxonomy mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nnode = ( branch-prefix WS )? tax-node ( WS "[" tax-attrs "]" )? NEWLINE\n INDENT child-node*\ntax-node = ( "question" | "q" ) WS quoted-string\n | ( "answer" | "a" | "leaf" ) WS quoted-string\nbranch-prefix = "yes:" | "no:" | "label" WS quoted-string ":"\n\n// \u2500\u2500 Decision-analysis mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nda-node = "decision" WS quoted-string NEWLINE INDENT da-child+\n | "chance" WS quoted-string NEWLINE INDENT da-prob-child+\n | ( "end" | "outcome" ) WS quoted-string ( WS "payoff=" number )? NEWLINE\nda-child = "choice" WS quoted-string NEWLINE INDENT da-node\nda-prob-child = "prob" WS number WS da-node // prob, value, and child all on one line\n\n// \u2500\u2500 ML mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nml-node = ( "true" | "false" )? ml-kind WS quoted-string ml-prop* NEWLINE\n INDENT ml-child*\n // "true"/"false" and ml-kind must be on the same line\nml-kind = "split" | "leaf"\nml-prop = WS key "=" value // no spaces around "="\n\ncomment = ( "#" | "//" ) any NEWLINE\nquoted-string = \'"\' any-char-but-quote* \'"\'\n```\n\nAuthoritative source: `src/diagrams/decisiontree/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
3382
+ "content": '## 1. Your first decision tree\n\nThe smallest useful decision tree: a root question with two branches.\n\n```\n\n decisiontree "Laptop troubleshoot"\n\n question "Does it power on?"\n yes: answer "Check display \u2014 connect external monitor"\n no: question "Is the charger light on?"\n yes: answer "Hold power button 10 s \u2014 try again"\n no: answer "Check outlet and charging cable"\n```\n\nFour rules cover 80% of usage:\n\n1. Start with `decisiontree`, optionally with `:mode` and a quoted title.\n2. Each question node uses `question "text"` (or shorthand `q "text"`).\n3. Each answer/leaf uses `answer "text"` (or `a "text"` or `leaf "text"`).\n4. Branch labels \u2014 `yes:`, `no:`, or a custom `label "X":` \u2014 prefix the child node on the same line.\n\nIndentation controls nesting: each level adds 2 spaces. The parser computes parent-child relationships from indent depth.\n\n> Comments must start with `#` or `//` on their own line.\n\n---\n\n## 2. Modes\n\nThe mode is set in the header line:\n\n| Header | Mode | Used for |\n|---|---|---|\n| `decisiontree` | taxonomy | Yes/no question flows, troubleshooting guides, clinical decision support |\n| `decisiontree:decision` (or `decisiontree:da`) | decision analysis | Investment decisions, risk analysis, expected value calculation |\n| `decisiontree:influence` (or `mode: influence`) | influence diagram | Compact DAG view of a decision problem \u2014 structure before unrolling to a tree |\n| `decisiontree:ml` | machine learning | Visualizing trained CART classifiers (scikit-learn, XGBoost, etc.) |\n\nDefault direction is `top-down` for taxonomy and ML, `left-right` for decision analysis.\n\n---\n\n## 3. Taxonomy mode\n\nBest for: troubleshooting guides, FAQs, clinical protocols, product recommendation flows.\n\n### Node keywords\n\n| Keyword | Aliases | Meaning |\n|---|---|---|\n| `question "\u2026"` | `q "\u2026"` | Internal node \u2014 a question with children |\n| `answer "\u2026"` | `a "\u2026"`, `leaf "\u2026"` | Leaf node \u2014 a terminal outcome |\n\n### Branch labels\n\n| Syntax | Meaning |\n|---|---|\n| `yes: question "\u2026"` | Branch labeled "yes" |\n| `no: answer "\u2026"` | Branch labeled "no" |\n| `label "Custom text": answer "\u2026"` | Branch with any custom label |\n\nCustom labels let you go beyond yes/no for multi-way decisions from one question.\n\n```\n\n decisiontree "Triage \u2014 chest pain onset"\n\n q "Onset sudden?"\n yes: q "ECG changes present?"\n yes: a "ACS protocol \u2014 cardiology consult"\n no: q "D-dimer elevated?"\n yes: a "PE workup \u2014 CT pulmonary angiography"\n no: a "Aortic dissection \u2014 CT angiography"\n no: q "Pain reproducible on palpation?"\n yes: a "Musculoskeletal \u2014 NSAIDs, follow-up PCP"\n no: a "GI / anxiety \u2014 further history"\n```\n\n```\n\n decisiontree "Pain level triage"\n\n question "Reported pain level?"\n label "Severe (8-10)": answer "Emergency \u2014 send to ER immediately"\n label "Moderate (4-7)": answer "Urgent care \u2014 within 2 hours"\n label "Mild (1-3)": answer "Schedule next available \u2014 OTC care"\n label "None": answer "Monitor \u2014 patient may be post-medication"\n```\n\n---\n\n## 4. Decision analysis mode\n\nBest for: investment decisions, build-vs-buy analysis, risk-weighted strategy evaluation.\n\n### Node keywords\n\n| Keyword | Aliases | Meaning |\n|---|---|---|\n| `decision "\u2026"` | \u2014 | Decision node \u2014 the actor chooses a branch |\n| `chance "\u2026"` | \u2014 | Chance node \u2014 an uncertain outcome |\n| `end "\u2026"` | `outcome "\u2026"` | Terminal node \u2014 final payoff |\n\n### Branch keywords\n\n| Keyword | Meaning |\n|---|---|\n| `choice "label"` | Names the incoming branch from a decision node |\n| `prob N` | Sets the probability (0\u20131) on the incoming branch from a chance node |\n\n### Payoff attribute\n\n`payoff=N` on any node sets the payoff value. On `end` / `outcome` nodes it defines the terminal value. The parser runs expected-value rollback automatically: each `chance` node\'s EV is the probability-weighted sum of its children\'s EVs; each `decision` node\'s EV is the maximum child EV, and the optimal branch is flagged.\n\n**Constraint:** probabilities on all direct children of a `chance` node must sum to 1.0 (\xB10.01). The parser throws a `DTreeParseError` if they do not.\n\n```\n\n decisiontree:decision "Cloud vendor selection"\n\n decision "Which vendor?"\n choice "Build in-house"\n chance "Project outcome"\n prob 0.6 end "On-time delivery" payoff=900000\n prob 0.4 end "Over budget / delayed" payoff=150000\n choice "Managed SaaS vendor"\n end "Predictable cost" payoff=500000\n choice "Hybrid approach"\n chance "Integration complexity"\n prob 0.5 end "Smooth integration" payoff=700000\n prob 0.5 end "Integration rework" payoff=300000\n```\n\n---\n\n## 5. Influence diagram mode\n\nBest for: framing a decision problem **compactly** before you unroll it. Where decision-analysis mode draws every branch of every outcome as an explicit tree, an **influence diagram** ([Howard & Matheson, 1981](https://en.wikipedia.org/wiki/Influence_diagram)) draws the *same* problem as a directed acyclic graph (DAG) of variables and the dependencies between them \u2014 one node per decision, uncertainty, and objective, no matter how many states each can take. It is the diagram decision analysts reach for first, because it shows the structure (what informs what, what affects the payoff) without the combinatorial blow-up of a tree.\n\nThis mode is **structural, not computational.** Unlike decision-analysis mode, it does not solve for expected value \u2014 the compact graph deliberately omits the probability and payoff tables that an EV rollback would need. Use it to communicate and validate the shape of the problem; use decision-analysis mode (section 4) when you want the numbers folded back.\n\n### Header forms\n\nTwo equivalent ways to select the mode:\n\n```\ndecisiontree:influence "Oil Wildcatter"\n```\n\nor, as a directive on its own line after the header:\n\n```\ndecisiontree "Market Entry"\n mode: influence\n```\n\n### Node keywords\n\nEach node is declared as `kind Id "label"` \u2014 an id (used to wire arcs) followed by a quoted display label.\n\n| Keyword | Shape | Meaning |\n|---|---|---|\n| `decision Id "\u2026"` | **rectangle** | A choice the decision-maker controls |\n| `chance Id "\u2026"` | **oval** | An uncertain variable (a state of the world) |\n| `value Id "\u2026"` | **octagon** | The objective / payoff being optimized |\n\nThe shapes follow the standard influence-diagram convention: decisions are rectangles, uncertainties are ovals, and the value node is an octagon. Add `utility=N` to a value node to annotate the payoff it represents (`value Profit "Net profit" utility=42`).\n\n### Arcs and their semantics\n\nArcs are written `Source -> Target` on their own lines, by node id. **An arc\'s meaning is read from its destination**, exactly as in the published standard:\n\n| Arc into a\u2026 | Meaning | Drawn as |\n|---|---|---|\n| `decision` | **Informational** \u2014 this is known *before* the decision is made | dashed line |\n| `chance` | **Relevance / conditioning** \u2014 the source conditions this uncertainty | solid line |\n| `value` | **Functional** \u2014 the source is an argument of the payoff function | solid line |\n\nThe dashed informational arc is the one to watch: `Seismic -> Drill` means "the seismic test result is observed before choosing whether to drill," which is precisely what makes the decision worth modelling.\n\n### Validation rules\n\n- The graph must be **acyclic** \u2014 a cycle (e.g. `A -> B` and `B -> A`) is rejected.\n- At least **one `value` node** is required; an influence diagram with no objective is not a decision problem.\n- Arcs reference node ids that must be declared.\n\n### Examples\n\nThe Oil Wildcatter \u2014 the canonical teaching problem. The seismic test result is observed before the drill decision (dashed informational arc `Seismic -> Drill`), the test is relevant to whether oil is actually present (`Seismic -> Oil`), and both the oil state and the drill choice feed the profit (`Oil -> Profit`, `Drill -> Profit`).\n\n```\n\n decisiontree:influence "Oil Wildcatter"\n decision Drill "Drill?"\n chance Oil "Oil present"\n chance Seismic "Seismic test"\n value Profit "Net profit" utility=42\n Seismic -> Oil\n Seismic -> Drill\n Oil -> Profit\n Drill -> Profit\n```\n\nA market-entry decision using the `mode: influence` directive form. Demand is observed before entering (`Demand -> Enter`, informational/dashed) and also drives profit directly, while the competitor\'s response feeds only the payoff.\n\n```\n\n decisiontree "Market Entry"\n mode: influence\n decision Enter "Enter market?"\n chance Demand "Market demand"\n chance Competition "Competitor response"\n value V "Profit" utility=120\n Demand -> Enter\n Demand -> V\n Competition -> V\n Enter -> V\n```\n\n---\n\n## 6. Machine learning mode\n\nBest for: explaining trained CART classifiers, model transparency reports, feature importance analysis.\n\n### Node keywords\n\n| Keyword | Meaning |\n|---|---|\n| `split "\u2026"` | Internal split node \u2014 contains a feature test |\n| `leaf "\u2026"` | Leaf node \u2014 class or regression value |\n\n### Branch prefixes\n\n`true` and `false` prefix child nodes to mark which branch each child represents.\n\n### Properties (key=value, no colon, no quotes around values)\n\n| Property | Applies to | Meaning |\n|---|---|---|\n| `feature=name` | split | Feature name used at the split |\n| `op="<="` | split | Comparison operator (quote if contains special chars) |\n| `threshold=5.9` | split | Split threshold value |\n| `samples=150` | split, leaf | Sample count at this node |\n| `gini=0.5` | split, leaf | Gini impurity |\n| `entropy=0.5` | split, leaf | Entropy impurity |\n| `mse=0.3` | split, leaf | Mean squared error (regression) |\n| `gain=0.2` | split, leaf | Information gain |\n| `class=name` | leaf | Predicted class name |\n| `value=50` | leaf | Sample count; use `value=[50,30,20]` for class distribution |\n\n```\n\n decisiontree:ml "Iris classification (CART)"\n direction: top-down\n impurity: gini\n\n split "Petal length \u2264 2.45" feature=petal_length op="<=" threshold=2.45 samples=150 gini=0.667\n true leaf "Setosa" class=Iris-setosa value=50 gini=0.0\n false split "Petal width \u2264 1.75" feature=petal_width op="<=" threshold=1.75 samples=100 gini=0.5\n true leaf "Versicolor" class=Iris-versicolor value=50 gini=0.0\n false leaf "Virginica" class=Iris-virginica value=50 gini=0.0\n```\n\n---\n\n## 7. Config options\n\nConfig lines appear between the header and the first node. Each is `key: value` (colon, no `config` keyword).\n\n### Shared config (all modes)\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `direction:` | `top-down`, `left-right` | `top-down` (taxonomy/ML), `left-right` (decision) | Layout direction |\n| `edgeStyle:` (or `edge-style:`) | `diagonal`, `orthogonal`, `bracket` | mode-dependent | Edge drawing style |\n\n### Taxonomy config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `branchLabels:` (or `branch-labels:`) | `boolean`, `relation` | `boolean` | Branch label style |\n\n### Decision analysis config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `branchLength:` (or `branch-length:`) | `probability` | off | Scale branch length proportional to probability |\n\n### ML config\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `impurity:` | `gini`, `entropy`, `mse`, `gain` | `gini` | Impurity metric shown on nodes |\n| `classes:` | comma-separated list | \u2014 | Class label names for display |\n\n```\ndecisiontree:ml "Loan classifier"\ndirection: top-down\nimpurity: gini\nclasses: Approved, Denied, Review\n```\n\n---\n\n## 8. Labels & comments\n\n- **Diagram title:** `decisiontree "Title"` \u2014 the quoted string after the header keyword.\n- **Node label:** the quoted string immediately after the node keyword \u2014 `question "Is the fee waived?"`.\n- **Branch label:** `yes:`, `no:`, or `label "Custom":` before the child node \u2014 on the same line as the child.\n- **Payoff:** `payoff=250000` at the end of a decision/end node line.\n- **ML properties:** `key=value` tokens after the node\'s label string (no `[\u2026]` brackets, no colons).\n- **Comments:** `#` or `//` at the start of a line (after optional leading whitespace). Only full-line comments are supported \u2014 inline trailing comments are not.\n\n---\n\n## 9. Reserved words & escaping\n\n**Reserved node keywords:** `decision`, `chance`, `end`, `outcome`, `choice`, `prob`, `split`, `leaf`, `question`, `q`, `answer`, `a`.\n\n**Reserved branch prefixes:** `yes:`, `no:`, `true`, `false`, `label`.\n\n**Reserved header forms:** `decisiontree`, `decisiontree:decision`, `decisiontree:da`, `decisiontree:ml`.\n\n**Strings with spaces** must be double-quoted: `question "Annual revenue > $1M?"`. Node labels, branch labels from `label "\u2026":` syntax, and the diagram title all require double quotes.\n\n| Reserved token | Context | Notes |\n|---|---|---|\n| `yes:` / `no:` | Line start in taxonomy | Cannot be used as a label \u2014 use `label "yes":` if you need literal text "yes" |\n| `true` / `false` | Line start in ML mode | Cannot be a node label |\n| `choice` | Line start in decision mode | Acts as branch wrapper, not a node |\n| `prob` | Line start in decision mode | Must be followed by a number |\n\n---\n\n## 10. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `yes: "Approve"` (no node keyword) | `DTreeParseError: Missing taxonomy node kind` | `yes: answer "Approve"` |\n| Probabilities on `chance` children summing to 0.8 | `DTreeParseError: probabilities do not sum to 1.0` | Adjust so all `prob` values sum to exactly 1.0 (\xB10.01) |\n| `question "text"` with a child at the same indent level | Child not parsed as a child \u2014 becomes a sibling | Indent children by 2 more spaces than the parent |\n| `config direction = top-down` (using `config` keyword) | `config` is a fishbone keyword \u2014 not recognized here | Use `direction: top-down` (no `config` prefix) |\n| `feature = petal_length` (spaces around `=`) | Parsed as separate tokens; property not recognized | No spaces: `feature=petal_length` |\n| `[payoff: 500000]` bracket syntax | Not recognized \u2014 parser ignores brackets for payoff | Use `payoff=500000` (no brackets, no spaces around `=`) |\n| `decisiontree:taxonomy` | `DTreeParseError: Invalid header` | Use `decisiontree` (no mode suffix for taxonomy) |\n\n---\n\n## 11. Grammar (EBNF)\n\n```text\ndocument = header ( config-line )* node\n\nheader = "decisiontree" ( ":" mode )? ( WS quoted-string )? NEWLINE\nmode = "decision" | "da" | "ml"\n // omitted \u2192 taxonomy\n\nconfig-line = config-key ":" WS config-value NEWLINE\nconfig-key = "direction" | "edgeStyle" | "edge-style"\n | "branchLabels" | "branch-labels"\n | "branchLength" | "branch-length"\n | "impurity" | "classes"\n\n// \u2500\u2500 Taxonomy mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nnode = ( branch-prefix WS )? tax-node ( WS "[" tax-attrs "]" )? NEWLINE\n INDENT child-node*\ntax-node = ( "question" | "q" ) WS quoted-string\n | ( "answer" | "a" | "leaf" ) WS quoted-string\nbranch-prefix = "yes:" | "no:" | "label" WS quoted-string ":"\n\n// \u2500\u2500 Decision-analysis mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nda-node = "decision" WS quoted-string NEWLINE INDENT da-child+\n | "chance" WS quoted-string NEWLINE INDENT da-prob-child+\n | ( "end" | "outcome" ) WS quoted-string ( WS "payoff=" number )? NEWLINE\nda-child = "choice" WS quoted-string NEWLINE INDENT da-node\nda-prob-child = "prob" WS number WS da-node // prob, value, and child all on one line\n\n// \u2500\u2500 ML mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nml-node = ( "true" | "false" )? ml-kind WS quoted-string ml-prop* NEWLINE\n INDENT ml-child*\n // "true"/"false" and ml-kind must be on the same line\nml-kind = "split" | "leaf"\nml-prop = WS key "=" value // no spaces around "="\n\ncomment = ( "#" | "//" ) any NEWLINE\nquoted-string = \'"\' any-char-but-quote* \'"\'\n```\n\nAuthoritative source: `src/diagrams/decisiontree/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
2910
3383
  },
2911
3384
  "flowchart": {
2912
3385
  "title": "Flowchart",
@@ -2914,7 +3387,7 @@ var SYNTAX = {
2914
3387
  },
2915
3388
  "matrix": {
2916
3389
  "title": "Matrix / Quadrant diagram",
2917
- "content": '## 1. Your first matrix\n\nThe smallest useful matrix: a custom 2\xD72 with two labeled axes and three points.\n\n```\nmatrix "Feature Prioritization"\nx-axis: Low Effort \u2192 High Effort\ny-axis: Low Value \u2192 High Value\n\n"Add search" at (0.3, 0.8)\n"Rebuild pipeline" at (0.85, 0.7)\n"Update footer" at (0.2, 0.2)\n```\n\nFour rules cover 80% of usage:\n\n1. Start with the keyword `matrix`, optionally followed by a template name or a quoted title.\n2. Set the axes with `x-axis:` and `y-axis:` \u2014 or use a built-in template and skip this step entirely.\n3. Each point is `"Label" at (x, y)` where `x` and `y` are decimal fractions from 0.0 (low/left/bottom) to 1.0 (high/right/top).\n4. Add optional properties \u2014 `size:`, `category:`, `color:`, `shape:`, `highlight:` \u2014 after the coordinates.\n\n> Comments must start with `#` anywhere on a line (outside quoted strings).\n\n---\n\n## 2. Built-in templates\n\nA template pre-configures axes, quadrant labels, and grid size. Just use the template name as the second token on the header line.\n\n| Template | Grid | Use case |\n|---|---|---|\n| `eisenhower` | 2\xD72 | Urgency / Importance task prioritization |\n| `impact-effort` | 2\xD72 | Feature prioritization by impact vs. effort |\n| `rice` | 2\xD72 | RICE scoring \u2014 Reach \xD7 Impact vs. Effort |\n| `bcg` | 2\xD72 | Portfolio \u2014 Market Share vs. Growth rate |\n| `ansoff` | 2\xD72 | Product/market growth strategy |\n| `johari` | 2\xD72 | Self-awareness \u2014 known-to-self vs. known-to-others |\n| `9-box` | 3\xD73 | HR talent review \u2014 Performance vs. Potential |\n| `risk-matrix` | 5\xD75 | Risk assessment \u2014 Likelihood vs. Severity (heatmap) |\n\n```\nmatrix eisenhower "This Week"\n"Ship hotfix" at (0.1, 0.9) size: 5 highlight: true\n"Team 1:1s" at (0.1, 0.7) size: 3\n"Write Q3 OKRs" at (0.8, 0.85) size: 4\n"Inbox zero" at (0.1, 0.3) size: 2\n"Refactor auth" at (0.75, 0.4) size: 3\n```\n\nAxes and quadrant labels from a template can be overridden with explicit `x-axis:` / `y-axis:` / `quadrant` directives.\n\n---\n\n## 3. Axes\n\nAxis lines declare the semantic poles of each dimension.\n\n```\nx-axis: Low Effort \u2192 High Effort\ny-axis: Low Value \u2192 High Value\n```\n\nThe arrow separates the low label (left / bottom) from the high label (right / top). All of these separators are equivalent:\n\n| Separator | Example |\n|---|---|\n| `\u2192` (Unicode) | `x-axis: Rare \u2192 Certain` |\n| `->` (ASCII) | `x-axis: Rare -> Certain` |\n| `\u2191` | `y-axis: Cheap \u2191 Expensive` |\n| `\u2190` / `<-` / `<` | Reversed axis \u2014 high label is on the left |\n\nA **reversed axis** is for conventions where the "high" value sits at the left or bottom:\n\n```\nx-axis: High Market Share \u2190 Low Market Share\n```\n\n```\nmatrix "Product Portfolio"\nx-axis: High Market Share \u2190 Low Market Share\ny-axis: Low Growth \u2192 High Growth\n\nquadrant Q1 "Question Marks"\nquadrant Q2 "Stars"\nquadrant Q3 "Cash Cows"\nquadrant Q4 "Dogs"\n\n"Analytics Suite" at (0.25, 0.35) size: 5\n"ChatBot Pro" at (0.2, 0.8) size: 4 highlight: true\n"Legacy CRM" at (0.75, 0.25) size: 6\n"Mobile App" at (0.65, 0.75) size: 3\n```\n\n---\n\n## 4. Points\n\nEach point is a bubble positioned by a normalized (x, y) coordinate pair.\n\n```\n"Label" at (x, y)\n"Label" at (x, y) size: 4 category: design color: #7B1FA2 highlight: true note: "clarify spec"\n```\n\n| Property | Values | Meaning |\n|---|---|---|\n| `size:` | positive number | Bubble area weight (default: 3) |\n| `category:` | bareword | Color group; drives the legend |\n| `color:` | hex string | Override bubble color for this point |\n| `shape:` | `circle` \\| `square` \\| `triangle` \\| `diamond` | Bubble shape (default: `circle`) |\n| `highlight:` | `true` | Draws an emphasis ring around the bubble |\n| `note:` | quoted string | Tooltip annotation |\n| `label:` | quoted string | Replaces the display label (different from the ID) |\n\nCoordinates outside `[0, 1]` are clamped to the chart boundary and flagged with a badge \u2014 the original value is stored for tooltip display.\n\n```\nmatrix "Risk Register"\nx-axis: Low Impact \u2192 High Impact\ny-axis: Rare \u2192 Certain\n\n"Vendor delay" at (0.45, 0.7) size: 4 category: schedule highlight: true\n"Security breach" at (0.9, 0.3) size: 5 category: security shape: diamond\n"Budget overrun" at (0.5, 0.65) size: 3 category: finance\n"Key hire falls through" at (0.6, 0.55) size: 3 category: people\n"Scope creep" at (0.4, 0.8) size: 4 category: schedule\n```\n\n---\n\n## 5. Quadrant labels\n\nLabel each quadrant with a name and an optional subtitle.\n\n```\nquadrant Q1 "Do First"\nquadrant Q2 "Schedule"\nquadrant Q3 "Delete"\nquadrant Q4 "Delegate"\n\n# With an optional subtitle:\nquadrant Q1 "Do First" description: "High urgency, high importance"\n```\n\nQuadrant numbering follows the standard mathematical convention: **Q1 = top-right, Q2 = top-left, Q3 = bottom-left, Q4 = bottom-right**. The `Q` prefix is optional \u2014 `quadrant 1 "Label"` is equally valid.\n\n---\n\n## 6. Heatmap mode\n\nHeatmap mode fills N\xD7M cells with color intensity instead of plotting bubble positions.\n\n```\nmatrix heatmap 4x3 "Skill Matrix"\nrows: [Strategy, Execution, Communication, Technical]\ncols: [Junior, Mid, Senior]\n\ncell (0,0) level: weak\ncell (1,0) level: medium\ncell (2,0) level: strong\ncell (0,1) value: 7\ncell (1,2) label: "Top 10%"\n```\n\n- `matrix heatmap COLxROW` \u2014 header sets the grid dimensions.\n- `rows:` and `cols:` \u2014 comma-separated or bracket-list of axis labels.\n- `cell (col, row)` \u2014 zero-indexed, column first, row second (row 0 = bottom).\n- `level:` \u2014 `strong` (3), `medium` (2), or `weak` (1) \u2014 shorthand for heat intensity.\n- `value:` \u2014 explicit numeric value (overrides `level:`).\n- `label:` \u2014 quoted text placed inside the cell.\n\n```\nmatrix heatmap 4x4 "Competency Heat Map"\nrows: [Leadership, Execution, Communication, Technical]\ncols: [Junior, Mid, Senior, Staff]\n\ncell (0,0) level: weak\ncell (1,0) level: medium\ncell (2,0) level: strong\ncell (3,0) level: strong\ncell (0,1) level: medium\ncell (1,1) level: medium\ncell (2,1) level: strong\ncell (3,1) level: strong\ncell (0,2) level: weak\ncell (1,2) level: medium\ncell (2,2) level: medium\ncell (3,2) level: strong\ncell (0,3) level: weak\ncell (1,3) level: weak\ncell (2,3) level: medium\ncell (3,3) level: strong\n```\n\n---\n\n## 7. Correlation mode\n\nCorrelation mode renders an N\xD7M dot matrix where intensity represents the relationship strength between row and column variables.\n\n```\nmatrix correlation 4x4 "Product Metrics"\nrows: [DAU, Retention, Revenue, NPS]\ncols: [DAU, Retention, Revenue, NPS]\n\ncell (0,0) value: 1\ncell (1,0) value: 0.82\ncell (2,0) value: 0.54\ncell (3,0) value: 0.71\n```\n\nThe same `cell` syntax applies. `level: strong | medium | weak` is also accepted in correlation mode.\n\n---\n\n## 8. Config options\n\nA `config:` block tunes visual rendering. Each option goes on its own indented line below the `config:` header.\n\n```\nconfig:\n quadrantBg: true\n gridLines: true\n axisArrows: true\n bubbleScale: area\n legendPosition: bottom-right\n```\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `quadrantBg` | `true` \\| `false` | `true` | Colored quadrant background fills |\n| `gridLines` | `true` \\| `false` | `true` | Grid lines overlay |\n| `axisArrows` | `true` \\| `false` | `true` | Arrows at axis ends |\n| `bubbleScale` | `area` \\| `radius` | `area` | Whether `size:` scales bubble area or radius |\n| `quadrantAnnotations` | `true` \\| `false` | `true` | Show quadrant label text in corners |\n| `legendPosition` | `bottom-right` \\| `right` \\| `bottom-center` \\| `none` | `bottom-right` | Category legend placement |\n| `labelCollision` | `auto` \\| `offset-only` \\| `leader-only` \\| `off` | `auto` | Overlap avoidance strategy for point labels |\n| `offChartPolicy` | `clamp-badge` \\| `drop` | `clamp-badge` | What to do with points outside [0,1] |\n\nTwo shorthand directives also work at the top level (not inside the `config:` block):\n\n```\naxis: off # off | on | auto \u2014 show or hide the axis lines\nmargins: true # true | false \u2014 show Score + Rank margins (correlation mode)\n```\n\n---\n\n## 9. Labels & comments\n\n- **Title:** `matrix "My Title"` or `title: My Title` as a standalone line.\n- **Point label:** the quoted string before `at (\u2026)`.\n- **Axis labels:** `x-axis:` and `y-axis:` directives.\n- **Quadrant labels:** `quadrant Q1 "Name"` directive.\n- **Comments:** `#` anywhere on a line, outside quoted strings.\n\n```\nmatrix "Prioritization"\n# This is a comment\nx-axis: Low Cost \u2192 High Cost # inline comment after a directive\n"Fix bug" at (0.1, 0.9) size: 3 # comment after a point\n```\n\n---\n\n## 10. Table mode (`style: table`)\n\nThe default matrix rendering is a **scatter / bubble chart** \u2014 points float at (x, y) coordinates. For frameworks where the output is a list of items grouped by quadrant (Eisenhower, Johari, Impact-Effort, 9-box), use `style: table` to switch to a **text-in-cell layout** instead.\n\n```\nmatrix eisenhower "This Week"\nstyle: table\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\nQ1: "Write Q3 OKRs"\nQ1: "Refactor auth layer"\nQ4: "LinkedIn updates"\nQ3: "Reorganize Slack channels"\n```\n\n`style: table` applies these changes automatically:\n\n| Effect | Detail |\n|---|---|\n| Axes and arrows hidden | No axis lines, labels, or arrowheads |\n| Grid lines hidden | Only the outer border and cell dividers remain |\n| Quadrant titles move inside cells | Each title becomes a cell header instead of a corner overlay |\n| Items stack as a bullet list | Multiple entries for the same quadrant stack top-down |\n\n### `Q1` \u2026 `Q4` shorthand (2\xD72 only)\n\nFor 2\xD72 templates, use `Qn: "item"` instead of the longer `cell (col, row) label: "item"` form. Mapping:\n\n| Shorthand | Cell | Eisenhower | Johari |\n|---|---|---|---|\n| `Q1:` | top-right | Schedule | Blind |\n| `Q2:` | top-left | Do First | Open / Arena |\n| `Q3:` | bottom-left | Delete | Hidden / Fa\xE7ade |\n| `Q4:` | bottom-right | Delegate | Unknown |\n\nRepeat a shorthand key to add multiple items to the same cell:\n\n```\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\n```\n\nFor 3\xD73 grids (9-box), use `cell (col, row) label: "\u2026"` directly \u2014 the `Q` shorthand is 2\xD72 only.\n\n### When to use table vs scatter\n\n| Use `style: table` for | Use scatter (default) for |\n|---|---|\n| Eisenhower with task lists | Eisenhower with `size:` effort weights |\n| Johari window coaching | Impact-Effort with bubble = revenue |\n| Backlog grouping (no numeric third dimension) | RICE / BCG portfolio (third dimension IS the bubble size) |\n| 9-box talent review | Risk heatmap (5\xD75 with numeric severity) |\n\n```\nmatrix eisenhower "This Week"\nstyle: table\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\nQ1: "Write Q3 OKRs"\nQ1: "Refactor auth layer"\nQ4: "LinkedIn updates"\nQ3: "Reorganize Slack channels"\n```\n\n---\n\n## 11. Reserved words & escaping\n\n**Reserved at line start:** `matrix` (header), `x-axis:`, `y-axis:`, `quadrant`, `config:`, `title:`, `rows:`, `cols:`, `grid:`, `axis:`, `margins:`, `cell`.\n\n**Point lines must start with a quote character** (`"` or `\'`). A line that does not start with a quote is not treated as a point.\n\n**Strings with spaces** in axis labels do not need quoting \u2014 the text after the colon (and after the arrow) is taken verbatim. In `note:` and `label:` point properties, use double quotes.\n\n---\n\n## 12. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `"Fix bug" at (1, 2)` | Point parsed; x=1 clamped, y=1 clamped; off-chart badge shown | Keep coordinates in [0.0, 1.0] or accept the clamp-badge |\n| `quadrant 1 "Quick Wins"` (no Q prefix) | Accepted \u2014 `Q` prefix is optional | Both `quadrant 1` and `quadrant Q1` work |\n| `config: gridLines: false` (on same line) | Only `config:` keyword recognized; `gridLines: false` silently ignored | Put options on their own indented lines below `config:` |\n| `x-axis: "Low" \u2192 "High"` (quoted labels) | Arrow not found inside quotes \u2014 treated as plain text | Remove quotes: `x-axis: Low \u2192 High` |\n| `matrix heatmap` without dimensions | Defaults to 2\xD72; rows/cols directives set actual size | Specify dimensions on the header: `matrix heatmap 4x4` |\n| `cell (0, 0) level: Strong` (capital S) | `level` match is case-insensitive \u2014 accepted | Both `strong` and `Strong` work |\n| `shape: oval` | Unknown shape value \u2014 silently ignored | Use `circle`, `square`, `triangle`, or `diamond` |\n| `"Fix bug" at (0.1, 0.9)` on an Eisenhower with a task list | Valid scatter point \u2014 but you probably wanted a list in a cell | Add `style: table` and use `Q2: "Fix bug"` instead |\n| `Q1: "item"` on a 3\xD73 template | `Q` shorthand is parsed as a point line \u2014 silently dropped | Use `cell (col, row) label: "item"` for 3\xD73 grids |\n\n---\n\n## 13. Grammar (EBNF)\n\n```text\ndocument = header directive*\n\nheader = "matrix" ( template-name | mode-header | title )? NEWLINE\ntemplate-name = "eisenhower"|"impact-effort"|"rice"|"bcg"|"ansoff"|"johari"|"9-box"|"risk-matrix"\nmode-header = ( "heatmap" | "correlation" ) ( number "x" number )? title?\ntitle = quoted-string | bare-text\n\ndirective = x-axis | y-axis | quadrant-dir | config-block\n | point | cell | q-short | rows-dir | cols-dir | grid-dir\n | style-dir | title-dir | axis-dir | margins-dir | comment | blank\n\nx-axis = "x-axis:" WS axis-spec NEWLINE\ny-axis = "y-axis:" WS axis-spec NEWLINE\naxis-spec = text arrow text | text # plain text \u2192 high label only\narrow = "\u2192" | "->" | "\u2191" | "\u2190" | "<-" | "<" | "\u2193"\n\nquadrant-dir = "quadrant" WS "Q"? digit WS quoted-string ( WS "description:" quoted-string )? NEWLINE\n\nconfig-block = "config:" NEWLINE ( INDENT key ":" WS value NEWLINE )*\n\npoint = quoted-string WS "at" WS "(" number "," number ")" ( WS point-prop )* NEWLINE\npoint-prop = "size:" number\n | "category:" bareword\n | "color:" hex-color\n | "shape:" ( "circle"|"square"|"triangle"|"diamond" )\n | "highlight:" "true"\n | "note:" quoted-string\n | "label:" quoted-string\n\ncell = "cell" WS "(" digit "," digit ")" ( WS cell-prop )* NEWLINE\ncell-prop = "value:" number\n | "label:" quoted-string\n | "level:" ( "strong" | "medium" | "weak" )\n\nstyle-dir = "style:" WS "table" NEWLINE\nq-short = "Q" ( "1" | "2" | "3" | "4" ) ":" WS quoted-string NEWLINE # 2\xD72 only\n\nrows-dir = "rows:" WS label-list NEWLINE\ncols-dir = "cols:" WS label-list NEWLINE\ngrid-dir = "grid:" WS number "x" number NEWLINE\naxis-dir = "axis:" WS ( "off" | "on" | "auto" ) NEWLINE\nmargins-dir = "margins:" WS ( "true" | "false" | "on" | "1" ) NEWLINE\n\nlabel-list = "[" text ("," text)* "]" | text ("," text)*\nquoted-string = \'"\' any-char-but-quote* \'"\' | "\'" any-char-but-quote* "\'"\ncomment = "#" any NEWLINE\n```\n\nAuthoritative source: `src/diagrams/matrix/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
3390
+ "content": '## 1. Your first matrix\n\nThe smallest useful matrix: a custom 2\xD72 with two labeled axes and three points.\n\n```\nmatrix "Feature Prioritization"\nx-axis: Low Effort \u2192 High Effort\ny-axis: Low Value \u2192 High Value\n\n"Add search" at (0.3, 0.8)\n"Rebuild pipeline" at (0.85, 0.7)\n"Update footer" at (0.2, 0.2)\n```\n\nFour rules cover 80% of usage:\n\n1. Start with the keyword `matrix`, optionally followed by a template name or a quoted title.\n2. Set the axes with `x-axis:` and `y-axis:` \u2014 or use a built-in template and skip this step entirely.\n3. Each point is `"Label" at (x, y)` where `x` and `y` are decimal fractions from 0.0 (low/left/bottom) to 1.0 (high/right/top).\n4. Add optional properties \u2014 `size:`, `category:`, `color:`, `shape:`, `highlight:` \u2014 after the coordinates.\n\n> Comments must start with `#` anywhere on a line (outside quoted strings).\n\n---\n\n## 2. Built-in templates\n\nA template pre-configures axes, quadrant labels, and grid size. Just use the template name as the second token on the header line.\n\n| Template | Grid | Use case |\n|---|---|---|\n| `eisenhower` | 2\xD72 | Urgency / Importance task prioritization |\n| `impact-effort` | 2\xD72 | Feature prioritization by impact vs. effort |\n| `rice` | 2\xD72 | RICE scoring \u2014 Reach \xD7 Impact vs. Effort |\n| `bcg` | 2\xD72 | Portfolio \u2014 Market Share vs. Growth rate |\n| `ansoff` | 2\xD72 | Product/market growth strategy |\n| `johari` | 2\xD72 | Self-awareness \u2014 known-to-self vs. known-to-others |\n| `9-box` | 3\xD73 | HR talent review \u2014 Performance vs. Potential |\n| `risk-matrix` | 5\xD75 | Risk assessment \u2014 Likelihood vs. Severity (heatmap) |\n\n```\nmatrix eisenhower "This Week"\n"Ship hotfix" at (0.1, 0.9) size: 5 highlight: true\n"Team 1:1s" at (0.1, 0.7) size: 3\n"Write Q3 OKRs" at (0.8, 0.85) size: 4\n"Inbox zero" at (0.1, 0.3) size: 2\n"Refactor auth" at (0.75, 0.4) size: 3\n```\n\nAxes and quadrant labels from a template can be overridden with explicit `x-axis:` / `y-axis:` / `quadrant` directives.\n\n---\n\n## 3. Axes\n\nAxis lines declare the semantic poles of each dimension.\n\n```\nx-axis: Low Effort \u2192 High Effort\ny-axis: Low Value \u2192 High Value\n```\n\nThe arrow separates the low label (left / bottom) from the high label (right / top). All of these separators are equivalent:\n\n| Separator | Example |\n|---|---|\n| `\u2192` (Unicode) | `x-axis: Rare \u2192 Certain` |\n| `->` (ASCII) | `x-axis: Rare -> Certain` |\n| `\u2191` | `y-axis: Cheap \u2191 Expensive` |\n| `\u2190` / `<-` / `<` | Reversed axis \u2014 high label is on the left |\n\nA **reversed axis** is for conventions where the "high" value sits at the left or bottom:\n\n```\nx-axis: High Market Share \u2190 Low Market Share\n```\n\n```\nmatrix "Product Portfolio"\nx-axis: High Market Share \u2190 Low Market Share\ny-axis: Low Growth \u2192 High Growth\n\nquadrant Q1 "Question Marks"\nquadrant Q2 "Stars"\nquadrant Q3 "Cash Cows"\nquadrant Q4 "Dogs"\n\n"Analytics Suite" at (0.25, 0.35) size: 5\n"ChatBot Pro" at (0.2, 0.8) size: 4 highlight: true\n"Legacy CRM" at (0.75, 0.25) size: 6\n"Mobile App" at (0.65, 0.75) size: 3\n```\n\n---\n\n## 4. Points\n\nEach point is a bubble positioned by a normalized (x, y) coordinate pair.\n\n```\n"Label" at (x, y)\n"Label" at (x, y) size: 4 category: design color: #7B1FA2 highlight: true note: "clarify spec"\n```\n\n| Property | Values | Meaning |\n|---|---|---|\n| `size:` | positive number | Bubble area weight (default: 3) |\n| `category:` | bareword | Color group; drives the legend |\n| `color:` | hex string | Override bubble color for this point |\n| `shape:` | `circle` \\| `square` \\| `triangle` \\| `diamond` | Bubble shape (default: `circle`) |\n| `highlight:` | `true` | Draws an emphasis ring around the bubble |\n| `note:` | quoted string | Tooltip annotation |\n| `label:` | quoted string | Replaces the display label (different from the ID) |\n\nCoordinates outside `[0, 1]` are clamped to the chart boundary and flagged with a badge \u2014 the original value is stored for tooltip display.\n\n```\nmatrix "Risk Register"\nx-axis: Low Impact \u2192 High Impact\ny-axis: Rare \u2192 Certain\n\n"Vendor delay" at (0.45, 0.7) size: 4 category: schedule highlight: true\n"Security breach" at (0.9, 0.3) size: 5 category: security shape: diamond\n"Budget overrun" at (0.5, 0.65) size: 3 category: finance\n"Key hire falls through" at (0.6, 0.55) size: 3 category: people\n"Scope creep" at (0.4, 0.8) size: 4 category: schedule\n```\n\n---\n\n## 5. Quadrant labels\n\nLabel each quadrant with a name and an optional subtitle.\n\n```\nquadrant Q1 "Do First"\nquadrant Q2 "Schedule"\nquadrant Q3 "Delete"\nquadrant Q4 "Delegate"\n\n# With an optional subtitle:\nquadrant Q1 "Do First" description: "High urgency, high importance"\n```\n\nQuadrant numbering follows the standard mathematical convention: **Q1 = top-right, Q2 = top-left, Q3 = bottom-left, Q4 = bottom-right**. The `Q` prefix is optional \u2014 `quadrant 1 "Label"` is equally valid.\n\n---\n\n## 6. Heatmap mode\n\nHeatmap mode fills N\xD7M cells with color intensity instead of plotting bubble positions.\n\n```\nmatrix heatmap 4x3 "Skill Matrix"\nrows: [Strategy, Execution, Communication, Technical]\ncols: [Junior, Mid, Senior]\n\ncell (0,0) level: weak\ncell (1,0) level: medium\ncell (2,0) level: strong\ncell (0,1) value: 7\ncell (1,2) label: "Top 10%"\n```\n\n- `matrix heatmap COLxROW` \u2014 header sets the grid dimensions.\n- `rows:` and `cols:` \u2014 comma-separated or bracket-list of axis labels.\n- `cell (col, row)` \u2014 zero-indexed, column first, row second (row 0 = bottom).\n- `level:` \u2014 `strong` (3), `medium` (2), or `weak` (1) \u2014 shorthand for heat intensity.\n- `value:` \u2014 explicit numeric value (overrides `level:`).\n- `label:` \u2014 quoted text placed inside the cell.\n\n```\nmatrix heatmap 4x4 "Competency Heat Map"\nrows: [Leadership, Execution, Communication, Technical]\ncols: [Junior, Mid, Senior, Staff]\n\ncell (0,0) level: weak\ncell (1,0) level: medium\ncell (2,0) level: strong\ncell (3,0) level: strong\ncell (0,1) level: medium\ncell (1,1) level: medium\ncell (2,1) level: strong\ncell (3,1) level: strong\ncell (0,2) level: weak\ncell (1,2) level: medium\ncell (2,2) level: medium\ncell (3,2) level: strong\ncell (0,3) level: weak\ncell (1,3) level: weak\ncell (2,3) level: medium\ncell (3,3) level: strong\n```\n\n---\n\n## 7. Correlation mode\n\nCorrelation mode renders an N\xD7M dot matrix where intensity represents the relationship strength between row and column variables.\n\n```\nmatrix correlation 4x4 "Product Metrics"\nrows: [DAU, Retention, Revenue, NPS]\ncols: [DAU, Retention, Revenue, NPS]\n\ncell (0,0) value: 1\ncell (1,0) value: 0.82\ncell (2,0) value: 0.54\ncell (3,0) value: 0.71\n```\n\nThe same `cell` syntax applies. `level: strong | medium | weak` is also accepted in correlation mode.\n\n---\n\n## 8. SIPOC mode\n\nA **SIPOC** is the one-page scoping table that opens the *Define* phase of a Six Sigma DMAIC project. It names, in five fixed columns left to right, everyone and everything the process touches: **S**uppliers \xB7 **I**nputs \xB7 **P**rocess \xB7 **O**utputs \xB7 **C**ustomers. Before a team measures or improves anything, SIPOC pins down the boundary \u2014 "where does this process start, where does it end, and who hands work in and out of it."\n\n```\nmatrix sipoc "Order fulfilment"\nsuppliers: "Vendor", "Warehouse"\ninputs: "PO", "Stock levels"\nprocess: "Receive order", "Pick", "Pack", "Ship"\noutputs: "Shipped package", "Invoice"\ncustomers: "End customer", "Finance"\n```\n\n- Start with `matrix sipoc`, optionally followed by a quoted title.\n- Each of the five columns is its own directive: `suppliers:`, `inputs:`, `process:`, `outputs:`, `customers:`.\n- After the colon, list the entries as **comma-separated quoted strings**. A column may have any number of entries; the rows simply stack top-down inside that column.\n- The `process:` column is the high-level step sequence (typically 4\u20137 steps) \u2014 keep it to the major stages, not a detailed flowchart.\n\nThe five columns always render in the canonical S-I-P-O-C order regardless of the order you declare them, so the diagram reads correctly even if an LLM emits the blocks out of sequence.\n\n```\nmatrix sipoc "Order fulfilment"\nsuppliers: "Vendor", "Warehouse"\ninputs: "PO", "Stock levels"\nprocess: "Receive order", "Pick", "Pack", "Ship"\noutputs: "Shipped package", "Invoice"\ncustomers: "End customer", "Finance"\n```\n\n---\n\n## 9. QFD mode (House of Quality)\n\n**Quality Function Deployment (QFD)** \u2014 the *House of Quality*, introduced by Yoji Akao \u2014 translates what customers want into the engineering characteristics that deliver it. Rows are the **WHATs** (customer requirements, each with an importance weight); columns are the **HOWs** (the measurable engineering characteristics the team controls). The body of the grid records how strongly each HOW serves each WHAT.\n\nThe differentiator: the engine **computes** the bottom row for you. Each HOW\'s *technical importance* is the sum down its column of `weight \xD7 relationship strength` \u2014 a ranked answer to "which engineering characteristic moves the most customer value, and is therefore worth the most effort." And the roof of the house \u2014 a half-matrix of diamond cells above the columns \u2014 records whether two HOWs help or fight each other.\n\n```\nmatrix qfd "Coffee maker"\nwhat: "Quiet operation" weight: 5\nwhat: "Brews fast" weight: 3\nwhat: "Energy efficient" weight: 4\nhow: "Fan RPM" dir: down\nhow: "Heater watts" dir: up\nhow: "Insulation" dir: up\nrel (0,0): 9\nrel (0,2): 3\nrel (1,1): 9\nrel (2,1): 3\nrel (2,2): 9\nroof (0,1): --\nroof (1,2): +\n```\n\n### WHATs and HOWs\n\n| Directive | Form | Meaning |\n|---|---|---|\n| `what:` | `what: "Label" weight: N` | A customer requirement (one row). `weight:` is its importance, conventionally 1\u20135. Declaration order is the row order, indexed from 0. |\n| `how:` | `how: "Label" dir: up\\|down` | An engineering characteristic (one column). Declaration order is the column order, indexed from 0. `dir:` is the optimization target \u2014 `up` = more is better, `down` = less is better. |\n\n### Relationship cells\n\n`rel (i, j): strength` records how strongly column-`j` HOW serves row-`i` WHAT. The index is **(row, column)**, both zero-based.\n\n| Strength | Meaning |\n|---|---|\n| `9` | Strong relationship |\n| `3` | Medium relationship |\n| `1` | Weak relationship |\n| *(omitted)* | No relationship \u2014 leave the cell out |\n\nThis 9 / 3 / 1 scale is the QFD convention: it is deliberately non-linear so that one strong link outweighs several weak ones when the importance row is summed.\n\n### Computed technical-importance row\n\nThe engine sums each column to produce the technical-importance row at the foot of the house:\n\n```\nimportance(j) = \u03A3 over rows i ( weight(i) \xD7 strength(i, j) )\n```\n\nFor the coffee-maker example above the row computes to **45 / 39 / 51** \u2014 Insulation (51) is the highest-leverage characteristic, Heater watts (39) the lowest. This ranking is the deliverable: it tells the team where to spend engineering effort.\n\nAdd `normalize: true` (its own line, anywhere in the block) to show each column as a **percentage of the total** instead of a raw sum \u2014 for this example, **33% / 29% / 38%**. Percentages make the relative priorities easier to read across very different weight scales.\n\n### The roof \u2014 HOW \xD7 HOW correlations\n\nThe **roof** is the triangular half-matrix sitting above the columns. `roof (i, j): glyph` records whether HOW `i` and HOW `j` reinforce or conflict with each other \u2014 the synergies and trade-offs a team must reconcile.\n\n| Glyph | Correlation |\n|---|---|\n| `++` | Strong positive \u2014 improving one strongly helps the other |\n| `+` | Positive |\n| `-` | Negative |\n| `--` | Strong negative \u2014 improving one hurts the other (a trade-off) |\n| *(omitted)* | No correlation \u2014 leave the cell out |\n\nEach roof entry renders as a diamond cell in the standard QFD pitched-roof grid. In the example, `roof (0,1): --` flags that pushing Fan RPM down while pushing Heater watts up is a trade-off, and `roof (1,2): +` flags that Heater watts and Insulation reinforce each other.\n\n```\nmatrix qfd "Coffee maker"\nwhat: "Quiet operation" weight: 5\nwhat: "Brews fast" weight: 3\nwhat: "Energy efficient" weight: 4\nhow: "Fan RPM" dir: down\nhow: "Heater watts" dir: up\nhow: "Insulation" dir: up\nrel (0,0): 9\nrel (0,2): 3\nrel (1,1): 9\nrel (2,1): 3\nrel (2,2): 9\nroof (0,1): --\nroof (1,2): +\n```\n\n---\n\n## 10. Config options\n\nA `config:` block tunes visual rendering. Each option goes on its own indented line below the `config:` header.\n\n```\nconfig:\n quadrantBg: true\n gridLines: true\n axisArrows: true\n bubbleScale: area\n legendPosition: bottom-right\n```\n\n| Key | Values | Default | Effect |\n|---|---|---|---|\n| `quadrantBg` | `true` \\| `false` | `true` | Colored quadrant background fills |\n| `gridLines` | `true` \\| `false` | `true` | Grid lines overlay |\n| `axisArrows` | `true` \\| `false` | `true` | Arrows at axis ends |\n| `bubbleScale` | `area` \\| `radius` | `area` | Whether `size:` scales bubble area or radius |\n| `quadrantAnnotations` | `true` \\| `false` | `true` | Show quadrant label text in corners |\n| `legendPosition` | `bottom-right` \\| `right` \\| `bottom-center` \\| `none` | `bottom-right` | Category legend placement |\n| `labelCollision` | `auto` \\| `offset-only` \\| `leader-only` \\| `off` | `auto` | Overlap avoidance strategy for point labels |\n| `offChartPolicy` | `clamp-badge` \\| `drop` | `clamp-badge` | What to do with points outside [0,1] |\n\nTwo shorthand directives also work at the top level (not inside the `config:` block):\n\n```\naxis: off # off | on | auto \u2014 show or hide the axis lines\nmargins: true # true | false \u2014 show Score + Rank margins (correlation mode)\n```\n\n---\n\n## 11. Labels & comments\n\n- **Title:** `matrix "My Title"` or `title: My Title` as a standalone line.\n- **Point label:** the quoted string before `at (\u2026)`.\n- **Axis labels:** `x-axis:` and `y-axis:` directives.\n- **Quadrant labels:** `quadrant Q1 "Name"` directive.\n- **Comments:** `#` anywhere on a line, outside quoted strings.\n\n```\nmatrix "Prioritization"\n# This is a comment\nx-axis: Low Cost \u2192 High Cost # inline comment after a directive\n"Fix bug" at (0.1, 0.9) size: 3 # comment after a point\n```\n\n---\n\n## 12. Table mode (`style: table`)\n\nThe default matrix rendering is a **scatter / bubble chart** \u2014 points float at (x, y) coordinates. For frameworks where the output is a list of items grouped by quadrant (Eisenhower, Johari, Impact-Effort, 9-box), use `style: table` to switch to a **text-in-cell layout** instead.\n\n```\nmatrix eisenhower "This Week"\nstyle: table\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\nQ1: "Write Q3 OKRs"\nQ1: "Refactor auth layer"\nQ4: "LinkedIn updates"\nQ3: "Reorganize Slack channels"\n```\n\n`style: table` applies these changes automatically:\n\n| Effect | Detail |\n|---|---|\n| Axes and arrows hidden | No axis lines, labels, or arrowheads |\n| Grid lines hidden | Only the outer border and cell dividers remain |\n| Quadrant titles move inside cells | Each title becomes a cell header instead of a corner overlay |\n| Items stack as a bullet list | Multiple entries for the same quadrant stack top-down |\n\n### `Q1` \u2026 `Q4` shorthand (2\xD72 only)\n\nFor 2\xD72 templates, use `Qn: "item"` instead of the longer `cell (col, row) label: "item"` form. Mapping:\n\n| Shorthand | Cell | Eisenhower | Johari |\n|---|---|---|---|\n| `Q1:` | top-right | Schedule | Blind |\n| `Q2:` | top-left | Do First | Open / Arena |\n| `Q3:` | bottom-left | Delete | Hidden / Fa\xE7ade |\n| `Q4:` | bottom-right | Delegate | Unknown |\n\nRepeat a shorthand key to add multiple items to the same cell:\n\n```\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\n```\n\nFor 3\xD73 grids (9-box), use `cell (col, row) label: "\u2026"` directly \u2014 the `Q` shorthand is 2\xD72 only.\n\n### When to use table vs scatter\n\n| Use `style: table` for | Use scatter (default) for |\n|---|---|\n| Eisenhower with task lists | Eisenhower with `size:` effort weights |\n| Johari window coaching | Impact-Effort with bubble = revenue |\n| Backlog grouping (no numeric third dimension) | RICE / BCG portfolio (third dimension IS the bubble size) |\n| 9-box talent review | Risk heatmap (5\xD75 with numeric severity) |\n\n```\nmatrix eisenhower "This Week"\nstyle: table\nQ2: "Ship hotfix"\nQ2: "Customer demo prep"\nQ1: "Write Q3 OKRs"\nQ1: "Refactor auth layer"\nQ4: "LinkedIn updates"\nQ3: "Reorganize Slack channels"\n```\n\n---\n\n## 13. Reserved words & escaping\n\n**Reserved at line start:** `matrix` (header), `x-axis:`, `y-axis:`, `quadrant`, `config:`, `title:`, `rows:`, `cols:`, `grid:`, `axis:`, `margins:`, `cell`. In **SIPOC** mode: `suppliers:`, `inputs:`, `process:`, `outputs:`, `customers:`. In **QFD** mode: `what:`, `how:`, `rel`, `roof`, `normalize:`.\n\n**Point lines must start with a quote character** (`"` or `\'`). A line that does not start with a quote is not treated as a point.\n\n**Strings with spaces** in axis labels do not need quoting \u2014 the text after the colon (and after the arrow) is taken verbatim. In `note:` and `label:` point properties, use double quotes.\n\n---\n\n## 14. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| `"Fix bug" at (1, 2)` | Point parsed; x=1 clamped, y=1 clamped; off-chart badge shown | Keep coordinates in [0.0, 1.0] or accept the clamp-badge |\n| `quadrant 1 "Quick Wins"` (no Q prefix) | Accepted \u2014 `Q` prefix is optional | Both `quadrant 1` and `quadrant Q1` work |\n| `config: gridLines: false` (on same line) | Only `config:` keyword recognized; `gridLines: false` silently ignored | Put options on their own indented lines below `config:` |\n| `x-axis: "Low" \u2192 "High"` (quoted labels) | Arrow not found inside quotes \u2014 treated as plain text | Remove quotes: `x-axis: Low \u2192 High` |\n| `matrix heatmap` without dimensions | Defaults to 2\xD72; rows/cols directives set actual size | Specify dimensions on the header: `matrix heatmap 4x4` |\n| `cell (0, 0) level: Strong` (capital S) | `level` match is case-insensitive \u2014 accepted | Both `strong` and `Strong` work |\n| `shape: oval` | Unknown shape value \u2014 silently ignored | Use `circle`, `square`, `triangle`, or `diamond` |\n| `"Fix bug" at (0.1, 0.9)` on an Eisenhower with a task list | Valid scatter point \u2014 but you probably wanted a list in a cell | Add `style: table` and use `Q2: "Fix bug"` instead |\n| `Q1: "item"` on a 3\xD73 template | `Q` shorthand is parsed as a point line \u2014 silently dropped | Use `cell (col, row) label: "item"` for 3\xD73 grids |\n\n---\n\n## 15. Grammar (EBNF)\n\n```text\ndocument = header directive*\n\nheader = "matrix" ( template-name | mode-header | title )? NEWLINE\ntemplate-name = "eisenhower"|"impact-effort"|"rice"|"bcg"|"ansoff"|"johari"|"9-box"|"risk-matrix"\nmode-header = ( "heatmap" | "correlation" ) ( number "x" number )? title?\n | ( "sipoc" | "qfd" ) title?\ntitle = quoted-string | bare-text\n\ndirective = x-axis | y-axis | quadrant-dir | config-block\n | point | cell | q-short | rows-dir | cols-dir | grid-dir\n | style-dir | title-dir | axis-dir | margins-dir\n | sipoc-col | qfd-what | qfd-how | qfd-rel | qfd-roof | normalize-dir\n | comment | blank\n\n# SIPOC mode\nsipoc-col = ( "suppliers:" | "inputs:" | "process:" | "outputs:" | "customers:" )\n WS quoted-string ( "," quoted-string )* NEWLINE\n\n# QFD / House of Quality mode\nqfd-what = "what:" WS quoted-string WS "weight:" number NEWLINE\nqfd-how = "how:" WS quoted-string ( WS "dir:" ( "up" | "down" ) )? NEWLINE\nqfd-rel = "rel" WS "(" number "," number ")" ":" WS ( "9" | "3" | "1" ) NEWLINE # (row, col)\nqfd-roof = "roof" WS "(" number "," number ")" ":" WS ( "++" | "+" | "-" | "--" ) NEWLINE # (how, how)\nnormalize-dir = "normalize:" WS "true" NEWLINE\n\nx-axis = "x-axis:" WS axis-spec NEWLINE\ny-axis = "y-axis:" WS axis-spec NEWLINE\naxis-spec = text arrow text | text # plain text \u2192 high label only\narrow = "\u2192" | "->" | "\u2191" | "\u2190" | "<-" | "<" | "\u2193"\n\nquadrant-dir = "quadrant" WS "Q"? digit WS quoted-string ( WS "description:" quoted-string )? NEWLINE\n\nconfig-block = "config:" NEWLINE ( INDENT key ":" WS value NEWLINE )*\n\npoint = quoted-string WS "at" WS "(" number "," number ")" ( WS point-prop )* NEWLINE\npoint-prop = "size:" number\n | "category:" bareword\n | "color:" hex-color\n | "shape:" ( "circle"|"square"|"triangle"|"diamond" )\n | "highlight:" "true"\n | "note:" quoted-string\n | "label:" quoted-string\n\ncell = "cell" WS "(" digit "," digit ")" ( WS cell-prop )* NEWLINE\ncell-prop = "value:" number\n | "label:" quoted-string\n | "level:" ( "strong" | "medium" | "weak" )\n\nstyle-dir = "style:" WS "table" NEWLINE\nq-short = "Q" ( "1" | "2" | "3" | "4" ) ":" WS quoted-string NEWLINE # 2\xD72 only\n\nrows-dir = "rows:" WS label-list NEWLINE\ncols-dir = "cols:" WS label-list NEWLINE\ngrid-dir = "grid:" WS number "x" number NEWLINE\naxis-dir = "axis:" WS ( "off" | "on" | "auto" ) NEWLINE\nmargins-dir = "margins:" WS ( "true" | "false" | "on" | "1" ) NEWLINE\n\nlabel-list = "[" text ("," text)* "]" | text ("," text)*\nquoted-string = \'"\' any-char-but-quote* \'"\' | "\'" any-char-but-quote* "\'"\ncomment = "#" any NEWLINE\n```\n\nAuthoritative source: `src/diagrams/matrix/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
2918
3391
  },
2919
3392
  "orgchart": {
2920
3393
  "title": "Org chart",
@@ -2922,7 +3395,7 @@ var SYNTAX = {
2922
3395
  },
2923
3396
  "mindmap": {
2924
3397
  "title": "Mind map",
2925
- "content": '## 1. Your first mind map\n\nThe smallest useful mind map: a central topic with two branches, one with a sub-item.\n\n```\nmindmap\n\n# Team retrospective\n\n## What went well\n- Clear sprint goals\n- Good test coverage\n\n## What to improve\n- Slower PR reviews\n - Add a review SLA\n```\n\nFour rules cover 80% of usage:\n\n1. Start with an optional `mindmap` keyword on its own line, then a blank line.\n2. The root is the single `#` heading \u2014 exactly one is allowed.\n3. Use `##`, `###`, and deeper headings to set branch depth. Heading level equals tree depth.\n4. Use `-`, `*`, or `+` bullets to add sub-items under any heading. Each 2-space indent adds one more depth level.\n\n> Comments are not supported. Use `%%` directives (before the `#` root) for configuration only.\n\n---\n\n## 2. Headings and depth\n\nHeading level maps directly to tree depth. `#` is always the root (depth 0). `##` is depth 1. `###` is depth 2, and so on up to `######` (depth 5).\n\n```\nmindmap\n\n# Root\n## Branch A \u2190 depth 1\n### Sub-branch \u2190 depth 2\n#### Leaf \u2190 depth 3\n## Branch B\n```\n\nHeadings can jump levels \u2014 `####` after `##` is valid and produces a node at depth 3. The tree depth is relative to the root, not to the previous heading.\n\n---\n\n## 3. Bullets\n\nBullets extend a heading branch with further detail. Any of `-`, `*`, or `+` is accepted as the bullet marker. Each **2 spaces** of indentation adds one level of depth relative to the enclosing heading.\n\n```\n## Risks\n- Technical complexity \u2190 depth 2 (one level under ## Risks)\n - Legacy integrations \u2190 depth 3 (2 spaces indent)\n - Auth service \u2190 depth 4 (4 spaces indent)\n- Team availability \u2190 depth 2 again\n```\n\n```\nmindmap\n\n# Book outline\n\n## Chapter 1 \u2014 Introduction\n- Why this matters\n - Historical context\n - Current state\n- What you will learn\n\n## Chapter 2 \u2014 Core concepts\n- Concept A\n - Definition\n - Examples\n- Concept B\n - Definition\n - Worked example\n - Step-by-step walkthrough\n```\n\n---\n\n## 4. Inline formatting\n\nNode labels support a subset of Markdown inline formatting. The parser tokenizes labels at parse time; the renderer uses the tokens to emit styled text.\n\n| Syntax | Effect | Example |\n|---|---|---|\n| `**text**` | Bold | `**Critical path**` |\n| `*text*` | Italic | `*optional*` |\n| `` `code` `` | Monospace code | `` `npm install` `` |\n| `[text](url)` | Link | `[RFC 7519](https://tools.ietf.org/html/rfc7519)` |\n| `[ ] item` | Unchecked task | `[ ] Write tests` |\n| `[x] item` | Checked task | `[x] Design review` |\n\nThe checkbox must be at the very start of the label (before any other text). Inline formatting can be nested: `**[bold link](url)**`.\n\n```\nmindmap\n\n# Sprint 24 review\n\n## Completed\n- [x] **Auth redesign** \u2014 JWT + refresh tokens\n- [x] API rate limiting \\`per-user\\`\n- [x] [Error budget dashboard](https://metrics.example.com)\n\n## In progress\n- [ ] *Mobile push notifications*\n - [ ] iOS APNs integration\n - [ ] Android FCM setup\n\n## Blocked\n- [ ] **Payment webhook** \u2014 waiting on Stripe team\n - *Escalated to account manager*\n```\n\n---\n\n## 5. Layout styles\n\nThe `%% style:` directive selects the layout algorithm. Place it before the `#` root heading.\n\n| Style | Layout | Best for |\n|---|---|---|\n| `map` (default) | Radial \u2014 branches spread in all directions from the center | Brainstorming, concept maps, free-form exploration |\n| `logic-right` | Horizontal tree \u2014 all branches extend to the right | Structured outlines, hierarchies, sequential breakdowns |\n\n```\n%% style: map\n%% style: logic-right\n```\n\n**`map`** (default) \u2014 radial layout, branches spread in all directions from the center. Best for brainstorming and concept maps.\n\n```\nmindmap\n\n# Machine learning\n\n## Supervised\n### Classification\n- Decision tree\n- SVM\n- Neural net\n### Regression\n- Linear\n- Gradient boosting\n\n## Unsupervised\n### Clustering\n- K-means\n- DBSCAN\n### Reduction\n- PCA\n- t-SNE\n\n## Reinforcement\n- Q-learning\n- Policy gradient\n```\n\n**`logic-right`** \u2014 horizontal tree, all branches extend to the right. Best for structured outlines and sequential hierarchies.\n\n```\nmindmap\n%% style: logic-right\n\n# Machine learning\n\n## Supervised\n### Classification\n- Decision tree\n- SVM\n- Neural net\n### Regression\n- Linear\n- Gradient boosting\n\n## Unsupervised\n### Clustering\n- K-means\n- DBSCAN\n### Reduction\n- PCA\n- t-SNE\n\n## Reinforcement\n- Q-learning\n- Policy gradient\n```\n\n---\n\n## 6. Directives\n\nDirectives are `%%` lines placed **before** the `#` root heading. They configure the diagram globally.\n\n| Directive | Values | Default | Effect |\n|---|---|---|---|\n| `%% style: \u2026` | `map`, `logic-right` | `map` | Layout algorithm |\n| `%% theme: \u2026` | any string | (none) | Theme override passed to renderer |\n| `%% maxLabelWidth: \u2026` | integer 80\u20131000 | `240` | Max pixel width before label wraps |\n\n```\nmindmap\n%% style: logic-right\n%% maxLabelWidth: 320\n\n# Wide label root\n```\n\n```\nmindmap\n%% style: logic-right\n%% maxLabelWidth: 200\n\n# Schematex features\n\n## DSL-first design\n- One keyword per diagram\n- AI-friendly syntax\n- CJK support\n\n## Zero dependencies\n- Hand-written parser\n- No D3, no dagre\n- ~KB-level bundle\n\n## Standards-compliant\n- IEEE for logic gates\n- IEC for circuits\n- McGoldrick for genograms\n```\n\n---\n\n## 7. Labels & comments\n\n- **Root title:** the text after `#` on the root heading line.\n- **Branch labels:** the text after `##`, `###`, etc.\n- **Bullet labels:** the text after the `- ` / `* ` / `+ ` marker.\n- **Inline formatting:** `**bold**`, `*italic*`, `` `code` ``, `[text](url)`, `[ ]` / `[x]`.\n- **Comments:** not supported in the body. Use `%%` directives before the `#` root for configuration; `%%` lines in the body are treated as directives (silently ignored if unrecognized).\n\n---\n\n## 8. Reserved words & escaping\n\n**Reserved at document start:** `mindmap` (optional keyword) and `%%` (directive prefix).\n\n**Reserved as root:** exactly one `#` heading; a second `#` heading throws a parse error.\n\n**Bullet markers:** `-`, `*`, `+` followed by a space. A `*` that is not followed by a space is treated as an italic marker if it appears inside label text.\n\n**Inline conflicts:** a label beginning with `[ ]` or `[x] ` is parsed as a checkbox, not a Markdown link. If you need a label that literally starts with `[`, write `\\[` \u2014 the backslash escapes the bracket.\n\n---\n\n## 9. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| Two `#` headings | `Error: multiple # center nodes not allowed` | Use exactly one `#` heading as the root |\n| `##Branch` (no space after `##`) | Line is not recognized as a heading; silently skipped | Always put a space: `## Branch` |\n| Bullet indented 3 spaces | Depth = `lastHeadingDepth + 1 + floor(3/2) = lastHeadingDepth + 2` \u2014 may create an unexpected level | Use multiples of 2 spaces: 0, 2, 4, 6\u2026 |\n| `%% style: radial` | Unknown value silently ignored; layout stays `map` | Use `map` or `logic-right` |\n| `mindmap` keyword mid-document | Treated as a plain text line (the keyword is only recognized on the very first line) | Place `mindmap` on line 1, before any content |\n| `[ ]text` (no space after bracket) | Checkbox not recognized; rendered as literal `[ ]text` | `[ ] text` \u2014 space required after the closing bracket |\n\n---\n\n## 10. Grammar (EBNF)\n\n```text\ndocument = ("mindmap" NEWLINE)? (blank | directive)* node*\n\ndirective = "%%" WS key ":" WS value NEWLINE\nkey = "style" | "theme" | "maxlabelwidth"\n\nnode = heading | bullet\nheading = INDENT? "#"+ SPACE label NEWLINE\nbullet = SPACE* bullet-marker SPACE label NEWLINE\nbullet-marker = "-" | "*" | "+"\n\nlabel = inline-token*\ninline-token = checkbox\n | "**" inline-token* "**"\n | "*" inline-token* "*"\n | "`" code-text "`"\n | "[" inline-token* "]" "(" url ")"\n | plain-text\n\ncheckbox = "[ ]" SPACE | "[x]" SPACE | "[X]" SPACE\n\nINDENT = WS* %% headings may have leading whitespace (ignored)\nSPACE = " " | "\\t"\n```\n\n**Depth rules:**\n- Heading `#` \u2192 depth 0 (root)\n- Heading `##` \u2192 depth 1, `###` \u2192 depth 2, etc.\n- Bullet at `n` leading spaces \u2192 depth = `lastHeadingDepth + 1 + floor(n / 2)`\n\nAuthoritative source: `src/diagrams/mindmap/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
3398
+ "content": '## 1. Your first mind map\n\nThe smallest useful mind map: a central topic with two branches, one with a sub-item.\n\n```\nmindmap\n\n# Team retrospective\n\n## What went well\n- Clear sprint goals\n- Good test coverage\n\n## What to improve\n- Slower PR reviews\n - Add a review SLA\n```\n\nFour rules cover 80% of usage:\n\n1. Start with an optional `mindmap` keyword on its own line, then a blank line.\n2. The root is the single `#` heading \u2014 exactly one is allowed.\n3. Use `##`, `###`, and deeper headings to set branch depth. Heading level equals tree depth.\n4. Use `-`, `*`, or `+` bullets to add sub-items under any heading. Each 2-space indent adds one more depth level.\n\n> Comments are not supported. Use `%%` directives (before the `#` root) for configuration only.\n\n---\n\n## 2. Headings and depth\n\nHeading level maps directly to tree depth. `#` is always the root (depth 0). `##` is depth 1. `###` is depth 2, and so on up to `######` (depth 5).\n\n```\nmindmap\n\n# Root\n## Branch A \u2190 depth 1\n### Sub-branch \u2190 depth 2\n#### Leaf \u2190 depth 3\n## Branch B\n```\n\nHeadings can jump levels \u2014 `####` after `##` is valid and produces a node at depth 3. The tree depth is relative to the root, not to the previous heading.\n\n---\n\n## 3. Bullets\n\nBullets extend a heading branch with further detail. Any of `-`, `*`, or `+` is accepted as the bullet marker. Each **2 spaces** of indentation adds one level of depth relative to the enclosing heading.\n\n```\n## Risks\n- Technical complexity \u2190 depth 2 (one level under ## Risks)\n - Legacy integrations \u2190 depth 3 (2 spaces indent)\n - Auth service \u2190 depth 4 (4 spaces indent)\n- Team availability \u2190 depth 2 again\n```\n\n```\nmindmap\n\n# Book outline\n\n## Chapter 1 \u2014 Introduction\n- Why this matters\n - Historical context\n - Current state\n- What you will learn\n\n## Chapter 2 \u2014 Core concepts\n- Concept A\n - Definition\n - Examples\n- Concept B\n - Definition\n - Worked example\n - Step-by-step walkthrough\n```\n\n---\n\n## 4. Inline formatting\n\nNode labels support a subset of Markdown inline formatting. The parser tokenizes labels at parse time; the renderer uses the tokens to emit styled text.\n\n| Syntax | Effect | Example |\n|---|---|---|\n| `**text**` | Bold | `**Critical path**` |\n| `*text*` | Italic | `*optional*` |\n| `` `code` `` | Monospace code | `` `npm install` `` |\n| `[text](url)` | Link | `[RFC 7519](https://tools.ietf.org/html/rfc7519)` |\n| `[ ] item` | Unchecked task | `[ ] Write tests` |\n| `[x] item` | Checked task | `[x] Design review` |\n\nThe checkbox must be at the very start of the label (before any other text). Inline formatting can be nested: `**[bold link](url)**`.\n\n```\nmindmap\n\n# Sprint 24 review\n\n## Completed\n- [x] **Auth redesign** \u2014 JWT + refresh tokens\n- [x] API rate limiting \\`per-user\\`\n- [x] [Error budget dashboard](https://metrics.example.com)\n\n## In progress\n- [ ] *Mobile push notifications*\n - [ ] iOS APNs integration\n - [ ] Android FCM setup\n\n## Blocked\n- [ ] **Payment webhook** \u2014 waiting on Stripe team\n - *Escalated to account manager*\n```\n\n---\n\n## 5. Layout styles\n\nThe `%% style:` directive selects the layout algorithm. Place it before the `#` root heading.\n\n| Style | Layout | Best for |\n|---|---|---|\n| `map` (default) | Radial \u2014 branches spread in all directions from the center | Brainstorming, concept maps, free-form exploration |\n| `logic-right` | Horizontal tree \u2014 all branches extend to the right | Structured outlines, hierarchies, sequential breakdowns |\n| `futureswheel` | Concentric rings \u2014 the root at the hub, each heading level on its own ring | Foresight, consequence mapping, structured brainstorming |\n| `driver` | Horizontal tree \u2014 aim on the left flowing right through drivers to change ideas | Improvement programs, aim \u2192 driver \u2192 action breakdowns |\n\n```\n%% style: map\n%% style: logic-right\n%% style: futureswheel\n%% style: driver\n```\n\n**`map`** (default) \u2014 radial layout, branches spread in all directions from the center. Best for brainstorming and concept maps.\n\n```\nmindmap\n\n# Machine learning\n\n## Supervised\n### Classification\n- Decision tree\n- SVM\n- Neural net\n### Regression\n- Linear\n- Gradient boosting\n\n## Unsupervised\n### Clustering\n- K-means\n- DBSCAN\n### Reduction\n- PCA\n- t-SNE\n\n## Reinforcement\n- Q-learning\n- Policy gradient\n```\n\n**`logic-right`** \u2014 horizontal tree, all branches extend to the right. Best for structured outlines and sequential hierarchies.\n\n```\nmindmap\n%% style: logic-right\n\n# Machine learning\n\n## Supervised\n### Classification\n- Decision tree\n- SVM\n- Neural net\n### Regression\n- Linear\n- Gradient boosting\n\n## Unsupervised\n### Clustering\n- K-means\n- DBSCAN\n### Reduction\n- PCA\n- t-SNE\n\n## Reinforcement\n- Q-learning\n- Policy gradient\n```\n\n**`futureswheel`** \u2014 a [Futures Wheel](https://en.wikipedia.org/wiki/Futures_wheel) (Jerome Glenn, 1971/72), the classic structured-brainstorming format for thinking through consequences. The central event or trend sits at the hub; first-order consequences land on the inner ring, second-order consequences on the next ring out, and so on. Each child stays inside the angular sector of its parent, and every ring is color-coded by order, so a reader can see at a glance how far a ripple is from the original event. Depth maps to rings: `#` is the hub, `##` is the first ring (1st-order), `###` / bullets under a heading push out to the next ring (2nd-order), and deeper levels keep stepping outward.\n\n```\nmindmap\n%% style: futureswheel\n\n# Remote work becomes default\n\n## Less commuting\n- Lower carbon emissions\n- Cheaper city living\n\n## Distributed teams\n- Async communication norms\n- Global hiring pools\n\n## Empty offices\n- Commercial real estate slump\n- Repurposed to housing\n```\n\n**`driver`** \u2014 a [Driver Diagram](https://www.ihi.org/resources/tools/driver-diagram), the planning tool from the IHI (Institute for Healthcare Improvement) model for improvement. It reads left to right as a tidy tree: the **aim** on the far left, the **primary drivers** (the few high-leverage areas that move the aim) in the next column, then **secondary drivers** and concrete **change ideas** branching further right. Tree levels map cleanly to the structure: `#` is the aim, `##` are primary drivers, and bullets / deeper headings under each become the secondary drivers and change ideas. Use it whenever you need to show *how* a goal will actually be reached.\n\n```\nmindmap\n%% style: driver\n\n# Reduce 30-day readmissions\n\n## Reliable discharge process\n- Teach-back at bedside\n- Med reconciliation\n\n## Timely follow-up\n- Appointment within 7 days\n- Post-discharge phone call\n```\n\n---\n\n## 6. Directives\n\nDirectives are `%%` lines placed **before** the `#` root heading. They configure the diagram globally.\n\n| Directive | Values | Default | Effect |\n|---|---|---|---|\n| `%% style: \u2026` | `map`, `logic-right`, `futureswheel`, `driver` | `map` | Layout algorithm |\n| `%% theme: \u2026` | any string | (none) | Theme override passed to renderer |\n| `%% maxLabelWidth: \u2026` | integer 80\u20131000 | `240` | Max pixel width before label wraps |\n\n```\nmindmap\n%% style: logic-right\n%% maxLabelWidth: 320\n\n# Wide label root\n```\n\n```\nmindmap\n%% style: logic-right\n%% maxLabelWidth: 200\n\n# Schematex features\n\n## DSL-first design\n- One keyword per diagram\n- AI-friendly syntax\n- CJK support\n\n## Zero dependencies\n- Hand-written parser\n- No D3, no dagre\n- ~KB-level bundle\n\n## Standards-compliant\n- IEEE for logic gates\n- IEC for circuits\n- McGoldrick for genograms\n```\n\n---\n\n## 7. Labels & comments\n\n- **Root title:** the text after `#` on the root heading line.\n- **Branch labels:** the text after `##`, `###`, etc.\n- **Bullet labels:** the text after the `- ` / `* ` / `+ ` marker.\n- **Inline formatting:** `**bold**`, `*italic*`, `` `code` ``, `[text](url)`, `[ ]` / `[x]`.\n- **Comments:** not supported in the body. Use `%%` directives before the `#` root for configuration; `%%` lines in the body are treated as directives (silently ignored if unrecognized).\n\n---\n\n## 8. Reserved words & escaping\n\n**Reserved at document start:** `mindmap` (optional keyword) and `%%` (directive prefix).\n\n**Reserved as root:** exactly one `#` heading; a second `#` heading throws a parse error.\n\n**Bullet markers:** `-`, `*`, `+` followed by a space. A `*` that is not followed by a space is treated as an italic marker if it appears inside label text.\n\n**Inline conflicts:** a label beginning with `[ ]` or `[x] ` is parsed as a checkbox, not a Markdown link. If you need a label that literally starts with `[`, write `\\[` \u2014 the backslash escapes the bracket.\n\n---\n\n## 9. Common mistakes\n\n| You wrote | Parser says | Fix |\n|---|---|---|\n| Two `#` headings | `Error: multiple # center nodes not allowed` | Use exactly one `#` heading as the root |\n| `##Branch` (no space after `##`) | Line is not recognized as a heading; silently skipped | Always put a space: `## Branch` |\n| Bullet indented 3 spaces | Depth = `lastHeadingDepth + 1 + floor(3/2) = lastHeadingDepth + 2` \u2014 may create an unexpected level | Use multiples of 2 spaces: 0, 2, 4, 6\u2026 |\n| `%% style: radial` | Unknown value silently ignored; layout stays `map` | Use `map`, `logic-right`, `futureswheel`, or `driver` |\n| `mindmap` keyword mid-document | Treated as a plain text line (the keyword is only recognized on the very first line) | Place `mindmap` on line 1, before any content |\n| `[ ]text` (no space after bracket) | Checkbox not recognized; rendered as literal `[ ]text` | `[ ] text` \u2014 space required after the closing bracket |\n\n---\n\n## 10. Grammar (EBNF)\n\n```text\ndocument = ("mindmap" NEWLINE)? (blank | directive)* node*\n\ndirective = "%%" WS key ":" WS value NEWLINE\nkey = "style" | "theme" | "maxlabelwidth"\n\nnode = heading | bullet\nheading = INDENT? "#"+ SPACE label NEWLINE\nbullet = SPACE* bullet-marker SPACE label NEWLINE\nbullet-marker = "-" | "*" | "+"\n\nlabel = inline-token*\ninline-token = checkbox\n | "**" inline-token* "**"\n | "*" inline-token* "*"\n | "`" code-text "`"\n | "[" inline-token* "]" "(" url ")"\n | plain-text\n\ncheckbox = "[ ]" SPACE | "[x]" SPACE | "[X]" SPACE\n\nINDENT = WS* %% headings may have leading whitespace (ignored)\nSPACE = " " | "\\t"\n```\n\n**Depth rules:**\n- Heading `#` \u2192 depth 0 (root)\n- Heading `##` \u2192 depth 1, `###` \u2192 depth 2, etc.\n- Bullet at `n` leading spaces \u2192 depth = `lastHeadingDepth + 1 + floor(n / 2)`\n\nAuthoritative source: `src/diagrams/mindmap/parser.ts`. If this diverges from the parser, the parser wins \u2014 please open an issue.\n\n---'
2926
3399
  },
2927
3400
  "timeline": {
2928
3401
  "title": "Timeline diagram",
@@ -2991,6 +3464,38 @@ var SYNTAX = {
2991
3464
  "bowtie": {
2992
3465
  "title": "Bowtie Risk Diagram",
2993
3466
  "content": '## 1. Your first diagram\n\nEvery document starts with the `bowtie` keyword, an optional title, then the hazard, the top event, and the two wings:\n\n```\nbowtie\ntopevent "Loss of containment"\nthreat "Corrosion"\n prevent "Inspection programme"\nconsequence "Release to atmosphere"\n mitigate "Gas detection + ESD"\n```\n\n`topevent` declares the single knot at the centre (mandatory, exactly one). Each `threat` starts a left-wing line; each `consequence` ends a right-wing line. A barrier under a `threat` is **preventative** (`prevent`); under a `consequence` it is **mitigative** (`mitigate`). The diagram is laid out symmetrically about the knot \u2014 threats fan in from the left, consequences fan out to the right.\n\nThe DSL is **indentation-structured** and mirrors the CCPS 7-step build methodology: identify the hazard \u2192 the top event \u2192 the threats \u2192 the consequences \u2192 the preventative barriers \u2192 the mitigative barriers \u2192 the escalation factors.\n\nHeader directives (any order):\n\n- `layout: symmetric | compact` \u2014 band model (default `symmetric`).\n- `legend: on | off | bottom | top` \u2014 the auto-derived colour legend (default on).\n\n---\n\n## 2. Hazard and top event\n\n```\nhazard "Working at height"\ntopevent "Person falls from height"\n```\n\n- **`hazard`** \u2014 the operation or material with the potential to cause harm: the *context* the bowtie is about (e.g. "Working at height", "Hydrocarbon under pressure"). Optional; renders as a header box above the knot with a tie-line down to it. At most one.\n- **`topevent`** \u2014 the moment control of the hazard is **lost** (e.g. "Loss of containment", "Person falls from height"). The knot of the bowtie, drawn as a green circle. **Mandatory, exactly one.**\n\nA hazard is a *thing/activity*, not a failure; the top event is the precise moment of *loss of control* \u2014 not a cause, and not yet a consequence.\n\n---\n\n## 3. Threats and consequences\n\n```\nthreat "Guardrail removed for access"\n prevent "Permit-to-work system"\n\nconsequence "Fatality"\n mitigate "Fall-arrest harness + lanyard"\n```\n\n- A **threat** is a credible cause that, *on its own*, could trigger the top event. Each threat is the start of one left-wing line, drawn as an orange box on the left edge.\n- A **consequence** is a credible outcome *of the top event* (not of the threat), drawn as a red box on the right edge.\n\nThreats and consequences may be declared in any interleaved order \u2014 the parser groups all left-wing blocks and all right-wing blocks regardless of sequence. A bowtie needs **at least one of each**: a one-wing diagram is a fault tree (see `faulttree`) or an event tree, not a bowtie.\n\n---\n\n## 4. Barriers (the controls in between)\n\n```\nthreat "Guardrail removed for access"\n prevent "Permit-to-work system"\n prevent "Temporary edge protection"\n prevent "Spotter / banksman"\n```\n\nA **barrier** is a control that interrupts the threat \u2192 top-event path (preventative) or reduces the consequence after the top event (mitigative). Each is a grey box *on the line*. Chains are free length (1..n) \u2014 the wing simply extends.\n\n**Barrier order is declaration order**: the first declared is the **outermost** (closest to the threat/consequence, the first line of defence); the last declared is the **innermost** (closest to the knot). This matches the left-to-right reading of a real bowtie. Each barrier carries `data-order` (0 = outermost) and `data-side` (`prevent` / `mitigate`) for downstream interactivity.\n\nWhen chains differ in length, barriers are **centre-anchored**: the innermost barriers align in a neat column near the knot, and the threat/consequence boxes are ragged by chain depth \u2014 reading as defence-in-depth.\n\n---\n\n## 5. Escalation factors\n\n```\nthreat "Corrosion"\n prevent "UT thickness inspection"\n escalation "Inspection interval too long"\n barrier "Risk-based inspection scheme"\n```\n\nAn **escalation factor** (or degradation factor) is a condition that *degrades a specific barrier\'s effectiveness* \u2014 e.g. "Edge protection not inspected", "Operator fatigue". It attaches to **one** barrier (not to the line) and drops vertically below it as an amber box, joined by a muted "degrades" connector.\n\nAn **escalation-factor barrier** is a control placed on the escalation factor itself \u2014 it protects the barrier from being degraded (e.g. a pre-use inspection regime). It nests one level deeper, under the escalation factor, and renders as a grey box below it.\n\nIndentation binds the nesting: `prevent`/`mitigate` at 2 spaces, `escalation` at 4, `barrier` at 6.\n\n---\n\n## 6. Correct by construction (the barrier rule set)\n\nBefore it draws a single shape, the engine validates the structural half of the CCPS/EI barrier rule set and **refuses to render** on failure \u2014 exactly as `prisma` refuses missing counts:\n\n- **Exactly one top event** \u2014 zero or several is an error.\n- **Every threat has \u2265 1 preventative barrier** \u2014 a bare threat is a Swiss-cheese cartoon, not a bowtie.\n- **Every consequence has \u2265 1 mitigative barrier** \u2014 the mirror rule.\n- **Every escalation factor is attached to a barrier** \u2014 it cannot float on a line or on the top event.\n- **At least one threat and one consequence** \u2014 a one-wing diagram is an FTA or an ETA.\n\nMessages name the offending element and the rule in plain English, e.g. *"Threat \'Corrosion\' has no preventative barrier \u2014 every threat must reach the top event through at least one barrier (CCPS/EI barrier rule). Add a `prevent` line under it."* This is what separates a real bowtie from a doodle. The engine does **not** judge whether a barrier is truly *effective* or *independent* \u2014 that is the analyst\'s qualitative judgement.\n\n---\n\n## 7. Bowtie vs fault tree\n\nA fully-developed bowtie *is* a fault tree glued to an event tree at the top event: the left wing read backwards is the fault tree whose top event is the bowtie\'s knot, and the right wing is the event tree that propagates it into consequences. Schematex keeps them as two engines because their use differs:\n\n- **`bowtie`** is qualitative and symmetric \u2014 the barrier inventory and the at-a-glance defence-in-depth story; no probability arithmetic.\n- **`faulttree`** is quantitative and Boolean \u2014 AND/OR gates, basic-event probabilities, minimal cut sets, a probability rollup.\n\nWhere you want the gate-level detail behind a single threat, draw a separate `faulttree`.\n\n---\n\n## 8. Theming\n\n`default` uses the recognised BowTieXP / bowtiemaster palette \u2014 **orange threats** (left), **grey barriers** on the line, a **green top-event disc** (centre knot), **red consequences** (right), **amber escalation factors** dropping below \u2014 mapped onto Schematex\'s semantic slots so it stays coherent with `prisma` / `pert` / `petri`. `monochrome` reproduces the regulator-print black-and-white look, where element distinction rides on shape/border + position (escalation factors get a dashed border, the knot a doubled ring) rather than colour. `dark` follows Catppuccin Mocha. All strokes/fills come from `BowtieTokens`; every element carries `data-*` (`data-role`, `data-side`, `data-line`, `data-order`, `data-barrier`) so the structure is inspectable downstream.'
3467
+ },
3468
+ "eventtree": {
3469
+ "title": "Event Tree Analysis",
3470
+ "content": '## 1. Your first event tree\n\nEvery document starts with the `eventtree` keyword (alias `eta`), an optional title, then a flat list of declarations:\n\n```\neventtree "Smoke detector demand"\n initiating FIRE "Fire starts" freq: 0.01\n function D "Detector actuates" p: 0.02\n function S "Suppression works" p: 0.05\n outcome s s -> "Controlled"\n outcome s f -> "Damage, contained"\n outcome f * -> "Uncontrolled fire"\n```\n\n- **`initiating ID "label" freq: N`** \u2014 exactly one. The challenge frequency, accepting decimals or scientific notation (`freq: 0.01` or `freq: 1e-4`).\n- **`function ID "label" p: N`** \u2014 one per branch column, **declared left\u2192right in query order**. `p:` is the **failure** probability; the engine derives the success leg as its complement `1 \u2212 p` (you never state both).\n- **`outcome <pattern> -> "end state"`** \u2014 one realised leaf each.\n\n---\n\n## 2. The s / f / * outcome pattern\n\nEach `outcome` row reads left\u2192right over the function columns:\n\n```\noutcome s s s -> "OK" # every function succeeds\noutcome s s f -> "Late release" # C fails on the last query\noutcome s f * -> "Early release"# B fails; C is never queried (pruned)\noutcome f * * -> "Core damage" # A fails; path terminates immediately\n```\n\n- **`s`** \u2014 success leg (upper branch).\n- **`f`** \u2014 failure leg (lower branch).\n- **`*`** \u2014 pruned: the path is not queried here, it runs flat to its leaf.\n\nThis is how an event tree avoids being a full balanced 2\u207F tree: once a function failure makes later questions moot, you write `*` and the sequence terminates early. Two hard rules: a pattern may not be **longer** than the column count, and **once a column is pruned (`*`) every later column must also be `*`** \u2014 a path that has terminated cannot resume querying.\n\n---\n\n## 3. Computed path frequencies & outcomes\n\nThis is the differentiator. With the failure probabilities and `freq`, the engine computes:\n\n- **Each path frequency** = `f\u2080 \xB7 \u220F branch-probabilities` along its `s`/`f` legs (success legs contribute `1 \u2212 p`, failure legs `p`).\n- **Outcome roll-up**: outcomes with the same end-state label are summed across every path that reaches them (every `"Core damage"` leaf adds up).\n- **The dominant sequence** \u2014 the largest-frequency path \u2014 gets the reserved-red accent, the ETA analogue of the fault tree\'s single point of failure.\n\nEvery leaf carries `data-*` (`data-freq`, `data-outcome`) so the computed numbers are inspectable downstream.\n\n---\n\n## 4. Common mistakes\n\n```\n# WRONG \u2014 function with no failure probability\nfunction A "ECCS"\n\n# WRONG \u2014 querying after a pruned column (path already terminated)\noutcome * s -> "bad"\n\n# WRONG \u2014 more tokens than declared columns\nfunction A p: 0.1\noutcome s s -> "ok"\n\n# WRONG \u2014 initiating event with no frequency\ninitiating LOCA "Large LOCA"\n```\n\nEach is rejected with a plain-English message naming the line. State `p:` as a **failure** probability (small), give the initiating event a `freq:`, keep prunes trailing, and you are correct by construction.\n\n---'
3471
+ },
3472
+ "fmea": {
3473
+ "title": "FMEA Worksheet",
3474
+ "content": '## 1. Your first worksheet\n\nEvery document starts with `fmea`, an optional title, optional directives, then a **nested** failure chain. Structure comes from the keyword, not the indent depth:\n\n```\nfmea "Pump DFMEA"\n item "Impeller" fn "Move fluid"\n mode "No flow"\n effect "Process stops" sev: 8\n cause "Impeller wear" occ: 4\n controls detection: "Flow sensor" det: 5\n```\n\n- **`item "name" fn "function"`** \u2014 a part and the function it delivers. `fn` is optional.\n- **`mode "failure mode"`** \u2014 how that function fails. An item may hold several.\n- **`effect "consequence" sev: 1..10`** \u2014 what the failure causes; carries its own **Severity**. A mode may have several (the engine uses the *worst* for every row of that mode).\n- **`cause "root cause" occ: 1..10`** \u2014 why it happens; carries **Occurrence**.\n- **`controls prevention: "\u2026", detection: "\u2026" det: 1..10`** \u2014 current controls and the **Detection** rating they earn. With no control, Detection defaults to **10** (undetectable).\n\nAll three ratings are integers 1\u201310; anything outside that range is rejected.\n\n---\n\n## 2. Header directives\n\n```\ntype: design # design (dfmea) | process (pfmea) | msr\nrank: ap # ap (default) | rpn \u2014 the sort/priority key\nflag: ap >= High # or `rpn > 100` \u2014 highlight rows over a threshold\nnumber: FMEA-2026-014 # free metadata: number/team/author/date/revision/dept/process/product\n```\n\n`rank` chooses how rows are prioritised; `flag` highlights the rows that breach a threshold (`ap >= High` / `ap == High`, or `rpn > 100` / `rpn >= 120`).\n\n---\n\n## 3. Computed RPN & Action Priority\n\nThis is the differentiator. From the nested AST the engine:\n\n1. **Flattens** to one row per (item, mode, cause); the mode\'s worst effect severity governs every row of that mode.\n2. **RPN = S \xD7 O \xD7 D** (1\u20131000).\n3. **Action Priority** by the AIAG-VDA band structure \u2014 **Severity is the primary axis**, Occurrence second, Detection third. S = 9\u201310 (safety/regulatory) is High for *every* O and D; mid-severity degrades High\u2192Medium\u2192Low as Occurrence falls.\n4. **Sorts** by the chosen key. For `ap`: High > Medium > Low, tie-broken by Severity then RPN \u2014 *never* RPN alone, which would re-introduce the bug AP was designed to fix.\n5. **Flags** rows over the threshold.\n\nEach row carries `data-rpn` and `data-ap` so the computed priority is inspectable.\n\n---\n\n## 4. After-action follow-up\n\nRecord corrective actions and the revised ratings to show the before/after delta:\n\n```\nfmea "Brake DFMEA"\n item "MC"\n mode "Seal leak"\n effect "Loss of braking" sev: 9\n cause "Degradation" occ: 3 det: 4\n action "Seal leak" / "Degradation"\n do: "Upgrade seal to EPDM" owner: "J. Lee" target: 2026-Q3\n revised sev: 9 occ: 1 det: 4\n```\n\n`action "Mode" / "Cause"` targets a chain by its quoted mode and (optional) cause; `do:` is the recommendation; `revised sev/occ/det` recomputes the after-action RPN and AP so the engine reports the risk reduction.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 rating out of the 1..10 band\neffect "Leak" sev: 11\n\n# WRONG \u2014 mode before any item\nmode "orphan mode"\n\n# WRONG \u2014 effect before any mode\nitem "x"\n effect "e" sev: 3\n```\n\nRatings must be integers 1\u201310; the failure chain must nest `item \u2192 mode \u2192 effect/cause`; out-of-order keywords are rejected by name. Detection silently defaults to 10 if no control rates it \u2014 state `det:` (on the cause or its `controls`) when you have a detection control.\n\n---'
3475
+ },
3476
+ "causalloop": {
3477
+ "title": "Causal Loop Diagram",
3478
+ "content": '## 1. Your first causal loop\n\nStart with the `causalloop` keyword (alias `cld`), an optional title, then **signed links**. Variables are auto-created from the links \u2014 you rarely declare them:\n\n```\ncausalloop "Adoption model"\n"Adoption rate" -> Adopters : +\nAdopters -> "Adoption rate" : +\nloop R1 "Word of mouth"\n```\n\nA link is `SOURCE -> TARGET : POLARITY`. Multi-word variable names are quoted (`"Adoption rate"`); single words need no quotes (`Adopters`). At least one link is required.\n\n---\n\n## 2. Link polarity\n\nPolarity is the sign of the causal influence and is **mandatory** on every link:\n\n```\nA -> B : + # same direction (more A \u2192 more B)\nB -> C : - # opposite direction (more B \u2192 less C)\nA -> B : s # alias for + (same)\nB -> C : o # alias for \u2212 (opposite)\nC -> D : same # alias for +\nD -> E : opposite # alias for \u2212\nA -> B + # the colon is optional\n```\n\n`+` / `s` / `same` mean *same direction*; `\u2212` / `o` / `opposite` mean *opposite*. A link with no polarity is rejected.\n\n---\n\n## 3. Delays and explicit variables\n\n```\n"Training quality" -> "Salesperson skills" : + delay # marked delay (\u2225 hash on the arrow)\nA -> B : + ~delay # the ~delay form also works\nvar "Adoption rate" # pin a variable so it isn\'t auto-created\nloop R1 "Word of mouth" # name/annotate a loop\n```\n\n- **`delay`** / **`~delay`** marks a link as delayed (the system-dynamics hash mark).\n- **`var "name"`** declares a variable explicitly (fixes its label; not auto-created).\n- **`loop ID "phrase"`** attaches a human-readable name to a loop the engine detects.\n\n---\n\n## 4. Computed feedback loops\n\nThis is the differentiator. The engine:\n\n1. Builds the **signed directed graph** (nodes = variables, edges = signed links).\n2. Enumerates **every elementary feedback loop** (simple directed cycle) with Johnson\'s algorithm \u2014 deterministically, in declaration order.\n3. **Classifies** each loop by counting negative links:\n - **even** count (including 0) \u2192 **R** (reinforcing); product of signs = +1\n - **odd** count \u2192 **B** (balancing); product of signs = \u22121\n\nThis is exactly Sterman\'s even/odd rule. Loops are numbered in detection order by kind (R1, B1, R2\u2026) and drawn with their `R`/`B` rotation glyph at the loop centre. Each loop carries `data-loop` and `data-kind`.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 link with no polarity\nA -> B\n\n# WRONG \u2014 a diagram with no links at all\ncld\n```\n\nEvery link needs a polarity (`: +` or `: -`); a CLD with no links is rejected. Remember polarity is about *direction of change*, not desirability \u2014 a link from "deaths" to "population" is still `-` (more deaths \u2192 less population) even though deaths are bad.\n\n---'
3479
+ },
3480
+ "markov": {
3481
+ "title": "Markov Chain",
3482
+ "content": '## 1. Your first chain\n\nStart with the `markov` keyword (alias `markovchain`), an optional title, then **probability-weighted transitions**. States are auto-created from the first arc that mentions them:\n\n```\nmarkov "Weather"\n Sunny -> Sunny : 0.9\n Sunny -> Rainy : 0.1\n Rainy -> Sunny : 0.5\n Rainy -> Rainy : 0.5\n```\n\nA transition is `FROM -> TO : PROBABILITY`. A self-loop (`Sunny -> Sunny`) is the probability of staying put. Every probability must be in `[0, 1]`, and **the probabilities leaving each state must sum to 1**.\n\n---\n\n## 2. Declaring states\n\nYou can declare states explicitly to fix label, order, or mark them absorbing:\n\n```\nmarkov "Gambler\'s ruin"\n state Broke "$0" absorbing\n state Rich "$4" absorbing\n state S1\n state S2\n Broke -> Broke : 1\n Rich -> Rich : 1\n S1 -> Broke : 0.5\n S1 -> S2 : 0.5\n S2 -> S1 : 0.5\n S2 -> Rich : 0.5\n```\n\n`state ID "label" absorbing` \u2014 the label is optional; `absorbing` asserts the state is a sink (a self-loop of probability 1). The engine cross-checks the assertion against the matrix.\n\n---\n\n## 3. Directives\n\n```\nlayout: layered # layout mode\nnormalize: true # scale each row to sum to 1 instead of hard-erroring\nanalysis: classify, absorbing # what to compute: classify | absorbing | stationary\n```\n\n- **`normalize: true`** rescales each row to sum to 1 (handy for unnormalised weights like `A -> B : 1`, `A -> C : 1`). Without it, a row that does not sum to 1 is a hard error.\n- **`analysis:`** selects which computations to run and surface.\n\n---\n\n## 4. The computed answer\n\nThis is the differentiator. Three hand-written, dependency-free linear-algebra passes:\n\n1. **Matrix assembly + validation** \u2014 build P and enforce the row-sum policy (hard error, or rescale under `normalize`).\n2. **State classification** \u2014 Tarjan SCC \u2192 communicating classes; a class is **recurrent** iff closed, **transient** otherwise; a closed singleton with a probability-1 self-loop is **absorbing**.\n3. **The quantitative answer**:\n - **Stationary distribution** \u03C0 (\u03C0P = \u03C0, \u03A3\u03C0 = 1) by power iteration with an exact Gaussian-elimination fallback for periodic chains.\n - For **absorbing chains**: the fundamental matrix N = (I\u2212Q)\u207B\xB9, absorption probabilities B = N\xB7R, and expected steps to absorption t = N\xB71.\n\nTransient states, recurrent classes, and absorbing states are rendered distinctly; computed values are carried in `data-*`.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 probability outside [0,1]\nA -> B : 1.5\n\n# WRONG \u2014 transition with no probability\nA -> B\n\n# WRONG \u2014 rows that don\'t sum to 1 (without `normalize: true`)\nA -> B : 0.3\nA -> C : 0.3\n```\n\nEvery transition needs a probability in `[0, 1]`; each state\'s outgoing probabilities must sum to 1, or you must set `normalize: true`. An empty chain (no transitions) is rejected.\n\n---'
3483
+ },
3484
+ "gitgraph": {
3485
+ "title": "Git Graph",
3486
+ "content": '## 1. Your first git graph\n\nStart with the `gitGraph` keyword (case-insensitive; a trailing colon is allowed), then one operation per line. The first commit lands on the main branch:\n\n```\ngitGraph\n commit\n branch develop\n checkout develop\n commit id: "feature work"\n checkout main\n merge develop tag: "v1.0"\n```\n\nEvery operation runs against the **currently checked-out branch**. `commit` appends to it; `branch` creates a new line from the current commit; `checkout` (alias `switch`) moves the cursor; `merge` brings another branch into the current one.\n\n---\n\n## 2. Operations\n\n```\ncommit # plain commit on the current branch\ncommit id: "init" # give the commit an explicit id\ncommit tag: "v0.1" # attach a release tag\ncommit type: HIGHLIGHT # NORMAL (default) | HIGHLIGHT | REVERSE\nbranch develop # fork a branch from the current commit\nbranch hotfix order: 3 # pin its lane order\ncheckout develop # switch the cursor (alias: switch)\nmerge develop tag: "v1.0" # merge a branch into the current one\ncherry-pick id: "abc" # copy a commit onto the current branch\n```\n\n- **`commit`** takes optional `id:`, `tag:`, and `type:` (`NORMAL` / `HIGHLIGHT` / `REVERSE`).\n- **`branch NAME`** takes an optional `order:` to control lane placement.\n- **`checkout` / `switch`** are interchangeable.\n- **`cherry-pick id:`** copies a commit; an optional `parent:` disambiguates a merge commit.\n\n---\n\n## 3. Orientation\n\nThe graph defaults to left-to-right. Set the direction inline on the header:\n\n```\ngitGraph TB:\n commit\n branch feature\n checkout feature\n commit\n checkout main\n merge feature\n```\n\n`LR` (default), `TB`, and `BT` are accepted. Config can also be supplied via a leading `%%{init: {\'gitGraph\': {...}}}%%` directive or a YAML frontmatter `config:` block for Mermaid compatibility (`mainBranchName`, `showCommitLabel`, `rotateCommitLabel`).\n\n---\n\n## 4. How the engine lays it out\n\nMermaid-compatibility is the differentiator, but the layout is the work:\n\n- The operation list is **replayed in order** to build the commit DAG and assign each commit to its branch lane.\n- Branch lanes are ordered by appearance (overridable with `order:`); merge connectors are routed from the merged branch\'s tip to the new merge commit.\n- Commit nodes are styled by `type:` \u2014 a HIGHLIGHT commit is emphasised, a REVERSE commit marked \u2014 and tags render as flags. Cherry-picks draw a dashed copy edge to the source.\n\nEvery commit carries `data-*` (branch, id, type) for downstream interaction.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 no gitGraph header\ncommit\ncommit\n\n# WRONG \u2014 unknown operation\ngitGraph\n rebase main\n\n# WRONG \u2014 unknown commit type\ngitGraph\n commit type: SQUASH\n```\n\nThe document must start with `gitGraph`; only `commit` / `branch` / `checkout` / `switch` / `merge` / `cherry-pick` are valid operations; `type:` must be `NORMAL`, `HIGHLIGHT`, or `REVERSE`. `%%` starts a comment, matching Mermaid.\n\n---'
3487
+ },
3488
+ "epc": {
3489
+ "title": "EPC (Event-driven Process Chain)",
3490
+ "content": '## 1. Your first EPC\n\nStart with the `epc` keyword, an optional title, then declare **nodes by id** and **wire them with arrows**:\n\n```\nepc "Order fulfilment"\n event E1 "Order received"\n function F1 "Check credit"\n event E2 "Credit OK"\n E1 -> F1 -> E2\n```\n\nNodes carry an id and an optional quoted label; arrows reference ids. The canonical form declares connectors as nodes (`xor X1`) and wires everything by id \u2014 closest to how ARIS stores an EPC.\n\n---\n\n## 2. Node kinds\n\n```\nevent E1 "Order received" # passive state (rounded hexagon)\nfunction F1 "Check credit" # active task (rounded rectangle); alias: func\nfunc F2 "Send invoice" # `func` is shorthand for `function`\nand A1 # AND connector (\u2227)\nor O1 # OR connector (\u2228)\nxor X1 # exclusive-OR connector (\xD7)\n```\n\nA connector fans either way (split or join) depending on its incoming/outgoing arcs, so the same glyph serves both. Connector labels are optional.\n\n---\n\n## 3. Wiring control flow\n\n```\nE1 -> F1 -> X1 # a chain is sugar for the pairwise arcs E1\u2192F1, F1\u2192X1\nX1 -> E2 # connector split into two branches\nX1 -> E3\nF2 -> E2 : sent # a single arc may carry a \': label\'\n```\n\n- A `->` **chain** expands to pairwise edges.\n- A trailing **`: label`** annotates a single arc.\n- An arrow endpoint not yet declared is **auto-created** (and flagged by the validator), so you can sketch fast and clean up later.\n\n```\nepc "Procure-to-pay"\n layout: tb\n event E1 "Need identified"\n function F1 "Create PO"\n and A1\n function F2 "Notify supplier"\n function F3 "Update budget"\n E1 -> F1 -> A1\n A1 -> F2\n A1 -> F3\n```\n\n`layout: tb` lays the chain top-to-bottom (default is also vertical-friendly).\n\n---\n\n## 4. Computed well-formedness\n\nThis is the differentiator. The engine validates (flags, doesn\'t throw):\n\n1. **Bipartite alternation** \u2014 events and functions strictly alternate along any path; connectors don\'t break it.\n2. **Start/end must be events** \u2014 a function may not be a start or end node.\n3. **Signature rule** \u2014 an *event must not be the source of an OR/XOR split* (a passive event cannot decide); an AND-split after an event is allowed.\n4. **Split/join balancing** \u2014 a split of type T should be closed by a join of type T; mismatches are warnings (real EPCs are sometimes unbalanced).\n5. **Single-in / single-out** per event and function \u2014 connectors carry the multiplicity.\n6. **Reachability** \u2014 every node reachable from a start and reaching an end.\n\nOffending nodes are highlighted with `data-*` flags; the diagram still renders.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 invalid id (must start with a letter)\nevent 9bad\n\n# AVOID \u2014 redeclaring a node (the first wins, a warning is raised)\nevent E1 "first"\nevent E1 "second"\n```\n\nIds must start with a letter; a redeclared node keeps the first definition and warns. Because event\u2192XOR/OR splits are flagged, route a decision through a **function** that produces the decision, then split.\n\n---'
3491
+ },
3492
+ "idef0": {
3493
+ "title": "IDEF0 Function Model",
3494
+ "content": '## 1. Your first IDEF0 diagram\n\nStart with the `idef0` keyword, an optional title, an optional `node` (the diagram\'s node number), then **function boxes** and their **ICOM arrows**:\n\n```\nidef0 "Fill order"\nfunction A1 "Receive order"\ninput A1 "Customer request"\ncontrol A1 "Order policy"\nmechanism A1 "Order clerk"\noutput A1 "Confirmed order"\n```\n\n`function ID "name"` declares a box (in declaration order). `node A0` sets the parent node number used to derive child node numbers (A0 \u2192 A1..An). FIPS guidance is 3\u20136 boxes per diagram; outside that range the engine warns.\n\n---\n\n## 2. ICOM boundary arrows\n\nEach keyword pins an arrow to a specific side of a box, and that *is* its role:\n\n```\ninput A1 "Sales orders" # enters the LEFT edge\ncontrol A1 "Production schedule" # enters the TOP edge (governs the activity)\noutput A1 "Product" # leaves the RIGHT edge\nmechanism A1 "CNC machines" # enters the BOTTOM edge (the resource)\n```\n\n`input`, `control`, and `mechanism` route from the diagram frame **into** the box; `output` routes from the box **out** to the frame. The engine codes these boundary arrows down each edge (I1, I2 / C1 / O1 / M1).\n\n---\n\n## 3. Flow arrows between boxes\n\nA `->` arrow connects two boxes; by default it lands on the target\'s **input**, but you can name the target\'s ICOM side:\n\n```\nA1 -> A2 "Work plan" # box\u2192box; defaults to A2\'s input\nA2 -> A3.control "Parts spec" # land on A3\'s control (top) edge\ninput A2 "Raw material" (tunnel) # (tunnel) hides the arrow at this level\n```\n\n- `target.control` / `target.input` / `target.mechanism` picks the landing side.\n- A flow **cannot land on the target\'s `.output`** \u2014 an output leaves a box, it does not enter one.\n- `(tunnel)` marks a tunnelled arrow (suppressed on the parent/child diagram per FIPS).\n\n---\n\n## 4. Computed structural enforcement\n\nThis is the differentiator \u2014 what makes the model correct where a drawing tool is not:\n\n1. **ICOM placement enforcement** \u2014 the role is resolved against the box\'s geometry side; a malformed role, or a flow asked to *enter* a box via `.output`, is rejected.\n2. **Reference resolution** \u2014 every box id named by an arrow must be declared.\n3. **Decomposition numbering** \u2014 boxes get contiguous box numbers 1..n (lower-right corner) and node numbers (A0 \u2192 A1..An); explicit `#N` numbers are checked for contiguity, range, and duplicates.\n4. **Boundary coding** \u2014 boundary arrows are coded I1/C1/O1/M1 down each edge.\n5. **Box-count guideline** \u2014 fewer than 3 or more than 6 boxes raises a FIPS-183 warning.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 no idef0 header\nfunction A1 "x"\n\n# WRONG \u2014 a flow landing on the target\'s output\nA1 -> A2.output "bad"\n\n# WRONG \u2014 an unknown ICOM side word\nA1 -> A2.sideways "bad"\n```\n\nThe document must start with `idef0`; arrows may target `.input` / `.control` / `.mechanism` only; every referenced box id must be declared. Because the keyword encodes the side, you cannot accidentally draw a control as an input \u2014 the standard is enforced, not suggested.\n\n---'
3495
+ },
3496
+ "threatmodel": {
3497
+ "title": "Threat Model (STRIDE DFD)",
3498
+ "content": '## 1. Your first threat model\n\nStart with the `threatmodel` keyword (alias `stride`), an optional title, then **declare elements** and **wire flows**:\n\n```\nthreatmodel "Login flow"\nexternal: User\nprocess 1.1: Web Server\ndatastore D1: User DB\nUser -> 1.1 : "Login request"\n1.1 -> D1 : Lookup\n```\n\nEach element is `kind: ID: Label` (or `kind: Label`, where the id is slugged from the label \u2014 `external: Mobile App` becomes id `Mobile_App`). A flow is `SOURCE -> TARGET : label`, and the **label is mandatory** (it names the data crossing).\n\n---\n\n## 2. Element kinds and flows\n\n```\nexternal: User # external entity (the attacker\'s side)\nprocess 1.1: Web Server # a process / service\ndatastore D1: User DB # a data store\ndatastore D2: Audit log # a log/audit store (gets conditional Repudiation)\nUser -> 1.1 : "HTTPS Request" # directed flow, quoted or bare label\n1.1 <-> D1 : Read/Write # <-> expands into two directed flows\n```\n\n- Process ids are often dotted DFD numbers (`1.1`, `2.3`); external/store ids are usually short slugs (`User`, `D1`).\n- A `<->` flow expands into **two** directed flows.\n- A store whose name/id matches `log|audit|journal` (or carries an explicit hint) is treated as a log store.\n\n**Flow rules** the engine enforces: no store\u2192store flows (data stores are passive), no external\u2192external flows, and every endpoint must be a declared element.\n\n---\n\n## 3. Trust boundaries\n\n```\nboundary "Internet" { User }\nboundary "DMZ" { 1.1 }\nboundary "Internal" { D1, D2 }\n```\n\n`boundary "name" { id, id, \u2026 }` groups elements into a trust zone. An element may belong to **at most one** boundary; members must be declared. Elements in no boundary share an implicit untrusted zone.\n\n---\n\n## 4. Computed STRIDE analysis\n\nThis is the differentiator. The engine applies the **STRIDE-per-element** mapping:\n\n| DFD element | Threats applied |\n|-----------------|------------------------|\n| External entity | S, R |\n| Process | S, T, R, I, D, E |\n| Data store | T, I, D (+ R if log) |\n| Data flow | T, I, D |\n\n- The data-store **Repudiation** is conditional \u2014 added for log / audit / journal stores (the Shostack green "?").\n- **Boundary crossing**: a flow whose endpoints sit in different trust zones is flagged, because that is where Spoofing / Tampering / Information-disclosure concentrate. Two elements in the same (or implicit) zone do not cross.\n\nEach element and flow carries its applicable STRIDE categories in `data-*` so the analysis is inspectable.\n\n---\n\n## 5. Common mistakes\n\n```\n# WRONG \u2014 flow with no label\nUser -> 1.1\n\n# WRONG \u2014 store to store (data stores are passive)\nD1 -> D2 : x\n\n# WRONG \u2014 external to external\nA -> B : x\n\n# WRONG \u2014 unknown flow endpoint\nP -> Ghost : x\n\n# WRONG \u2014 an element in two boundaries\nboundary "A" { P }\nboundary "B" { P }\n```\n\nEvery flow needs a label; stores and externals cannot be flow partners with their own kind; endpoints must be declared; an element belongs to at most one trust boundary. Duplicate ids are rejected.\n\n---'
2994
3499
  }
2995
3500
  };
2996
3501
 
@@ -3556,6 +4061,224 @@ var PROFILES = {
3556
4061
  "'needs at least one threat/consequence' \u2014 a one-wing diagram is a fault tree or event tree; add the missing wing.",
3557
4062
  "'exactly one top event' \u2014 keep a single `topevent` line."
3558
4063
  ]
4064
+ },
4065
+ eventtree: {
4066
+ type: "eventtree",
4067
+ header: 'eventtree "Title"',
4068
+ mode: "initiating event + ordered safety functions, then outcome rows with s/f/* branch patterns",
4069
+ forms: [
4070
+ 'initiating LOCA "Large LOCA" freq: 1e-4',
4071
+ 'function A "ECCS injects" p: 0.001',
4072
+ 'function B "Containment spray" p: 0.01',
4073
+ 'outcome s s -> "OK"',
4074
+ 'outcome s f -> "Late release"',
4075
+ 'outcome f * -> "Core damage"'
4076
+ ],
4077
+ prefer: [
4078
+ "Keyword `eventtree` (or `eta`). One `initiating` line with a `freq:`; then `function` lines in left-to-right order, each with its failure probability `p:` (success is auto-complemented to 1\u2212p).",
4079
+ 'Each `outcome` row gives a `s`/`f`/`*` pattern (one symbol per function) then `-> "label"`. `*` = path already terminated (pruned) \u2014 runs flat to its leaf, no further branching.',
4080
+ "Let the engine compute path frequencies (f_initiating \xD7 \u03A0 branch-probs) \u2014 don't hand-write them."
4081
+ ],
4082
+ avoid: [
4083
+ "Don't try to make a balanced 2\u207F tree \u2014 prune with `*` where a sequence ends early.",
4084
+ "Don't put probabilities on outcome rows; `p:` belongs on `function` lines.",
4085
+ "Don't reverse the success/failure convention \u2014 success branches up, failure down."
4086
+ ],
4087
+ repair: [
4088
+ "'outcome pattern length' \u2014 give exactly one s/f/* per declared function.",
4089
+ "'no initiating event' \u2014 add one `initiating <id> \"\u2026\" freq: \u2026` line.",
4090
+ "'once pruned stays pruned' \u2014 after a `*` in a pattern, the rest must also be `*`."
4091
+ ]
4092
+ },
4093
+ fmea: {
4094
+ type: "fmea",
4095
+ header: 'fmea "Title"',
4096
+ mode: "indentation-structured worksheet: item \u2192 failure mode \u2192 effect/cause/controls with S/O/D scores",
4097
+ forms: [
4098
+ 'fmea "Brake System DFMEA"',
4099
+ ' item "Master cylinder" fn "Generate pressure"',
4100
+ ' mode "Internal seal leak"',
4101
+ ' effect "Loss of braking" sev: 9',
4102
+ ' cause "Seal degradation" occ: 3',
4103
+ ' controls detection: "Bench test" det: 4'
4104
+ ],
4105
+ prefer: [
4106
+ "Keyword `fmea`. Nest `item` \u2192 `mode` \u2192 `effect`/`cause` \u2192 `controls`. Put Severity on the effect (`sev:`), Occurrence on the cause (`occ:`), Detection on the controls (`det:`) \u2014 each 1\u201310.",
4107
+ "Let the engine compute RPN = S\xD7O\xD7D and the Action Priority (High/Medium/Low) and rank the sheet \u2014 don't hand-write RPN.",
4108
+ "Optional `rank: ap|rpn` and `flag: ap >= High` / `flag: rpn > 100` to control sorting/highlighting."
4109
+ ],
4110
+ avoid: [
4111
+ "Don't write the RPN or AP yourself \u2014 they are computed.",
4112
+ "Don't use scores outside 1\u201310.",
4113
+ "Don't put `occ:` on an effect or `sev:` on a cause \u2014 they bind to the wrong column."
4114
+ ],
4115
+ repair: [
4116
+ "'score out of range' \u2014 keep S/O/D in 1\u201310.",
4117
+ "'mode has no effect/cause' \u2014 every failure mode needs at least one effect and one cause."
4118
+ ]
4119
+ },
4120
+ causalloop: {
4121
+ type: "causalloop",
4122
+ header: 'causalloop "Title"',
4123
+ mode: "signed causal links between variables; engine detects R/B loops",
4124
+ forms: [
4125
+ 'causalloop "Adoption model"',
4126
+ '"Adoption rate" -> Adopters : +',
4127
+ 'Adopters -> "Adoption rate" : +',
4128
+ '"Adoption rate" -> "Potential adopters" : -',
4129
+ 'loop R1 "Word of mouth"',
4130
+ 'loop B1 "Market saturation"'
4131
+ ],
4132
+ prefer: [
4133
+ "Keyword `causalloop` (or `cld`). Write `A -> B : +` or `A -> B : -` (you may use `s`/`o` aliases). Multi-word variables go in quotes.",
4134
+ 'Let the engine classify loops R (reinforcing) / B (balancing) by counting negative links; optionally name them with `loop R1 "\u2026"`.',
4135
+ "Put the `+`/`\u2212` polarity at the end of each link."
4136
+ ],
4137
+ avoid: [
4138
+ "Don't draw boxes around variables \u2014 CLD variables are boxless text.",
4139
+ "Don't omit polarity \u2014 every causal link is signed."
4140
+ ],
4141
+ repair: [
4142
+ "'link needs a polarity' \u2014 append `: +` or `: -`.",
4143
+ "'unknown variable' \u2014 variables are created on first use; check quoting/spelling."
4144
+ ]
4145
+ },
4146
+ markov: {
4147
+ type: "markov",
4148
+ header: 'markov "Title"',
4149
+ mode: "probability-labelled state transitions; engine computes stationary distribution + classification",
4150
+ forms: [
4151
+ 'markov "Weather"',
4152
+ "Sunny -> Sunny : 0.9",
4153
+ "Sunny -> Rainy : 0.1",
4154
+ "Rainy -> Sunny : 0.5",
4155
+ "Rainy -> Rainy : 0.5"
4156
+ ],
4157
+ prefer: [
4158
+ "Keyword `markov` (or `markovchain`). Write `S1 -> S2 : 0.3` transitions; each state's outgoing probabilities must sum to 1.",
4159
+ "Mark absorbing states by a 1.0 self-loop (or the `absorbing` keyword); the engine computes the stationary distribution and classifies states.",
4160
+ "For an absorbing chain add `analysis: classify, absorbing` to get absorption probabilities + expected steps."
4161
+ ],
4162
+ avoid: [
4163
+ "Don't let a state's out-probabilities sum to \u2260 1 (the engine errors); use `normalize: true` only if you intend rescaling.",
4164
+ "Don't hand-compute the stationary distribution \u2014 it is derived."
4165
+ ],
4166
+ repair: [
4167
+ "'row does not sum to 1' \u2014 fix the probabilities or add `normalize: true`.",
4168
+ "'probability out of range' \u2014 keep each on (0,1]."
4169
+ ]
4170
+ },
4171
+ gitgraph: {
4172
+ type: "gitgraph",
4173
+ header: "gitGraph",
4174
+ mode: "Mermaid-compatible ordered git operations (commit/branch/checkout/merge)",
4175
+ forms: [
4176
+ "gitGraph",
4177
+ ' commit id: "init"',
4178
+ " branch develop",
4179
+ " checkout develop",
4180
+ ' commit tag: "v0.1"',
4181
+ " checkout main",
4182
+ " merge develop"
4183
+ ],
4184
+ prefer: [
4185
+ 'Header `gitGraph` (Mermaid syntax). Operations in order: `commit`, `branch <name>`, `checkout <name>`, `merge <name>`, `cherry-pick id: "\u2026"`.',
4186
+ 'Annotate commits with `id: "\u2026"`, `tag: "\u2026"`, `type: HIGHLIGHT|REVERSE|NORMAL`.',
4187
+ "`checkout` (or `switch`) before committing onto a branch; `merge <name>` merges that branch into the current one."
4188
+ ],
4189
+ avoid: [
4190
+ "Don't `merge` or `checkout` a branch you never created with `branch`.",
4191
+ "Don't merge a branch into itself."
4192
+ ],
4193
+ repair: [
4194
+ "'unknown branch' \u2014 add a `branch <name>` before `checkout`/`merge`.",
4195
+ "'cannot merge current branch' \u2014 checkout the target lane first."
4196
+ ]
4197
+ },
4198
+ epc: {
4199
+ type: "epc",
4200
+ header: 'epc "Title"',
4201
+ mode: "alternating events/functions joined by AND/OR/XOR connectors; alternation validated",
4202
+ forms: [
4203
+ 'epc "Order fulfilment"',
4204
+ ' event E1 "Order received"',
4205
+ ' function F1 "Check credit"',
4206
+ " xor X1",
4207
+ ' event E2 "Credit OK"',
4208
+ ' event E3 "Credit rejected"',
4209
+ " E1 -> F1 -> X1",
4210
+ " X1 -> E2",
4211
+ " X1 -> E3"
4212
+ ],
4213
+ prefer: [
4214
+ "Keyword `epc`. Declare `event`, `function`, and connectors `and`/`or`/`xor` with ids; then the control flow `A -> B -> C`.",
4215
+ "Strictly alternate events and functions (a connector may sit between them); start and end with events.",
4216
+ "Use a connector node to split/join \u2014 e.g. a `xor` after a function fans out to alternative events."
4217
+ ],
4218
+ avoid: [
4219
+ "Don't make an event the source of an OR/XOR split (only functions may decide) \u2014 the engine rejects it.",
4220
+ "Don't connect event\u2192event or function\u2192function directly."
4221
+ ],
4222
+ repair: [
4223
+ "'event cannot be an OR/XOR split source' \u2014 put a function (decision) before the connector.",
4224
+ "'alternation violated' \u2014 insert the missing event/function between two same-kind nodes."
4225
+ ]
4226
+ },
4227
+ idef0: {
4228
+ type: "idef0",
4229
+ header: 'idef0 "Title"',
4230
+ mode: "function boxes + positional ICOM arrows (input-left, control-top, output-right, mechanism-bottom)",
4231
+ forms: [
4232
+ 'idef0 "Manufacture product"',
4233
+ ' function A1 "Plan production"',
4234
+ ' function A2 "Make parts"',
4235
+ ' input A1 "Sales orders"',
4236
+ ' control A1 "Production schedule"',
4237
+ ' A1 -> A2 "Work plan"',
4238
+ ' mechanism A2 "CNC machines"',
4239
+ ' output A2 "Product"'
4240
+ ],
4241
+ prefer: [
4242
+ "Keyword `idef0`. Declare `function` boxes; attach boundary arrows with `input`/`control`/`output`/`mechanism`.",
4243
+ 'Box\u2192box flow `A1 -> A2 "label"` lands on A2\'s input by default; use `A1 -> A2.control "\u2026"` to land on another ICOM side.',
4244
+ "Remember the ICOM rule: Inputs enter left, Controls enter top, Outputs exit right, Mechanisms enter bottom \u2014 the engine enforces it."
4245
+ ],
4246
+ avoid: [
4247
+ "Don't route an output anywhere but the right edge, or a control anywhere but the top \u2014 the engine rejects it.",
4248
+ "Don't hand-number the boxes \u2014 node numbers are assigned."
4249
+ ],
4250
+ repair: [
4251
+ "'output must exit the right edge' \u2014 declare it with `output` / land flows on the correct side.",
4252
+ "'unknown box' \u2014 declare the `function` before referencing it."
4253
+ ]
4254
+ },
4255
+ threatmodel: {
4256
+ type: "threatmodel",
4257
+ header: 'threatmodel "Title"',
4258
+ mode: "DFD elements + data flows + trust boundaries; engine maps STRIDE + flags boundary crossings",
4259
+ forms: [
4260
+ 'threatmodel "Web App \u2014 STRIDE"',
4261
+ "external: User",
4262
+ "process 1.1: Web Server",
4263
+ "datastore D1: User DB",
4264
+ "User -> 1.1 : HTTPS Request",
4265
+ "1.1 -> D1 : Lookup",
4266
+ 'boundary "Internet" { User }',
4267
+ 'boundary "DMZ" { 1.1 }'
4268
+ ],
4269
+ prefer: [
4270
+ 'Keyword `threatmodel` (or `stride`). Declare `external:`, `process N:`, `datastore D:`; connect with `A -> B : "label"` flows; group ids into `boundary "name" { \u2026 }` trust zones.',
4271
+ "Let the engine annotate each element's STRIDE letters and flag flows that cross a trust boundary \u2014 don't list threats by hand.",
4272
+ "Name audit/log stores with `log`/`audit`/`journal` so they pick up the conditional Repudiation threat."
4273
+ ],
4274
+ avoid: [
4275
+ "Don't connect store\u2192store or external\u2192external (not valid DFD edges).",
4276
+ "Don't leave data flows unlabelled."
4277
+ ],
4278
+ repair: [
4279
+ "'unknown element' \u2014 declare the external/process/datastore before referencing it.",
4280
+ "'flow needs a label' \u2014 add `: \"\u2026\"` to the flow."
4281
+ ]
3559
4282
  }
3560
4283
  };
3561
4284
  function getGenerationProfile(type) {
@@ -3639,7 +4362,7 @@ function getExamples(type, opts = {}) {
3639
4362
  function validateDsl(type, dsl) {
3640
4363
  const resolvedType = type ? resolveDiagramType(type) : void 0;
3641
4364
  const config = type ? { type: resolvedType ?? type } : void 0;
3642
- const result = chunk3YRYBTLG_cjs.parseResult(dsl, config);
4365
+ const result = chunk3CSST5GZ_cjs.parseResult(dsl, config);
3643
4366
  if (result.ok) {
3644
4367
  return {
3645
4368
  ok: true,
@@ -3665,7 +4388,7 @@ function renderDsl(type, dsl, options = {}) {
3665
4388
  ...options,
3666
4389
  ...type ? { type: resolvedType ?? type } : {}
3667
4390
  };
3668
- const result = chunk3YRYBTLG_cjs.renderResult(dsl, config);
4391
+ const result = chunk3CSST5GZ_cjs.renderResult(dsl, config);
3669
4392
  if (result.ok) {
3670
4393
  return {
3671
4394
  ok: true,
@@ -3718,5 +4441,5 @@ exports.listDiagrams = listDiagrams;
3718
4441
  exports.renderDsl = renderDsl;
3719
4442
  exports.resolveDiagramType = resolveDiagramType;
3720
4443
  exports.validateDsl = validateDsl;
3721
- //# sourceMappingURL=chunk-DZGA25O5.cjs.map
3722
- //# sourceMappingURL=chunk-DZGA25O5.cjs.map
4444
+ //# sourceMappingURL=chunk-RKN6QJ7K.cjs.map
4445
+ //# sourceMappingURL=chunk-RKN6QJ7K.cjs.map