schematex 0.5.0 → 0.6.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 (180) hide show
  1. package/README.md +89 -7
  2. package/dist/ai/ai-sdk.cjs +32 -26
  3. package/dist/ai/ai-sdk.cjs.map +1 -1
  4. package/dist/ai/ai-sdk.d.cts +4 -3
  5. package/dist/ai/ai-sdk.d.ts +4 -3
  6. package/dist/ai/ai-sdk.js +28 -22
  7. package/dist/ai/ai-sdk.js.map +1 -1
  8. package/dist/ai/index.cjs +37 -25
  9. package/dist/ai/index.d.cts +20 -153
  10. package/dist/ai/index.d.ts +20 -153
  11. package/dist/ai/index.js +17 -17
  12. package/dist/{api-C5UcmT7n.d.cts → api-XWHHAhQI.d.ts} +12 -2
  13. package/dist/{api-C5UcmT7n.d.ts → api-qVDutqXH.d.cts} +12 -2
  14. package/dist/browser.cjs +37 -19
  15. package/dist/browser.cjs.map +1 -1
  16. package/dist/browser.d.cts +12 -3
  17. package/dist/browser.d.ts +12 -3
  18. package/dist/browser.js +27 -19
  19. package/dist/browser.js.map +1 -1
  20. package/dist/{chunk-3YUUC6RN.cjs → chunk-25ZON47K.cjs} +4105 -267
  21. package/dist/chunk-25ZON47K.cjs.map +1 -0
  22. package/dist/{chunk-N7W5KZK7.cjs → chunk-2L4YXZAZ.cjs} +4 -4
  23. package/dist/{chunk-N7W5KZK7.cjs.map → chunk-2L4YXZAZ.cjs.map} +1 -1
  24. package/dist/{chunk-ZYRCPSBU.js → chunk-3IE7KZY4.js} +4 -4
  25. package/dist/{chunk-ZYRCPSBU.js.map → chunk-3IE7KZY4.js.map} +1 -1
  26. package/dist/{chunk-ZX74KJPM.js → chunk-3VB5AT4R.js} +3 -3
  27. package/dist/{chunk-ZX74KJPM.js.map → chunk-3VB5AT4R.js.map} +1 -1
  28. package/dist/{chunk-S2KJRHDZ.cjs → chunk-4UFR2LB6.cjs} +13 -13
  29. package/dist/{chunk-S2KJRHDZ.cjs.map → chunk-4UFR2LB6.cjs.map} +1 -1
  30. package/dist/{chunk-QTNPMIO2.cjs → chunk-6JI6FWLZ.cjs} +341 -35
  31. package/dist/chunk-6JI6FWLZ.cjs.map +1 -0
  32. package/dist/{chunk-GTDQAN2Z.js → chunk-7AFW2J6J.js} +4075 -247
  33. package/dist/chunk-7AFW2J6J.js.map +1 -0
  34. package/dist/{chunk-UFTYX73U.js → chunk-7F76AWOI.js} +339 -35
  35. package/dist/chunk-7F76AWOI.js.map +1 -0
  36. package/dist/{chunk-BW4KGTV7.cjs → chunk-C5C5EF3W.cjs} +4 -4
  37. package/dist/{chunk-BW4KGTV7.cjs.map → chunk-C5C5EF3W.cjs.map} +1 -1
  38. package/dist/{chunk-VJGMEGMR.js → chunk-DOK7LKLO.js} +125 -13
  39. package/dist/chunk-DOK7LKLO.js.map +1 -0
  40. package/dist/{chunk-3M6WB62Y.cjs → chunk-ECD5XHBM.cjs} +139 -7
  41. package/dist/chunk-ECD5XHBM.cjs.map +1 -0
  42. package/dist/{chunk-L3CTXXVZ.js → chunk-FBS3PACU.js} +3 -3
  43. package/dist/{chunk-L3CTXXVZ.js.map → chunk-FBS3PACU.js.map} +1 -1
  44. package/dist/{chunk-QUKVGHN4.cjs → chunk-FKJBXGWP.cjs} +4 -4
  45. package/dist/{chunk-QUKVGHN4.cjs.map → chunk-FKJBXGWP.cjs.map} +1 -1
  46. package/dist/{chunk-SZK376QB.js → chunk-H4MT5TJP.js} +3 -3
  47. package/dist/{chunk-SZK376QB.js.map → chunk-H4MT5TJP.js.map} +1 -1
  48. package/dist/{chunk-3M6T7KB4.js → chunk-HAZALB7U.js} +3 -3
  49. package/dist/{chunk-3M6T7KB4.js.map → chunk-HAZALB7U.js.map} +1 -1
  50. package/dist/{chunk-VFZOPRQP.cjs → chunk-HWVBHU3O.cjs} +5 -5
  51. package/dist/{chunk-VFZOPRQP.cjs.map → chunk-HWVBHU3O.cjs.map} +1 -1
  52. package/dist/{chunk-D7EHZFK4.cjs → chunk-L7POWM5B.cjs} +138 -2
  53. package/dist/chunk-L7POWM5B.cjs.map +1 -0
  54. package/dist/{chunk-6OSUNBZY.js → chunk-LGABFD3L.js} +135 -7
  55. package/dist/chunk-LGABFD3L.js.map +1 -0
  56. package/dist/{chunk-2VNMKOUO.js → chunk-LRI4RH2N.js} +135 -3
  57. package/dist/chunk-LRI4RH2N.js.map +1 -0
  58. package/dist/{chunk-VDSYMSUY.js → chunk-MVIEIKOI.js} +3 -3
  59. package/dist/{chunk-VDSYMSUY.js.map → chunk-MVIEIKOI.js.map} +1 -1
  60. package/dist/{chunk-ZL5RB4UV.js → chunk-N5B242WY.js} +3 -3
  61. package/dist/{chunk-ZL5RB4UV.js.map → chunk-N5B242WY.js.map} +1 -1
  62. package/dist/{chunk-IM4RCUHA.js → chunk-P63S7P6N.js} +1309 -69
  63. package/dist/chunk-P63S7P6N.js.map +1 -0
  64. package/dist/{chunk-YLEVMOK2.cjs → chunk-R66QG3XT.cjs} +5 -4
  65. package/dist/{chunk-YLEVMOK2.cjs.map → chunk-R66QG3XT.cjs.map} +1 -1
  66. package/dist/{chunk-I55HO32M.js → chunk-RJMCWT7Z.js} +3 -3
  67. package/dist/{chunk-I55HO32M.js.map → chunk-RJMCWT7Z.js.map} +1 -1
  68. package/dist/{chunk-TZTCIAYW.cjs → chunk-S3RMAXH5.cjs} +137 -25
  69. package/dist/chunk-S3RMAXH5.cjs.map +1 -0
  70. package/dist/{chunk-IBRW3UOA.js → chunk-TWLKXV2O.js} +3 -3
  71. package/dist/{chunk-IBRW3UOA.js.map → chunk-TWLKXV2O.js.map} +1 -1
  72. package/dist/{chunk-HUPDIRBX.js → chunk-UWA5MWCI.js} +949 -918
  73. package/dist/chunk-UWA5MWCI.js.map +1 -0
  74. package/dist/{chunk-RYVV5UVI.cjs → chunk-V4GILQR6.cjs} +4 -4
  75. package/dist/{chunk-RYVV5UVI.cjs.map → chunk-V4GILQR6.cjs.map} +1 -1
  76. package/dist/{chunk-XRCY75UV.cjs → chunk-V4RO5KYY.cjs} +951 -918
  77. package/dist/chunk-V4RO5KYY.cjs.map +1 -0
  78. package/dist/{chunk-VZ5LDNHK.cjs → chunk-VTSH4YPT.cjs} +12 -12
  79. package/dist/{chunk-VZ5LDNHK.cjs.map → chunk-VTSH4YPT.cjs.map} +1 -1
  80. package/dist/{chunk-UUBNQV2T.cjs → chunk-WQDIZH2Z.cjs} +12 -12
  81. package/dist/{chunk-UUBNQV2T.cjs.map → chunk-WQDIZH2Z.cjs.map} +1 -1
  82. package/dist/{chunk-EGSUMHCS.cjs → chunk-YB4XJY5L.cjs} +12 -12
  83. package/dist/{chunk-EGSUMHCS.cjs.map → chunk-YB4XJY5L.cjs.map} +1 -1
  84. package/dist/{chunk-JHDR56XO.js → chunk-YS6CGUNH.js} +3 -3
  85. package/dist/{chunk-JHDR56XO.js.map → chunk-YS6CGUNH.js.map} +1 -1
  86. package/dist/{chunk-NWPCY65Z.cjs → chunk-YVDUEUFV.cjs} +1311 -68
  87. package/dist/chunk-YVDUEUFV.cjs.map +1 -0
  88. package/dist/{types-BOAsqHoU.d.ts → diagnostics-DRxhodP6.d.cts} +74 -2
  89. package/dist/{types-BOAsqHoU.d.cts → diagnostics-DRxhodP6.d.ts} +74 -2
  90. package/dist/diagrams/blockdiagram/index.d.cts +1 -1
  91. package/dist/diagrams/blockdiagram/index.d.ts +1 -1
  92. package/dist/diagrams/circuit/index.cjs +8 -8
  93. package/dist/diagrams/circuit/index.d.cts +1 -1
  94. package/dist/diagrams/circuit/index.d.ts +1 -1
  95. package/dist/diagrams/circuit/index.js +2 -2
  96. package/dist/diagrams/ecomap/index.cjs +7 -7
  97. package/dist/diagrams/ecomap/index.d.cts +1 -1
  98. package/dist/diagrams/ecomap/index.d.ts +1 -1
  99. package/dist/diagrams/ecomap/index.js +2 -2
  100. package/dist/diagrams/entity/index.cjs +6 -6
  101. package/dist/diagrams/entity/index.d.cts +1 -1
  102. package/dist/diagrams/entity/index.d.ts +1 -1
  103. package/dist/diagrams/entity/index.js +2 -2
  104. package/dist/diagrams/fishbone/index.cjs +8 -8
  105. package/dist/diagrams/fishbone/index.d.cts +1 -1
  106. package/dist/diagrams/fishbone/index.d.ts +1 -1
  107. package/dist/diagrams/fishbone/index.js +2 -2
  108. package/dist/diagrams/flowchart/index.cjs +8 -8
  109. package/dist/diagrams/flowchart/index.d.cts +2 -2
  110. package/dist/diagrams/flowchart/index.d.ts +2 -2
  111. package/dist/diagrams/flowchart/index.js +2 -2
  112. package/dist/diagrams/genogram/index.cjs +9 -9
  113. package/dist/diagrams/genogram/index.d.cts +1 -1
  114. package/dist/diagrams/genogram/index.d.ts +1 -1
  115. package/dist/diagrams/genogram/index.js +2 -2
  116. package/dist/diagrams/ladder/index.cjs +6 -6
  117. package/dist/diagrams/ladder/index.d.cts +1 -1
  118. package/dist/diagrams/ladder/index.d.ts +1 -1
  119. package/dist/diagrams/ladder/index.js +2 -2
  120. package/dist/diagrams/logic/index.cjs +6 -6
  121. package/dist/diagrams/logic/index.d.cts +1 -1
  122. package/dist/diagrams/logic/index.d.ts +1 -1
  123. package/dist/diagrams/logic/index.js +2 -2
  124. package/dist/diagrams/orgchart/index.cjs +7 -7
  125. package/dist/diagrams/orgchart/index.d.cts +1 -1
  126. package/dist/diagrams/orgchart/index.d.ts +1 -1
  127. package/dist/diagrams/orgchart/index.js +2 -2
  128. package/dist/diagrams/pedigree/index.cjs +7 -7
  129. package/dist/diagrams/pedigree/index.d.cts +1 -1
  130. package/dist/diagrams/pedigree/index.d.ts +1 -1
  131. package/dist/diagrams/pedigree/index.js +2 -2
  132. package/dist/diagrams/phylo/index.cjs +7 -7
  133. package/dist/diagrams/phylo/index.d.cts +1 -1
  134. package/dist/diagrams/phylo/index.d.ts +1 -1
  135. package/dist/diagrams/phylo/index.js +2 -2
  136. package/dist/diagrams/sld/index.cjs +14 -6
  137. package/dist/diagrams/sld/index.d.cts +14 -2
  138. package/dist/diagrams/sld/index.d.ts +14 -2
  139. package/dist/diagrams/sld/index.js +2 -2
  140. package/dist/diagrams/sociogram/index.cjs +6 -6
  141. package/dist/diagrams/sociogram/index.d.cts +1 -1
  142. package/dist/diagrams/sociogram/index.d.ts +1 -1
  143. package/dist/diagrams/sociogram/index.js +2 -2
  144. package/dist/diagrams/timing/index.d.cts +1 -1
  145. package/dist/diagrams/timing/index.d.ts +1 -1
  146. package/dist/diagrams/venn/index.cjs +9 -9
  147. package/dist/diagrams/venn/index.d.cts +1 -1
  148. package/dist/diagrams/venn/index.d.ts +1 -1
  149. package/dist/diagrams/venn/index.js +2 -2
  150. package/dist/{index-CJai_TEZ.d.ts → index-BRIkOPnd.d.cts} +60 -2
  151. package/dist/{index-C9A0h-CB.d.cts → index-C7SN-FB3.d.ts} +60 -2
  152. package/dist/index.cjs +351 -51
  153. package/dist/index.cjs.map +1 -1
  154. package/dist/index.d.cts +38 -4
  155. package/dist/index.d.ts +38 -4
  156. package/dist/index.js +283 -16
  157. package/dist/index.js.map +1 -1
  158. package/dist/react.cjs +21 -23
  159. package/dist/react.cjs.map +1 -1
  160. package/dist/react.d.cts +3 -2
  161. package/dist/react.d.ts +3 -2
  162. package/dist/react.js +21 -23
  163. package/dist/react.js.map +1 -1
  164. package/dist/tools-BVeUNdsU.d.ts +161 -0
  165. package/dist/tools-DdhP1kWY.d.cts +161 -0
  166. package/package.json +2 -2
  167. package/dist/chunk-2VNMKOUO.js.map +0 -1
  168. package/dist/chunk-3M6WB62Y.cjs.map +0 -1
  169. package/dist/chunk-3YUUC6RN.cjs.map +0 -1
  170. package/dist/chunk-6OSUNBZY.js.map +0 -1
  171. package/dist/chunk-D7EHZFK4.cjs.map +0 -1
  172. package/dist/chunk-GTDQAN2Z.js.map +0 -1
  173. package/dist/chunk-HUPDIRBX.js.map +0 -1
  174. package/dist/chunk-IM4RCUHA.js.map +0 -1
  175. package/dist/chunk-NWPCY65Z.cjs.map +0 -1
  176. package/dist/chunk-QTNPMIO2.cjs.map +0 -1
  177. package/dist/chunk-TZTCIAYW.cjs.map +0 -1
  178. package/dist/chunk-UFTYX73U.js.map +0 -1
  179. package/dist/chunk-VJGMEGMR.js.map +0 -1
  180. package/dist/chunk-XRCY75UV.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk3YUUC6RN_cjs = require('./chunk-3YUUC6RN.cjs');
3
+ var chunk25ZON47K_cjs = require('./chunk-25ZON47K.cjs');
4
4
 
5
5
  // src/ai/registry.ts
6
6
  var DIAGRAM_REGISTRY = [
@@ -211,12 +211,40 @@ var DIAGRAM_REGISTRY = [
211
211
  {
212
212
  type: "usecase",
213
213
  name: "UML use case diagram",
214
- tagline: "UML 2.5 use case \u2014 actors, use cases, subject boundary, include/extend/generalization.",
214
+ tagline: "UML 2.5.1 use case diagram \u2014 captures what a system does and for whom: actors, use cases, a subject boundary, and include/extend/generalization.",
215
215
  useWhen: "Use for software-engineering requirements and scope diagrams \u2014 'what does this system do, and for whom'. Actors (stick figures, or `(external)` rectangles for other systems) sit outside a subject boundary; use cases are ellipses inside it. `--` association, `..>` \xABinclude\xBB (source includes target), `<..` \xABextend\xBB (left extends right, with optional `[condition]` and extension points), `--|>` generalization (hollow triangle to parent, between actors or between use cases). Accepts a PlantUML-style inline form (`:Actor:`, `(Use case)`, `as ID`). Distinct from `state` (intra-object behavior, not system scope), `flowchart` (no actor/subject/include-extend semantics), and `bpmn` (how a process executes, not what a system offers).",
216
216
  cluster: "behavior-modeling",
217
217
  standard: "OMG UML 2.5.1 \xA718 visual subset; see 29-USECASE-STANDARD.md",
218
218
  syntaxKey: "usecase"
219
219
  },
220
+ {
221
+ type: "sequence",
222
+ name: "UML sequence diagram",
223
+ tagline: "UML 2.5.1 \xA717 interaction diagram \u2014 shows how participants exchange messages over time (who calls whom, in what order): lifelines, activations, and all twelve combined fragments.",
224
+ useWhen: "Use for time-ordered interactions between participants \u2014 API call flows, auth handshakes, distributed protocols, object collaborations, 'who calls whom in what order'. Lifelines run top\u2192bottom; messages run left\u2192right: `->` synchronous (filled head), `->>` asynchronous (open head), `-->` reply (dashed), `-x` lost, `o->` found. `+`/`-` suffixes open/close activation bars; `*Target` creates a participant and `destroy` ends one. All twelve UML combined fragments \u2014 `alt`/`opt`/`loop`/`par`/`break`/`critical`/`seq`/`strict`/`neg`/`ignore`/`consider`/`assert` \u2014 plus `ref` interaction-use frames. Participant kinds `actor`/`boundary`/`control`/`entity`/`database` render their UML/Jacobson symbols; `\xABstereotype\xBB` overrides the label. Distinct from `usecase` (system scope, not message order), `state` (one object's modes, not inter-object messages), `bpmn` (organisational process), and `flowchart` (no lifelines/time axis).",
225
+ cluster: "behavior-modeling",
226
+ standard: "OMG UML 2.5.1 \xA717 (Interactions); see 33-SEQUENCE-STANDARD.md",
227
+ syntaxKey: "sequence"
228
+ },
229
+ {
230
+ type: "petri",
231
+ name: "Petri net",
232
+ tagline: "Place/transition net that computes the dynamics \u2014 enabled transitions and token firing, not just shapes.",
233
+ useWhen: "Use whenever the user mentions 'Petri net', 'place/transition net', 'token', 'marking', 'concurrency model', 'mutual exclusion', 'producer/consumer', or wants to model concurrent resource flow / synchronisation. Declare `place <id> *<tokens>` (circles holding tokens), `transition <id>` (bars \u2014 add `timed rate: <\u03BB>` for a GSPN timed transition), and bipartite arcs: `->` standard, `-o` inhibitor (enabled only while the place is empty), `--` read/test, `=>` reset. Arc weight via `weight: n` or `*n`; place limit via `capacity: n`. The engine validates the bipartite structure, applies a `fire: T1, T2` sequence to the initial marking, and highlights which transitions are *enabled* in the result. `layout: lr|tb`. Distinct from `state` (one active state, not a token distribution), `sfc` (a restricted PLC Petri net), `bpmn` (organisational process), and `flowchart` (single thread, no concurrency or marking).",
234
+ cluster: "concurrency",
235
+ standard: "Murata 1989 + ISO/IEC 15909-1 (place/transition net); see 34-PETRINET-STANDARD.md",
236
+ syntaxKey: "petri"
237
+ },
238
+ // ── Network / infrastructure ─────────────────────────────────
239
+ {
240
+ type: "network",
241
+ name: "Network topology",
242
+ tagline: "IT / CCTV network topology with Cisco-convention device icons, typed links, subnets/VLANs, and topology-correct layout.",
243
+ useWhen: "Use whenever the user mentions 'network diagram', 'network topology', 'infrastructure diagram', a 'c\xE1maras / CCTV / camera network', a LAN/WAN/data-center diagram, or wants to lay out routers, switches, firewalls, access points, servers, IP cameras, NVRs, etc. Declare typed devices `<kind> <id> \"label\"` (router, switch, l3switch, firewall, loadbalancer, ap, wlc, gateway, modem, ids, proxy, vpngw, server, serverfarm, pc, laptop, mobile, ipphone, printer, storage, camera (with `type: fixed|bullet|dome|ptz|turret`), nvr, dvr, poeswitch, encoder, monitor, internet, wan, pstn, cloud, lan) and connect with `a -- b` (undirected), `a -> b` (directed), or `a == b` (LAG). After `:` add a link spec: a link type (fiber/wireless/serial/poe/vpn/lag), `trunk`/`access` mode, `vlan: 10,20`, a speed like `1G`/`10G`, and `port: Gi0/1>eth0`. Group devices in nested boundaries: `site`/`rack` (physical) and `subnet`/`vlan`/`zone`/`dmz` (logical) blocks `{ \u2026 }`. Choose `layout: tiered` (default; band by `tier: edge|core|distribution|access`), `tree`, `star`, `ring`, `bus`, `mesh`, `spine-leaf` (declare `spines:`/`leaves:` and the mesh is auto-generated), or `manual`. The engine never drops a device/port/link, and validates VLAN range 1\u20134094 plus device IP-in-subnet-CIDR. Distinct from `flowchart` (no device icons/topology), `c4` (software containers, not physical devices), and `sld` (electrical single-line, not data network).",
244
+ cluster: "network-infrastructure",
245
+ standard: "Cisco-convention topology icons + hierarchical/spine-leaf models + ANSI/TIA-606 + ONVIF; see 35-NETWORK-STANDARD.md",
246
+ syntaxKey: "network"
247
+ },
220
248
  // ── Research / evidence synthesis ────────────────────────────
221
249
  {
222
250
  type: "prisma",
@@ -286,48 +314,95 @@ var DIAGRAM_REGISTRY = [
286
314
  syntaxKey: "timeline"
287
315
  }
288
316
  ];
317
+ var DIAGRAM_SINCE = {
318
+ // 0.1.0 — initial release (2026-03-15)
319
+ genogram: "0.1.0",
320
+ ecomap: "0.1.0",
321
+ pedigree: "0.1.0",
322
+ phylo: "0.1.0",
323
+ sociogram: "0.1.0",
324
+ logic: "0.1.0",
325
+ circuit: "0.1.0",
326
+ timing: "0.1.0",
327
+ blockdiagram: "0.1.0",
328
+ ladder: "0.1.0",
329
+ sld: "0.1.0",
330
+ entity: "0.1.0",
331
+ fishbone: "0.1.0",
332
+ // 0.1.1 (2026-04-18)
333
+ flowchart: "0.1.1",
334
+ venn: "0.1.1",
335
+ matrix: "0.1.1",
336
+ mindmap: "0.1.1",
337
+ orgchart: "0.1.1",
338
+ // 0.2.0 (2026-04-20)
339
+ timeline: "0.2.0",
340
+ decisiontree: "0.2.0",
341
+ // 0.3.0 (2026-04-29)
342
+ state: "0.3.0",
343
+ pid: "0.3.0",
344
+ // 0.4.0 (2026-05-05)
345
+ erd: "0.4.0",
346
+ breadboard: "0.4.0",
347
+ bpmn: "0.4.0",
348
+ fbd: "0.4.0",
349
+ sfc: "0.4.0",
350
+ // 0.4.3 (2026-05-16) — usecase
351
+ usecase: "0.4.3",
352
+ // 0.5.0 (2026-05-19)
353
+ prisma: "0.5.0",
354
+ // 0.5.1
355
+ sequence: "0.5.1",
356
+ // 0.6.0 — upcoming (committed post-0.5.2, not yet released)
357
+ pert: "0.6.0",
358
+ petri: "0.6.0",
359
+ network: "0.6.0"
360
+ };
361
+ function getDiagramSince(type) {
362
+ const resolved = resolveDiagramType(type);
363
+ return resolved ? DIAGRAM_SINCE[resolved] : void 0;
364
+ }
365
+ var TYPE_ALIASES = {
366
+ block: "blockdiagram",
367
+ "entity-structure": "entity",
368
+ graph: "flowchart",
369
+ statediagram: "state",
370
+ "statediagram-v2": "state",
371
+ sequencediagram: "sequence"
372
+ };
373
+ function resolveDiagramType(type) {
374
+ const normalized = type.trim().toLowerCase();
375
+ return DIAGRAM_REGISTRY.find((d) => d.type === normalized)?.type ?? TYPE_ALIASES[normalized];
376
+ }
289
377
  function getDiagramMeta(type) {
290
- return DIAGRAM_REGISTRY.find((d) => d.type === type);
378
+ const resolved = resolveDiagramType(type);
379
+ return DIAGRAM_REGISTRY.find((d) => d.type === resolved);
291
380
  }
292
381
  function getAllDiagramTypes() {
293
382
  return DIAGRAM_REGISTRY.map((d) => d.type);
294
383
  }
295
384
 
296
- // src/ai/errors.ts
297
- var ENGINE_BUG_NAMES = /* @__PURE__ */ new Set([
298
- "ReferenceError",
299
- "TypeError",
300
- "RangeError"
301
- ]);
302
- function extractError(err) {
303
- if (err instanceof Error) {
304
- const anyErr = err;
305
- const hasParseFields = typeof anyErr.line === "number";
306
- const isEngineBug = !hasParseFields && ENGINE_BUG_NAMES.has(err.name);
307
- const sourceHint = isEngineBug ? firstStackFrame(err.stack) : typeof anyErr.source === "string" ? anyErr.source : void 0;
308
- return {
309
- line: typeof anyErr.line === "number" ? anyErr.line : void 0,
310
- column: typeof anyErr.column === "number" ? anyErr.column : void 0,
311
- source: sourceHint,
312
- message: isEngineBug ? `[engine bug: ${err.name}] ${err.message}` : err.message,
313
- hint: typeof anyErr.hint === "string" ? anyErr.hint : isEngineBug ? "This looks like a Schematex internal error rather than a DSL syntax problem. Please file an issue with the failing DSL at https://github.com/SchemaTex/Schematex/issues." : void 0
314
- };
315
- }
316
- return { message: String(err) };
317
- }
318
- function firstStackFrame(stack) {
319
- if (!stack) return void 0;
320
- for (const line of stack.split("\n")) {
321
- const trimmed = line.trim();
322
- if (trimmed.startsWith("at ")) {
323
- return trimmed.replace(/\((?:.*\/)?([^/]+)\)/, "($1)");
324
- }
325
- }
326
- return void 0;
327
- }
328
-
329
385
  // src/ai/_generated.ts
330
386
  var EXAMPLES = [
387
+ {
388
+ "slug": "block-nested-feedback",
389
+ "diagram": "block",
390
+ "title": "Nested feedback loops",
391
+ "description": "A multi-loop control block diagram \u2014 an inner loop closes G2\xB7G3 around sensor H1, an outer loop closes the whole forward path around sensor H2 routed over the top, and two summing junctions combine the reference with each feedback signal. The kind of cascaded architecture a generic flowchart can't lay out.",
392
+ "standard": "Ogata control systems",
393
+ "tags": [
394
+ "nested-loops",
395
+ "cascade",
396
+ "feedback",
397
+ "summing-junction",
398
+ "sensor",
399
+ "signal-flow"
400
+ ],
401
+ "complexity": 3,
402
+ "featured": false,
403
+ "dsl": 'blockdiagram "Nested Feedback Loops"\nG1 = block("G1(s)") [role: plant]\nG2 = block("G2(s)") [role: plant]\nG3 = block("G3(s)") [role: plant]\nH1 = block("H1(s)") [role: sensor]\nH2 = block("H2(s)") [role: sensor, route: above]\ns1 = sum(+R, -h2)\ns2 = sum(+a, -h1)\nin -> s1 ["R(s)"]\ns1 -> G1 -> s2\ns2 -> G2 -> G3\nG3 -> out ["Y(s)"]\nG2 -> H1\nH1 -> s2\nG3 -> H2\nH2 -> s1',
404
+ "notes": '## Scenario\n\nCascaded and nested control loops are everywhere in real plants \u2014 an inner velocity loop inside an outer position loop, an inner current loop inside a speed loop. Textbooks (Ogata, Franklin) draw them as block diagrams with multiple summing junctions and feedback paths that cross over the forward chain. Schematex routes those crossings automatically from a signal-flow description.\n\n## Annotation key\n\n- `block("label") [role: plant|sensor]` \u2014 transfer-function block; `role` drives the visual styling so plants and sensors read differently\n- `sum(+R, -h2)` \u2014 a summing junction with explicit signs: adds reference `R`, subtracts the outer feedback `h2`\n- `route: above` on `H2` \u2014 forces the outer feedback sensor to route over the top of the diagram, keeping the long return path clear of the forward chain\n- `s1 -> G1 -> s2` \u2014 chained connections; signal flows left to right through the forward path\n\n## How to read\n\nThe inner loop is `G2 \u2192 G3 \u2192 H1 \u2192 s2`: sensor H1 feeds the plant output back to the second summing junction, closing a loop around `G2\xB7G3`. The outer loop is wider \u2014 `G3 \u2192 H2 \u2192 s1` \u2014 where H2 (routed above) returns the final output to the first summing junction. The reference `R(s)` enters at `s1`, is corrected by both feedback signals in turn, and the cascaded plants drive `Y(s)`. Two nested loops, one signal-flow spec.'
405
+ },
331
406
  {
332
407
  "slug": "block-pid-loop",
333
408
  "diagram": "block",
@@ -468,6 +543,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
468
543
  "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',
469
544
  "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."
470
545
  },
546
+ {
547
+ "slug": "circuit-bridge-rectifier-supply",
548
+ "diagram": "circuit",
549
+ "title": "Bridge rectifier power supply",
550
+ "description": "Four-diode full-wave bridge rectifier with a smoothing capacitor and load resistor, written as a compact electrical netlist.",
551
+ "standard": "IEEE 315",
552
+ "tags": [
553
+ "rectifier",
554
+ "diode",
555
+ "power-supply",
556
+ "capacitor",
557
+ "netlist"
558
+ ],
559
+ "complexity": 2,
560
+ "featured": false,
561
+ "dsl": 'circuit "Bridge Rectifier Supply" netlist\nV1 ac1 ac2 12Vac\nD1 ac1 vout 1N4007\nD2 ac2 vout 1N4007\nD3 0 ac1 1N4007\nD4 0 ac2 1N4007\nC1 vout 0 470u\nRload vout 0 1k',
562
+ "notes": "## Scenario\n\nA technician needs a quick schematic for the textbook AC-to-DC rectifier: a transformer secondary, four rectifier diodes, a reservoir capacitor, and a load. The netlist form keeps the topology precise while the renderer chooses the schematic placement.\n\n## Annotation key\n\n- `D1` to `D4` are ordinary diode components inferred from the `D` prefix.\n- `0` is the canonical ground net.\n- `C1` smooths the rectified output across `vout` and ground.\n\n## How to read\n\nDuring each half-cycle, two diodes conduct and steer current into `vout` with the same polarity. The capacitor charges near the peak and supplies the load between peaks, reducing ripple at `Rload`."
563
+ },
471
564
  {
472
565
  "slug": "circuit-ce-amplifier",
473
566
  "diagram": "circuit",
@@ -486,6 +579,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
486
579
  "dsl": 'circuit "CE Amp (netlist)" netlist\nV1 vcc 0 9V\nRc vcc c 2.2k\nRb vcc b 100k\nQ1 c b e npn\nRe e 0 1k',
487
580
  "notes": '## Scenario\n\nThe NPN common-emitter amplifier is the first transistor circuit every electronics student builds. Schematex renders it from a five-line SPICE netlist \u2014 the same format used in LTspice, ngspice, and Cadence \u2014 with automatic component placement and rail routing, so the diagram matches the hand-drawn textbook version without any manual layout.\n\n## Annotation key\n\n- `circuit "..." netlist` \u2014 enables netlist parsing mode (SPICE syntax)\n- `V1 vcc 0 9V` \u2014 voltage source named V1, positive terminal at node `vcc`, negative at `0` (ground), value 9V\n- `Rc vcc c 2.2k` \u2014 resistor Rc between nodes `vcc` and `c` with value 2.2 k\u03A9\n- `Rb vcc b 100k` \u2014 base bias resistor between `vcc` and node `b`\n- `Q1 c b e npn` \u2014 NPN BJT transistor: collector=c, base=b, emitter=e\n- `Re e 0 1k` \u2014 emitter degeneration resistor between node `e` and ground\n\n## How to read\n\nThe supply rail (Vcc = 9 V) connects to the top of both resistors. Rc is the collector load; the output signal is taken across it. Rb biases the base into the active region. Re provides emitter degeneration for stability. Q1 amplifies a small base current into a large collector-to-emitter current flow. The auto-layout positions Vcc at top, ground at bottom, and Q1 in the center.'
488
581
  },
582
+ {
583
+ "slug": "circuit-opamp-inverting-amplifier",
584
+ "diagram": "circuit",
585
+ "title": "Inverting op-amp amplifier",
586
+ "description": "A practical inverting op-amp stage rendered from a SPICE-style netlist with feedback and input resistors, split supply rails, and a labelled output node.",
587
+ "standard": "IEEE 315",
588
+ "tags": [
589
+ "op-amp",
590
+ "amplifier",
591
+ "feedback",
592
+ "netlist",
593
+ "analog"
594
+ ],
595
+ "complexity": 2,
596
+ "featured": false,
597
+ "dsl": 'circuit "Inverting Op-Amp (netlist)" netlist\nVin vin 0 AC1V\nRin vin inv 10k\nRf out inv 100k\nU1 noninv inv out type=opamp label="U1 TL072"\nRbias noninv 0 10k\nVp vcc 0 +12V\nVn vee 0 -12V',
598
+ "notes": "## Scenario\n\nAn analog engineer documents a standard inverting gain stage before moving to PCB layout. The two resistors make the gain visible in the schematic: `Rf / Rin = 10`, so the output is an inverted, amplified version of the input.\n\n## Annotation key\n\n- `type=opamp` selects the three-pin op-amp symbol in netlist mode.\n- `Rin` and `Rf` are ordinary SPICE-style resistor lines.\n- `noninv`, `inv`, and `out` are named nets, not pixel coordinates.\n\n## How to read\n\nThe input signal enters through `Rin` into the inverting input. `Rf` feeds the output back to the same node, closing the negative feedback loop. The non-inverting input is tied to ground through `Rbias`, giving the stage a stable reference."
599
+ },
489
600
  {
490
601
  "slug": "decisiontree-investment-analysis",
491
602
  "diagram": "decisiontree",
@@ -504,6 +615,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
504
615
  "dsl": 'decisiontree:decision "Platform Vendor Choice"\n\ndecision "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',
505
616
  "notes": '## Scenario\n\nA CTO is deciding how to stand up a new data-platform layer: build internally, buy managed SaaS, or take a hybrid. Each path has outcomes with probabilities and net-value estimates. The decision-analysis tree rolls the expected value back to the decision node so the optimal branch is identified automatically.\n\n## Annotation key\n\n- `decision "\u2026"` \u2014 actor chooses a branch\n- `chance "\u2026"` \u2014 nature chooses; `prob N` on each child (must sum to 1)\n- `end "\u2026" payoff=N` \u2014 terminal payoff\n- `choice "label"` \u2014 label on an outgoing decision branch\n- EV rollback: chance = probability-weighted sum; decision = max child EV\n\n## How to read\n\nEvaluate each branch\'s expected value. Build in-house: 0.6 \xD7 900k + 0.4 \xD7 150k = **600k**. Managed SaaS: flat **500k**. Hybrid: 0.5 \xD7 700k + 0.5 \xD7 300k = **500k**. Under these estimates the optimal branch is *Build in-house* \u2014 the parser flags it on render. The chart\'s real value is in forcing stakeholders to state probabilities explicitly; sensitivity to them is where the interesting argument happens.'
506
617
  },
618
+ {
619
+ "slug": "decisiontree-iris-cart-ml",
620
+ "diagram": "decisiontree",
621
+ "title": "Iris CART decision tree",
622
+ "description": "Machine-learning decision tree for the classic Iris classifier with split thresholds, sample counts, Gini impurity, and class leaves.",
623
+ "standard": "CART / scikit-learn plot_tree convention",
624
+ "tags": [
625
+ "decisiontree",
626
+ "ml",
627
+ "cart",
628
+ "iris",
629
+ "gini"
630
+ ],
631
+ "complexity": 3,
632
+ "featured": false,
633
+ "dsl": 'decisiontree:ml "Iris CART Classifier"\ndirection: top-down\nimpurity: gini\nclasses: setosa, versicolor, virginica\n\nsplit "Petal length <= 2.45" feature=petal_length op="<=" threshold=2.45 samples=150 gini=0.667\n true leaf "Setosa" class=setosa value=50 gini=0.0 samples=50\n false split "Petal width <= 1.75" feature=petal_width op="<=" threshold=1.75 samples=100 gini=0.5\n true split "Petal length <= 4.95" feature=petal_length op="<=" threshold=4.95 samples=54 gini=0.168\n true leaf "Versicolor" class=versicolor value=49 gini=0.04 samples=49\n false leaf "Virginica" class=virginica value=5 gini=0.32 samples=5\n false leaf "Virginica" class=virginica value=45 gini=0.0 samples=45',
634
+ "notes": "## Scenario\n\nModel explainability often starts with a small tree. This Iris example mirrors the structure exported by CART tools: feature thresholds inside split nodes, impurity metrics, samples, and class-labelled leaves.\n\n## Annotation key\n\n- `decisiontree:ml` selects ML mode.\n- `split` nodes carry `feature`, `threshold`, `samples`, and `gini`.\n- `true` and `false` prefixes define the branch semantics."
635
+ },
507
636
  {
508
637
  "slug": "decisiontree-support-triage",
509
638
  "diagram": "decisiontree",
@@ -574,6 +703,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
574
703
  "dsl": 'ecomap "Marcus, age 15"\n center: client [label: "Marcus"]\n mom [label: "Mother", category: family]\n dad [label: "Father (divorced)", category: family]\n school [label: "East High School", category: education]\n coach [label: "Soccer Coach", category: community]\n peers [label: "Soccer team", category: community]\n therapist [label: "Ms. Chen", category: mental-health]\n mom === client [label: "primary caregiver"]\n dad --- client [label: "EOW weekends"]\n school === client\n coach --> client [label: "mentor"]\n peers === client\n therapist <-> client [label: "weekly"]',
575
704
  "notes": "## Scenario\n\nA school-based counselor draws this ecomap during an initial intake session with Marcus, a 15-year-old referred for behavioral issues. The diagram takes under five minutes and immediately shows where Marcus's support is concentrated (peers, soccer team) and where it's fragile (divorced father, every-other-weekend contact).\n\n## Annotation key\n\n- `center: client` \u2014 the identified young person\n- `===` \u2014 strong connection; `---` \u2014 tenuous connection\n- `-->` \u2014 mentor/support flows toward client\n- `<->` \u2014 reciprocal therapeutic alliance\n- `EOW weekends` \u2014 edge label capturing the visitation schedule\n\n## How to read\n\nMarcus's strongest systems are his mother (primary caregiver), school, and soccer peers \u2014 all strong ties (===). His father is a tenuous connection (---). The therapist and soccer coach have supportive directional relationships pointing toward Marcus. The soccer team appears to be the central protective factor in this adolescent's life."
576
705
  },
706
+ {
707
+ "slug": "entity-family-office-trust",
708
+ "diagram": "entity",
709
+ "title": "Family office trust structure",
710
+ "description": "Estate-planning entity structure with a grantor, irrevocable trust, trustee LLC, holding company, investment LLCs, and beneficiary distributions.",
711
+ "standard": "Trust and estate planning conventions",
712
+ "tags": [
713
+ "entity",
714
+ "trust",
715
+ "family-office",
716
+ "estate-planning",
717
+ "distributions"
718
+ ],
719
+ "complexity": 3,
720
+ "featured": false,
721
+ "dsl": 'entity-structure "Family Office Trust"\njurisdiction SD "South Dakota" [color: "#2563eb"]\njurisdiction DE "Delaware" [color: "#059669"]\n\nentity grantor "Grantor" individual [role: "Settlor"]\nentity trust1 "Irrevocable Dynasty Trust" trust@SD [est: "2021-04-01", note: "GST exempt"]\nentity trustee "Trustee Services LLC" llc@SD [role: "Directed trustee"]\nentity holdco "Family Holdings, Inc." corp@DE\nentity realestate "Real Estate LLC" llc@DE\nentity markets "Marketable Securities LLC" llc@DE\nentity beneficiary "Children Trust Share" trust@SD\n\ngrantor -> trust1 : contribution\ntrustee ==> trust1 [label: "administrative control"]\ntrust1 -> holdco : 100%\nholdco -> realestate : 100%\nholdco -> markets : 100%\ntrust1 --> beneficiary [label: "distributions"]',
722
+ "notes": "## Scenario\n\nEstate attorneys often need to explain control, economics, and fiduciary roles on one page. This structure separates voting/control relationships from ownership and beneficiary distributions.\n\n## Annotation key\n\n- `trust` renders as an ellipse, distinct from corporate entities.\n- `==>` marks voting or control without economic ownership.\n- `-->` marks distributions to beneficiaries."
723
+ },
577
724
  {
578
725
  "slug": "entity-holding-company",
579
726
  "diagram": "entity",
@@ -647,6 +794,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
647
794
  "dsl": 'erd\ntitle: "E-commerce Schema"\ndirection: LR\n\ntable Customer {\n customer_id int PK\n email varchar UK\n name varchar\n created_at timestamp\n}\n\ntable Address {\n address_id int PK\n customer_id int FK -> Customer.customer_id\n line1 varchar\n city varchar\n zip varchar\n}\n\ntable Category {\n category_id int PK\n name varchar\n}\n\ntable Product {\n product_id int PK\n category_id int FK -> Category.category_id\n name varchar\n price decimal\n}\n\ntable Order {\n order_id int PK\n customer_id int FK -> Customer.customer_id\n placed_at timestamp\n status varchar\n}\n\ntable OrderLine {\n order_id int PK FK -> Order.order_id\n line_no int PK\n product_id int FK -> Product.product_id\n qty int\n price decimal\n}\n\nref Address.customer_id many-mandatory -- one-mandatory Customer.customer_id\nref Order.customer_id many-mandatory -- one-mandatory Customer.customer_id : "places"\nref Product.category_id many-mandatory -- one-mandatory Category.category_id\nref OrderLine.order_id many-mandatory -- one-mandatory Order.order_id\nref OrderLine.product_id many-mandatory -- one-mandatory Product.product_id',
648
795
  "notes": 'A six-table schema spanning four FK-depth layers: Category and Customer at the root; Product and Address at depth one; Order at depth two; OrderLine at depth three with a composite PK `(order_id, line_no)`. Demonstrates layered LR routing and labelled relationships ("places").'
649
796
  },
797
+ {
798
+ "slug": "erd-saas-rbac-multitenant",
799
+ "diagram": "erd",
800
+ "title": "Multi-tenant SaaS RBAC schema",
801
+ "description": "Crow's-foot ERD for tenants, users, memberships, roles, permissions, invitations, and audit events in a SaaS workspace product.",
802
+ "standard": "Crow's-foot ERD",
803
+ "tags": [
804
+ "erd",
805
+ "saas",
806
+ "multitenant",
807
+ "rbac",
808
+ "schema"
809
+ ],
810
+ "complexity": 3,
811
+ "featured": false,
812
+ "dsl": 'erd\ntitle: "SaaS RBAC"\ndirection: LR\n\ntable Tenant {\n tenant_id uuid PK\n name varchar NN\n plan varchar\n}\ntable User {\n user_id uuid PK\n email varchar UK\n name varchar\n}\ntable Membership {\n tenant_id uuid PK FK -> Tenant.tenant_id\n user_id uuid PK FK -> User.user_id\n role_id uuid FK -> Role.role_id\n status varchar NN\n}\ntable Role {\n role_id uuid PK\n tenant_id uuid FK -> Tenant.tenant_id\n name varchar NN\n}\ntable Permission {\n permission_id uuid PK\n key varchar UK\n}\ntable RolePermission {\n role_id uuid PK FK -> Role.role_id\n permission_id uuid PK FK -> Permission.permission_id\n}\ntable Invitation {\n invitation_id uuid PK\n tenant_id uuid FK -> Tenant.tenant_id\n email varchar NN\n}\ntable AuditEvent {\n event_id uuid PK\n tenant_id uuid FK -> Tenant.tenant_id\n actor_user_id uuid FK -> User.user_id\n}\n\nref Membership.tenant_id many-mandatory -- one-mandatory Tenant.tenant_id : "belongs to"\nref Membership.user_id many-mandatory -- one-mandatory User.user_id : "joins"\nref Membership.role_id many-optional -- one-mandatory Role.role_id : "has role"\nref Role.tenant_id many-mandatory -- one-mandatory Tenant.tenant_id : "scoped to"\nref RolePermission.role_id many-mandatory -- one-mandatory Role.role_id\nref RolePermission.permission_id many-mandatory -- one-mandatory Permission.permission_id\nref Invitation.tenant_id many-optional .. one-mandatory Tenant.tenant_id\nref AuditEvent.tenant_id many-mandatory -- one-mandatory Tenant.tenant_id\nref AuditEvent.actor_user_id many-optional .. one-optional User.user_id',
813
+ "notes": "## Scenario\n\nRBAC schemas are where generic ERD examples stop being useful. A SaaS product needs tenant scoping, composite keys on join tables, optional actor references for system events, and non-identifying edges for nullable audit metadata.\n\n## Annotation key\n\n- `Membership` and `RolePermission` use composite primary keys.\n- `..` marks a non-identifying relationship.\n- FK targets are declared inline and repeated as explicit `ref` lines for crow's-foot cardinality."
814
+ },
650
815
  {
651
816
  "slug": "erd-university-schema",
652
817
  "diagram": "erd",
@@ -724,6 +889,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
724
889
  "dsl": 'fbd "Tank Level Setpoint Limiter"\n\nvar DesiredSetpoint: real\nvar SafeSetpoint: real\nvar Alarm: bool\n\nnetwork 0 "Clamp setpoint to safe range":\n SafeSetpoint = LIMIT(MN: 0.0, IN: DesiredSetpoint, MX: 95.0)\n\nnetwork 1 "Alarm on out-of-range request":\n OutOfRange = OR(LT(DesiredSetpoint, 0.0), GT(DesiredSetpoint, 95.0))\n Alarm = MOVE(OutOfRange.OUT)',
725
890
  "notes": "When an operator types a tank-level setpoint into the HMI, you can't just trust the number. Maybe they meant to type 75 and hit 750. Maybe they typed -5 because they were copying from a spec sheet that used a different reference. Maybe the HMI's input field doesn't have validation and the value is whatever bit-pattern the OPC UA bridge happened to land on. The PLC always validates, and the canonical pattern is: clamp to a safe range, then raise an alarm if the requested value was out of range so a human knows to check.\n\n**Network 0 \u2014 LIMIT.** `SafeSetpoint = LIMIT(MN: 0.0, IN: DesiredSetpoint, MX: 95.0)` is the clamp. LIMIT takes three REAL inputs and returns a REAL: the value of `IN` if it's in [MN, MX], otherwise MN or MX. Both bounds are inline constants \u2014 they render as yellow boxed text at their ports, no wire needed. The downstream control loop reads `SafeSetpoint`, never `DesiredSetpoint` directly. Even if the operator's input is corrupted, the tank can never be commanded outside the physically safe range.\n\n**Network 1 \u2014 out-of-range alarm.** `OutOfRange = OR(LT(DesiredSetpoint, 0.0), GT(DesiredSetpoint, 95.0))` is an inline expression that nests two comparison blocks inside an OR. LT (less-than) returns BOOL when its first input is less than the second; GT (greater-than) is the opposite. The OR fires when *either* fires \u2014 the operator's value was either too low or too high. The `Alarm = MOVE(OutOfRange.OUT)` then drives whatever HMI alarm channel \u2014 a red banner, a Slack notification, a SCADA event log entry.\n\n**Two data types in one diagram.** Look at the wire colors in the rendered SVG: the wire from `DesiredSetpoint` into LIMIT.IN is REAL (orange \u2014 IEEE 754 floating-point); the wires from LT.OUT and GT.OUT into OR are BOOL (black). The LIMIT.OUT is also REAL (orange). One of the FBD engine's small but pleasant features: the renderer infers each wire's type from the source port and colors it accordingly, following the TIA Portal convention. If you accidentally wired an INT to a REAL port the colors would mismatch at the junction and you'd notice immediately.\n\n**Why not ladder?** Ladder logic excels at boolean signal routing \u2014 contacts in series and parallel feeding into output coils. It has zero affordance for REAL arithmetic and comparison; you'd write the LIMIT expression as a structured-text \"function block\" call inside a ladder rung, which kills the visual semantic. FBD makes the math first-class. For per-scan combinational logic involving any non-BOOL signal, FBD is what the IEC 61131-3 standard expects you to use."
726
891
  },
892
+ {
893
+ "slug": "fishbone-manufacturing-defect-6m",
894
+ "diagram": "fishbone",
895
+ "title": "Manufacturing defect 6M fishbone",
896
+ "description": "Ishikawa 6M root-cause analysis for a solder joint defect spike on an electronics assembly line.",
897
+ "standard": "Ishikawa 1968 / ISO 9001 CAPA",
898
+ "tags": [
899
+ "fishbone",
900
+ "6m",
901
+ "manufacturing",
902
+ "defect",
903
+ "capa"
904
+ ],
905
+ "complexity": 3,
906
+ "featured": false,
907
+ "dsl": 'fishbone "Solder Joint Defect Spike"\nconfig density = spacious\neffect "3.2% solder joint rejects"\n\ncategory man "Man"\ncategory machine "Machine"\ncategory material "Material"\ncategory method "Method"\ncategory measurement "Measurement"\ncategory environment "Environment"\n\nman : "New operators on night shift"\n - "Training checklist not signed"\nman : "Handover notes incomplete"\n\nmachine : "Reflow oven zone 4 drift"\n - "Thermocouple calibration overdue"\nmachine : "Stencil printer squeegee worn"\n\nmaterial : "Solder paste past floor life"\nmaterial : "PCB pads oxidized after storage"\n\nmethod : "Stencil aperture undersized"\nmethod : "Pick-and-place speed raised"\n\nmeasurement : "AOI false accept rate rising"\nmeasurement : "X-ray sampling reduced"\n\nenvironment : "Humidity above process window"\nenvironment : "ESD straps failing audit"',
908
+ "notes": "## Scenario\n\nAn electronics manufacturer opens a CAPA after solder rejects spike above the control limit. The 6M fishbone forces the team to inspect people, machines, material, method, measurement, and environment before committing to corrective action.\n\n## Annotation key\n\n- `config density = spacious` gives the dense six-category diagram breathing room.\n- Indented `-` lines are sub-causes under the preceding cause.\n- The 6M category names match the manufacturing convention quality teams expect."
909
+ },
727
910
  {
728
911
  "slug": "fishbone-website-traffic",
729
912
  "diagram": "fishbone",
@@ -866,6 +1049,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
866
1049
  "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',
867
1050
  "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.'
868
1051
  },
1052
+ {
1053
+ "slug": "ladder-conveyor-interlock",
1054
+ "diagram": "ladder",
1055
+ "title": "Conveyor interlock chain",
1056
+ "description": "Three-rung ladder logic for a downstream-first conveyor start sequence with jam permissives and an alarm latch.",
1057
+ "standard": "IEC 61131-3",
1058
+ "tags": [
1059
+ "ladder",
1060
+ "conveyor",
1061
+ "interlock",
1062
+ "permissive",
1063
+ "alarm"
1064
+ ],
1065
+ "complexity": 3,
1066
+ "featured": false,
1067
+ "dsl": 'ladder "Conveyor Interlock Chain"\n\nrung 1 "Run downstream conveyor first":\n XIC(AUTO_MODE, "BIT 1.0", name="Auto mode")\n XIO(ESTOP_OK, "BIT 1.1", name="E-stop not tripped")\n XIO(CV2_JAM, "IN 2.2", name="Downstream jam clear")\n OTE(CV2_RUN, "OUT 4.1", name="Conveyor 2 run")\n\nrung 2 "Start upstream only when downstream is running":\n XIC(AUTO_MODE, "BIT 1.0", name="Auto mode")\n XIC(CV2_RUN, "OUT 4.1", name="CV2 running")\n XIO(CV1_JAM, "IN 2.1", name="Upstream jam clear")\n OTE(CV1_RUN, "OUT 4.0", name="Conveyor 1 run")\n\nrung 3 "Latch jam alarm":\n parallel:\n branch:\n XIC(CV1_JAM, "IN 2.1", name="CV1 jam")\n branch:\n XIC(CV2_JAM, "IN 2.2", name="CV2 jam")\n OTL(JAM_ALARM, "BIT 3.0", name="Jam alarm")',
1068
+ "notes": "## Scenario\n\nConveyors are started downstream-first so a running upstream belt never feeds material into a stopped downstream belt. This example shows the interlock directly in the rungs: conveyor 1 cannot run until conveyor 2 is already commanded on.\n\n## Annotation key\n\n- `XIC(CV2_RUN)` is the downstream permissive on the upstream conveyor rung.\n- `XIO(CV1_JAM)` and `XIO(CV2_JAM)` block the run command when a jam bit is true.\n- `OTL(JAM_ALARM)` latches the alarm so it remains visible after the jam sensor clears."
1069
+ },
869
1070
  {
870
1071
  "slug": "ladder-mode-selection",
871
1072
  "diagram": "ladder",
@@ -904,6 +1105,42 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
904
1105
  "dsl": 'ladder "Motor Start/Stop"\nrung 1 "Seal-in circuit":\n parallel:\n branch:\n XIC(START_PB, "IN 1.0", name="Start Button")\n branch:\n XIC(MOTOR_AUX, "BIT 3.0", name="Aux Contact")\n XIO(STOP_PB, "IN 1.1", name="Stop Button")\n OTE(MOTOR_CMD, "OUT 2.0", name="Motor Command")',
905
1106
  "notes": "## Scenario\n\nEvery controls engineer learns the three-wire motor start/stop circuit before writing their first PLC program. It appears verbatim in IEC 61131-3 training materials, Allen-Bradley certification exams, and factory acceptance tests worldwide. The seal-in contact latches the motor ON after the momentary start pushbutton is released \u2014 the fundamental pattern for any maintained-output logic.\n\n## Annotation key\n\n- `XIC(tag, address, name=...)` \u2014 Examine If Closed: contact passes power when the referenced bit is `1` (true)\n- `XIO(tag, address, name=...)` \u2014 Examine If Open: contact passes power when the referenced bit is `0` (false); normal for stop buttons wired N.C.\n- `OTE(tag, address, name=...)` \u2014 Output Energize: coil energizes the referenced bit when rung has power\n- `parallel: branch:` \u2014 models a parallel contact branch (logical OR)\n- The `MOTOR_AUX` contact in the parallel branch is the seal-in: once the motor output energizes, the aux contact closes and holds the rung true even after the START_PB releases\n\n## How to read\n\nThe rung reads left to right. Power flows if *either* the start button (XIC START_PB) *or* the aux contact (XIC MOTOR_AUX) is closed, *and* the stop button (XIO STOP_PB) is not pressed. When the output coil (OTE MOTOR_CMD) energizes the motor, it also drives the aux contact bit \u2014 latching the rung high. Pressing STOP breaks the series path and de-energizes the rung, dropping the motor and the seal-in simultaneously."
906
1107
  },
1108
+ {
1109
+ "slug": "ladder-tank-level-pump-control",
1110
+ "diagram": "ladder",
1111
+ "title": "Tank level pump control",
1112
+ "description": "Pump control ladder with low-level start, high-level stop, seal-in logic, and a run-delay timer.",
1113
+ "standard": "IEC 61131-3",
1114
+ "tags": [
1115
+ "ladder",
1116
+ "pump",
1117
+ "tank",
1118
+ "timer",
1119
+ "seal-in"
1120
+ ],
1121
+ "complexity": 3,
1122
+ "featured": false,
1123
+ "dsl": 'ladder "Tank Level Pump Control"\n\nrung 1 "Fill pump seal-in":\n parallel:\n branch:\n XIC(LOW_LEVEL, "IN 1.0", name="Low level")\n branch:\n XIC(PUMP_RUN, "OUT 2.0", name="Pump running")\n XIO(HIGH_LEVEL, "IN 1.1", name="High level")\n XIO(FAULT, "BIT 3.0", name="Fault clear")\n OTE(PUMP_RUN, "OUT 2.0", name="Fill pump")\n\nrung 2 "Run delay proves flow":\n XIC(PUMP_RUN, "OUT 2.0", name="Pump running")\n TON(FLOW_DELAY, IN=PUMP_RUN, PT=5000, name="Flow prove delay")\n\nrung 3 "No-flow alarm after delay":\n XIC(FLOW_DELAY, "TMR 1.0", name="Delay done")\n XIO(FLOW_OK, "IN 1.2", name="Flow switch")\n OTL(FLOW_ALARM, "BIT 3.1", name="No flow alarm")',
1124
+ "notes": "## Scenario\n\nA fill pump starts when the tank reaches low level, stays on through a seal-in path, and stops at the high-level switch. A timer allows a few seconds for the flow switch to prove flow before latching an alarm.\n\n## Annotation key\n\n- The `parallel:` block is the seal-in path.\n- `TON` is the on-delay timer used as a permissive before declaring no-flow.\n- `OTL` latches the alarm so an operator must acknowledge the failure."
1125
+ },
1126
+ {
1127
+ "slug": "logic-ansi-vs-iec-gate-gallery",
1128
+ "diagram": "logic",
1129
+ "title": "IEC logic gate gallery",
1130
+ "description": "Logic gate gallery using IEC 60617 rectangular symbols for AND, OR, XOR, NAND, NOR, NOT, and special-output buffers.",
1131
+ "standard": "IEC 60617-12",
1132
+ "tags": [
1133
+ "logic",
1134
+ "iec",
1135
+ "gate-gallery",
1136
+ "ansi-alternative",
1137
+ "symbols"
1138
+ ],
1139
+ "complexity": 2,
1140
+ "featured": false,
1141
+ "dsl": 'logic "IEC Gate Gallery" style: iec\ninput A, B, C, EN\noutput Y_and, Y_or, Y_xor, Y_nand, Y_nor, Y_not, Y_tri\nY_and = AND(A, B)\nY_or = OR(A, B)\nY_xor = XOR(A, B)\nY_nand = NAND(A, B)\nY_nor = NOR(A, B)\nY_not = NOT(C)\nY_tri = TRISTATE_BUF(A, EN)',
1142
+ "notes": "## Scenario\n\nANSI curved gates are common in US education, but IEC rectangular logic symbols are standard in many international and industrial documents. This example shows the same functional DSL rendered with the IEC style.\n\n## Annotation key\n\n- `style: iec` switches the symbol family for the whole diagram.\n- Gate definitions remain ordinary functional assignments.\n- The gallery includes both basic gates and a special-output buffer."
1143
+ },
907
1144
  {
908
1145
  "slug": "logic-full-adder",
909
1146
  "diagram": "logic",
@@ -1053,6 +1290,124 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1053
1290
  "dsl": "mindmap\n\n# Q4 Company OKRs\n\n## Grow ARR 30%\n### Expand enterprise pipeline\n- 10 new qualified logos\n- Win rate \u2265 25%\n### Increase expansion\n- Net revenue retention \u2265 120%\n- Seat adoption +40%\n\n## Ship Platform v2\n### Core migration\n- 100% API coverage\n- Zero-downtime cutover\n### Developer experience\n- Sub-5-min quickstart\n- 95% doc satisfaction\n\n## Strengthen team\n### Hiring\n- 8 senior engineers\n- 2 staff PMs\n### Retention\n- Voluntary attrition < 5%\n- eNPS \u2265 40",
1054
1291
  "notes": "## Scenario\n\nThe chief of staff projects this during the Q4 all-hands. Three objectives radiate from the centre; every key result is a leaf with a specific number. The mindmap format reads fast \u2014 every person in the company can find their team's objective within three seconds \u2014 and it tolerates mid-quarter edits without disturbing other branches.\n\n## Annotation key\n\n- `#` \u2014 root; company-level frame\n- `##` \u2014 objective (qualitative direction)\n- `###` \u2014 key-result cluster\n- `-` bullets \u2014 specific measurable KRs\n\n## How to read\n\nThe root names the quarter. The three `##` branches are the objectives \u2014 the things that will be judged at the end of the quarter. Each `###` groups key results by theme; the bullets are the actual measurable targets. If a KR can't be reduced to a number, it probably belongs in a planning doc rather than on this mindmap."
1055
1292
  },
1293
+ {
1294
+ "slug": "network-boundaries",
1295
+ "diagram": "network",
1296
+ "title": "Physical and logical boundaries in one diagram",
1297
+ "description": "A branch-office topology nesting physical containers (a site holding an MDF rack) and logical overlays (a DMZ security zone and a CIDR subnet) \u2014 solid borders for physical, dashed tinted borders for logical \u2014 so the same devices read correctly in both the cabling and the addressing views.",
1298
+ "standard": "Cisco-convention topology icons + hierarchical campus model",
1299
+ "tags": [
1300
+ "network",
1301
+ "boundaries",
1302
+ "subnet",
1303
+ "vlan",
1304
+ "dmz",
1305
+ "rack",
1306
+ "security-zone"
1307
+ ],
1308
+ "complexity": 3,
1309
+ "featured": false,
1310
+ "dsl": 'network "Branch Office"\n layout: tiered\n internet net\n site hq "HQ Building" {\n rack mdf "MDF Rack" {\n firewall fw1 tier: edge\n l3switch core1 tier: core\n }\n }\n zone dmz "DMZ" {\n server web "Web Server"\n }\n subnet lan "10.0.10.0/24" {\n switch a1 tier: access\n pc u1 "User PC" ip: 10.0.10.50\n }\n net -- fw1 : wan\n fw1 -- web\n fw1 -- core1 : 10G\n core1 -- a1 : trunk vlan: 10\n a1 -- u1',
1311
+ "notes": "Real network documentation has to answer two different questions at once: *where is this box physically* (which building, which rack) and *what address space / security zone is it in*. Schematex draws both kinds of grouping and distinguishes them visually.\n\n**Physical containers are solid.** `site` and `rack` blocks get solid borders \u2014 the HQ building holds an MDF rack holding the firewall and core switch.\n\n**Logical overlays are dashed and tinted.** `zone` (the DMZ) and `subnet` (the `10.0.10.0/24` LAN) get dashed, tinted borders \u2014 they group by policy and addressing, not by location. The engine validates that `u1`'s `ip: 10.0.10.50` actually falls inside the subnet's CIDR.\n\n**Tiers still band the layout.** `tier: edge|core|access` keeps the hierarchical top-to-bottom ordering even inside the boundaries, so the diagram reads as a proper three-tier design."
1312
+ },
1313
+ {
1314
+ "slug": "network-cctv-camera-system",
1315
+ "diagram": "network",
1316
+ "title": "CCTV camera network topology",
1317
+ "description": "IP-video surveillance topology \u2014 Internet \u2192 perimeter firewall \u2192 core switch \u2192 PoE switches \u2192 dome/PTZ/bullet cameras, with the cameras isolated on their own 192.168.20.0/24 subnet and an NVR for recording.",
1318
+ "standard": "Cisco topology icons + IP/CCTV (ONVIF) conventions",
1319
+ "tags": [
1320
+ "cctv",
1321
+ "surveillance",
1322
+ "ip-camera",
1323
+ "nvr",
1324
+ "poe",
1325
+ "vlan",
1326
+ "subnet"
1327
+ ],
1328
+ "complexity": 3,
1329
+ "featured": true,
1330
+ "dsl": 'network "Acme HQ \u2014 CCTV"\n layout: tiered\n internet net "Internet"\n firewall fw1 "Perimeter FW" tier: edge\n l3switch core1 "Core SW" tier: core\n poeswitch poe1 "PoE Switch A" tier: access\n poeswitch poe2 "PoE Switch B" tier: access\n nvr nvr1 "Video Recorder"\n monitor wall1 "Guard Station"\n subnet cams "192.168.20.0/24" {\n camera cam1 "Lobby Dome" type: dome ip: 192.168.20.11\n camera cam2 "Gate PTZ" type: ptz ip: 192.168.20.12\n camera cam3 "Dock Bullet" type: bullet ip: 192.168.20.13\n poe1\n poe2\n }\n net -- fw1 : wan "ISP 1Gbps"\n fw1 -- core1 : fiber 10G\n core1 -- poe1 : trunk vlan: 20 1G\n core1 -- poe2 : trunk vlan: 20 1G\n core1 -- nvr1 : 1G\n core1 -- wall1\n poe1 -- cam1 : poe\n poe1 -- cam2 : poe\n poe2 -- cam3 : poe',
1331
+ "notes": "## Scenario\n\nA security integrator is documenting an IP-CCTV install for hand-off. The cameras need their own isolated subnet (so they can't route to the corporate LAN), Power-over-Ethernet from the access switches, and a single NVR recording everything. The diagram has to be **editable and printable** so it can be reused for the next building.\n\n## What the diagram shows\n\n- **The camera subnet** `192.168.20.0/24` is drawn as a dashed boundary enclosing the three cameras and both PoE switches \u2014 the cameras' IPs are validated against the CIDR, so a typo'd address is caught, not silently rendered.\n- **Camera body styles** are real silhouettes: `type: dome`, `type: ptz`, and `type: bullet` each render differently, the way an integrator's drawings distinguish them.\n- **PoE links** (`poe`) are drawn in green with a power tag \u2014 these carry both data and power to the cameras.\n- **The uplinks** are a trunk carrying VLAN 20 at 1G; the fiber backbone to the core is orange at 10G; the ISP hand-off is a serial/WAN circuit.\n\n## Annotation key\n\n| Element | Meaning |\n|---|---|\n| Dashed blue box | Logical subnet (label = CIDR) |\n| Green link + PoE | Power-over-Ethernet to a camera |\n| Orange link | Fiber uplink |\n| `Trunk \xB7 VLAN 20 \xB7 1G` | 802.1Q trunk carrying VLAN 20 at 1 Gbps |\n\nSwap `layout: tiered` for `layout: tree` to get a pure hierarchy, or add more `subnet` / `vlan` boundaries as the install grows."
1332
+ },
1333
+ {
1334
+ "slug": "network-enterprise-campus",
1335
+ "diagram": "network",
1336
+ "title": "Three-tier enterprise campus network",
1337
+ "description": "Classic Cisco hierarchical campus \u2014 Internet/WAN edge, redundant core switches with a LAG uplink, a distribution layer, and an access layer feeding a server farm. The canonical core/distribution/access model.",
1338
+ "standard": "Cisco hierarchical internetworking model (core/distribution/access)",
1339
+ "tags": [
1340
+ "campus",
1341
+ "three-tier",
1342
+ "core",
1343
+ "distribution",
1344
+ "access",
1345
+ "lag",
1346
+ "enterprise"
1347
+ ],
1348
+ "complexity": 3,
1349
+ "featured": false,
1350
+ "dsl": 'network "Driscoll Campus"\n layout: tiered\n internet inet\n cloud wan "WAN"\n firewall fw1 "Core Firewall" tier: edge\n router er1 "Edge Rtr 1" tier: edge\n l3switch cs1 "Core SW 1" tier: core\n l3switch cs2 "Core SW 2" tier: core\n switch d1 "Dist A" tier: distribution\n switch d2 "Dist B" tier: distribution\n serverfarm farm "Server Farm" count: 4\n a1 a2 a3 : switch tier: access\n inet -- fw1\n wan -- er1 : serial\n fw1 -- cs1 : 10G\n er1 -- cs2\n cs1 == cs2 : lag 40G\n cs1 -- d1\n cs2 -- d2\n cs1 -- farm : trunk vlan: 100\n d1 -- a1\n d2 -- a2\n d2 -- a3',
1351
+ "notes": '## Scenario\n\nA network engineer is drawing the as-built for a campus that follows Cisco\'s three-tier hierarchical model. The `tier:` attribute on each device drives the banding, so the diagram lands in the canonical **edge \u2192 core \u2192 distribution \u2192 access** rows automatically \u2014 no manual placement.\n\n## What the diagram shows\n\n- **`tier:` banding** \u2014 `edge`, `core`, `distribution`, `access` place devices in fixed rows top-to-bottom. Endpoints (the server farm) hang below their switch.\n- **The redundant core** \u2014 `cs1 == cs2 : lag 40G` is the EtherChannel uplink, drawn as a double line with a 40G label (`==` is shorthand for a LAG link).\n- **Shorthand** \u2014 `a1 a2 a3 : switch tier: access` declares three identical access switches on one line.\n- **The server farm** \u2014 `serverfarm ... count: 4` draws a stacked icon labelled \xD74.\n\n## Annotation key\n\n| Element | Meaning |\n|---|---|\n| Double line + `40G` | Aggregated link (LAG / EtherChannel) |\n| `tier: core` etc. | Hierarchical band assignment |\n| `Trunk \xB7 VLAN 100` | Server-farm uplink trunk |\n\nFor a two-tier "collapsed core" just drop the distribution devices; for a data-center fabric use `layout: spine-leaf` instead.'
1352
+ },
1353
+ {
1354
+ "slug": "network-layout-modes-star-ring-bus-mesh",
1355
+ "diagram": "network",
1356
+ "title": "Network layout modes",
1357
+ "description": "Four small network topology sketches showing star, ring, bus, and mesh layouts as separate diagrams in the gallery corpus.",
1358
+ "standard": "Cisco topology conventions",
1359
+ "tags": [
1360
+ "network",
1361
+ "layout",
1362
+ "star",
1363
+ "ring",
1364
+ "bus",
1365
+ "mesh"
1366
+ ],
1367
+ "complexity": 2,
1368
+ "featured": false,
1369
+ "dsl": 'network "Branch LAN - Star Layout"\n layout: star\n router gw "Gateway"\n switch sw1 "Access Switch"\n pc pc1 "Admin PC"\n laptop lt1 "Laptop"\n printer prn "Printer"\n ap ap1 "Wi-Fi AP"\n gw -- sw1 : 1G\n sw1 -- pc1 : access vlan: 10 1G\n sw1 -- lt1 : access vlan: 10 1G\n sw1 -- prn : access vlan: 20 1G\n sw1 -- ap1 : poe vlan: 30 1G',
1370
+ "notes": "## Scenario\n\nNetwork examples should prove that layout is semantic, not incidental. This one uses `layout: star`, where the gateway and access switch form the center and endpoints fan around them.\n\n## Annotation key\n\n- `layout: star` selects the hub-and-spoke placer.\n- Link annotations carry VLANs, speed, and PoE.\n- The same device/link model can be rendered with other layouts in companion examples."
1371
+ },
1372
+ {
1373
+ "slug": "network-link-types",
1374
+ "diagram": "network",
1375
+ "title": "Every link type on one fabric",
1376
+ "description": "A compact topology that exercises the full link vocabulary \u2014 fiber with speed and port tags, a LAG bundle, a wireless association, an 802.1Q trunk carrying VLANs, a PoE drop to a camera, and a site-to-site VPN tunnel \u2014 each rendered with its own line style.",
1377
+ "standard": "Cisco-convention topology icons + ANSI/TIA-606 labelling",
1378
+ "tags": [
1379
+ "network",
1380
+ "links",
1381
+ "fiber",
1382
+ "lag",
1383
+ "vlan",
1384
+ "poe",
1385
+ "vpn"
1386
+ ],
1387
+ "complexity": 2,
1388
+ "featured": false,
1389
+ "dsl": 'network "Link types"\n layout: tree\n router core "Core"\n switch a "Sw A"\n switch b "Sw B"\n ap ap1 "AP"\n server s1\n camera c1 type: turret\n vpngw vpn1 "VPN GW"\n core -- a : fiber 10G port: Gi0/1>Gi1/0/1\n core -- b : lag 20G\n a -- ap1 : wireless\n a -- s1 : trunk vlan: 10,20 1G\n b -- c1 : poe\n core -- vpn1 : vpn "site-to-site"',
1390
+ "notes": "A network diagram is only as useful as the *link* annotations \u2014 \"are these two switches on fiber or copper, trunk or access, what VLANs, what speed.\" Schematex encodes all of it after the `:` on a connection, and renders each link type distinctly.\n\n**One line, many semantics.** `fiber 10G port: Gi0/1>Gi1/0/1` draws the orange ticked fiber style and prints both the speed and the two interface names. `lag 20G` thickens the line for a bundled uplink. `wireless` and `vpn` go dashed; `poe` carries power to the camera; `trunk vlan: 10,20` tags the 802.1Q VLAN list.\n\n**Validation comes free.** VLAN ids are range-checked (1\u20134094) and the engine never silently drops a port, link, or device \u2014 if you wrote it, it's on the diagram or it's a reported error."
1391
+ },
1392
+ {
1393
+ "slug": "network-spine-leaf-fabric",
1394
+ "diagram": "network",
1395
+ "title": "Spine-leaf data-center fabric",
1396
+ "description": "A folded-Clos data-center fabric \u2014 two spine switches, four leaf switches fully meshed to the spines automatically, and servers attached to their leaves at 25G.",
1397
+ "standard": "Clos (1953) folded-Clos / spine-leaf fabric",
1398
+ "tags": [
1399
+ "spine-leaf",
1400
+ "clos",
1401
+ "fabric",
1402
+ "datacenter",
1403
+ "leaf",
1404
+ "spine"
1405
+ ],
1406
+ "complexity": 2,
1407
+ "featured": false,
1408
+ "dsl": 'network "DC Fabric"\n layout: spine-leaf\n spines: sp1 sp2\n leaves: lf1 lf2 lf3 lf4\n server h1\n server h2\n server h3\n lf1 -- h1 : 25G\n lf2 -- h2 : 25G\n lf4 -- h3 : 25G',
1409
+ "notes": "## Scenario\n\nA data-center architect wants the fabric drawn without hand-typing every spine-to-leaf cable. In `layout: spine-leaf` mode you declare the spines and leaves, and the engine **auto-meshes every leaf to every spine** \u2014 here that's 2 \xD7 4 = 8 generated links \u2014 leaving you to add only the host attachments.\n\n## What the diagram shows\n\n- **`spines:` / `leaves:`** declare the two fabric rows. The devices don't need a separate `kind` line \u2014 spines become L3 switches, leaves become switches.\n- **Auto-mesh** \u2014 every spine\u2194leaf link is generated; you never type them. Add or remove a leaf and the mesh updates.\n- **Host attachments** \u2014 `lf1 -- h1 : 25G` hangs a server below its leaf at 25 Gbps.\n\n## Annotation key\n\n| Element | Meaning |\n|---|---|\n| Top row | Spine switches |\n| Middle row | Leaf switches (fully meshed to spines) |\n| Bottom row | Hosts, under their leaf |\n\nThis is the modern east-west fabric; for a traditional north-south campus use `layout: tiered` with `tier:` bands instead."
1410
+ },
1056
1411
  {
1057
1412
  "slug": "orgchart-matrix-reporting",
1058
1413
  "diagram": "orgchart",
@@ -1089,6 +1444,25 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1089
1444
  "dsl": 'orgchart "Acme \u2014 Series A Team"\nceo: "Jamie Torres" | CEO [role: ceo]\n cto: "Raj Patel" | CTO [role: cto]\n lead_fe: "Priya Nair" | Eng Lead | Frontend [role: engineer]\n eng1: "Alex Kim" | Senior Engineer [role: engineer]\n eng2: "Jordan Lee" | Engineer [role: engineer, status: new]\n open1: open "TBH" | Frontend Engineer [role: engineer]\n lead_be: "Omar Hassan" | Eng Lead | Backend [role: engineer]\n eng3: "Yuki Tanaka" | Staff Engineer [role: engineer]\n draft1: draft "TBH" | Senior Engineer [role: engineer]\n cpo: "Ellen Wu" | CPO [role: cpo]\n pm1: "Tyler Brooks" | Product Lead | Core [role: product]\n pm2: "Suki Ito" | Product Lead | Growth [role: product]\n coo: "Maria Santos" | COO [role: ops]\n fin1: "Nour Ahmed" | Finance Manager [role: ops]\nadvisor adv1: "Dr. Alan Ford" | Board Advisor [role: advisor]',
1090
1445
  "notes": '## Scenario\n\nThe founder is preparing a hiring plan for the next two quarters and uses this chart in a board update. It shows the current team, the one confirmed open req (Frontend Engineer), one planned-but-not-recruiting slot (Staff Engineer backend), and the board advisor relationship. Indentation communicates reporting lines without drawing edges.\n\n## Annotation key\n\n- `id: "Name" | "Title" | "Department"` \u2014 a person node\n- Indentation (2 spaces) \u2014 reporting hierarchy\n- `open \u2026` / `draft \u2026` \u2014 unfilled / planned roles\n- `advisor \u2026` \u2014 external board or advisor relationship\n- `[role: \u2026]` \u2014 colour-coded by function\n\n## How to read\n\nThe single root is the CEO. Each two-space indent step moves one level down the reporting tree. Two kinds of "ghost" slots appear: `open` nodes (Frontend Engineer) are reqs you are actively hiring for; `draft` nodes (Staff Backend) are next-quarter plans. The advisor sits outside the tree \u2014 not in the reporting chain but formally associated with the org.'
1091
1446
  },
1447
+ {
1448
+ "slug": "pedigree-assisted-reproduction-ivf-donor-surrogate",
1449
+ "diagram": "pedigree",
1450
+ "title": "Assisted reproduction pedigree",
1451
+ "description": "Clinical pedigree showing IVF donor and surrogate context with a proband child and clearly separated biological and gestational participants.",
1452
+ "standard": "Bennett 2022",
1453
+ "tags": [
1454
+ "pedigree",
1455
+ "assisted-reproduction",
1456
+ "ivf",
1457
+ "donor",
1458
+ "surrogate",
1459
+ "proband"
1460
+ ],
1461
+ "complexity": 3,
1462
+ "featured": false,
1463
+ "dsl": 'pedigree "Assisted Reproduction Family"\n intended_father [male, unaffected]\n intended_mother [female, unaffected]\n egg_donor [female, evaluated]\n surrogate [female, evaluated]\n\n intended_father -- egg_donor\n embryo [unknown, pregnancy]\n\n intended_father -- intended_mother\n child [female, unaffected, proband]\n\n surrogate -- intended_father\n child [female, unaffected, proband]',
1464
+ "notes": "## Scenario\n\nAssisted reproduction cases need more than a simple parent-child line. This example keeps the clinical pedigree compact while labeling the relevant participants: intended parents, donor, surrogate, embryo, and proband.\n\n## Annotation key\n\n- `pregnancy` marks the embryo node.\n- `evaluated` indicates participants evaluated in the genetics intake.\n- Re-declaring `child` preserves the proband while documenting alternate family context."
1465
+ },
1092
1466
  {
1093
1467
  "slug": "pedigree-brca1",
1094
1468
  "diagram": "pedigree",
@@ -1233,6 +1607,114 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1233
1607
  "dsl": 'pert\ntitle: "Three-point project"\nunit: days\ncritical-tolerance: 0.01\n\ntask A "Spec" duration: 2/3/5\ntask B "Build" duration: 5/8/14 after: A\ntask C "Test" duration: 3/4/6 after: B\ntask D "Deploy" duration: 1/2/3 after: C',
1234
1608
  "notes": "PERT's original 1959 contribution wasn't the box \u2014 it was handling *uncertainty* in durations. Write a duration as `O/M/P` (optimistic / most-likely / pessimistic) and the engine treats it as a beta-PERT estimate.\n\n**Expected duration.** Each three-point activity collapses to its beta-distribution mean **te = (O + 4M + P) / 6**, which is what the Duration field shows. `Build` at `5/8/14` becomes `te = 8.5` \u2014 pulled above the most-likely 8 because the pessimistic tail (14) is fatter than the optimistic one (5). The whole forward/backward pass then runs on these `te` values, so the project duration here is \u2248 17.83 days, not an integer.\n\n**Variance and project risk.** Each activity also carries `\u03C3\xB2 = ((P \u2212 O) / 6)\xB2`, surfaced as a small `\u03C3=\u2026` annotation under the name and on a `data-pert-variance` attribute. Summed over the critical-path activities (under the classical independence assumption), the project standard deviation is \u2248 1.69 days \u2014 a one-line risk figure you can quote: \"\u224868% chance of finishing within \xB11.69 days of 17.83.\"\n\n**Why `critical-tolerance: 0.01`.** Once durations are fractional, accumulated floating-point error can give a truly-critical activity a slack of `0.0000001`. The tolerance treats anything within 0.01 of zero as critical, so the red path stays stable across runs. For all-integer projects you can leave it at the default of `0`."
1235
1609
  },
1610
+ {
1611
+ "slug": "petri-arc-types-inhibitor-read-reset",
1612
+ "diagram": "petri",
1613
+ "title": "Petri net arc types",
1614
+ "description": "Petri net showing standard, inhibitor, read, reset, weighted arcs, capacity, and enabled-transition highlighting in one compact control example.",
1615
+ "standard": "Murata 1989 / ISO-IEC 15909",
1616
+ "tags": [
1617
+ "petri",
1618
+ "inhibitor",
1619
+ "read-arc",
1620
+ "reset-arc",
1621
+ "capacity"
1622
+ ],
1623
+ "complexity": 3,
1624
+ "featured": false,
1625
+ "dsl": 'petri "Arc Types"\nlayout: lr\ntokens: auto\nplace Idle *1\nplace Busy\nplace Fault\nplace Permit *1\nplace Buffer capacity: 3 tokens: 2\ntransition Start\ntransition Complete\ntransition Reset\nIdle -> Start\nPermit -- Start\nFault -o Start\nBuffer -> Start weight: 2\nStart -> Busy\nBusy -> Complete\nComplete -> Idle\nFault => Reset\nBusy -> Reset\nReset -> Idle',
1626
+ "notes": "## Scenario\n\nPetri nets become much more expressive once arc types carry semantics. This example shows a start transition that requires permission, is inhibited by a fault, consumes two buffer tokens, and can be reset from a fault state.\n\n## Annotation key\n\n- `--` is a read arc: test without consuming.\n- `-o` is an inhibitor arc: enabled only when the place is empty.\n- `=>` is a reset arc: clear the source place when the transition fires."
1627
+ },
1628
+ {
1629
+ "slug": "petri-classic-net",
1630
+ "diagram": "petri",
1631
+ "title": "The classic Petri net \u2014 concurrency with feedback",
1632
+ "description": "Murata's canonical place/transition net \u2014 one transition forks a token into two concurrent branches that a second transition joins, with a feedback place closing the loop. The engine marks which transitions are enabled in the current marking.",
1633
+ "standard": "Murata 1989 + ISO/IEC 15909-1 (place/transition net)",
1634
+ "tags": [
1635
+ "petri",
1636
+ "concurrency",
1637
+ "fork-join",
1638
+ "feedback",
1639
+ "enabled-transitions"
1640
+ ],
1641
+ "complexity": 2,
1642
+ "featured": true,
1643
+ "dsl": 'petri "Classic"\n place P1 *1\n place P2\n place P3 *2\n place P4 *1\n transition T1\n transition T2\n P1 -> T1\n T1 -> P2\n T1 -> P3\n P2 -> T2\n P3 -> T2\n T2 -> P4\n P4 -> T1',
1644
+ "notes": "This is the net you find on the first page of every Petri-net text (Murata's 1989 survey, Wikipedia's figure 1): the smallest diagram that shows concurrency, synchronisation, and feedback at once.\n\n**One fork, one join.** Firing `T1` puts a token into both `P2` and `P3` \u2014 two branches now run concurrently. `T2` cannot fire until *both* branches deposit their tokens, so it is a synchronisation point (an AND-join).\n\n**The feedback place closes the loop.** `P4 -> T1` routes the result back as a back-edge, drawn around the layout so the cycle reads cleanly. In the current marking only `T1` is *enabled* (highlighted) \u2014 `T2` is dead because `P2` is empty until `T1` fires.\n\n**The render is downstream of the semantics.** Schematex isn't drawing shapes you positioned; it validated the bipartite structure, checked each transition's input places against the marking, and coloured the enabled one."
1645
+ },
1646
+ {
1647
+ "slug": "petri-fire-sequence",
1648
+ "diagram": "petri",
1649
+ "title": "Firing a transition advances the marking",
1650
+ "description": "The same three-place net before and after firing \u2014 the `fire:` directive replays a transition, so the rendered marking is the state *after* the token moved from P1 to P2, and the engine now highlights T2 as the newly enabled transition.",
1651
+ "standard": "Murata 1989 (place/transition net firing rule)",
1652
+ "tags": [
1653
+ "petri",
1654
+ "firing-rule",
1655
+ "marking",
1656
+ "dynamics",
1657
+ "enabled-transitions"
1658
+ ],
1659
+ "complexity": 1,
1660
+ "featured": false,
1661
+ "dsl": 'petri "After fire: T1"\n place P1 *1\n transition T1\n place P2\n transition T2\n place P3\n P1 -> T1\n T1 -> P2\n P2 -> T2\n T2 -> P3\n fire: T1',
1662
+ "notes": "Most text-to-diagram tools draw a Petri net as static shapes. Schematex *runs* it.\n\n**The firing rule, applied.** The net is declared with one token in `P1`. The `fire: T1` directive tells the engine to apply T1's firing \u2014 consume one token from each input place (`P1`), produce one in each output place (`P2`). What you see rendered is the marking **after** that step: the token now sits in `P2`.\n\n**Enablement is recomputed.** With the token in `P2`, `T1` is now dead (its input `P1` is empty) and `T2` is the *enabled* transition (highlighted). Chain more steps \u2014 `fire: T1, T2` \u2014 to walk the net forward and watch the token travel P1 \u2192 P2 \u2192 P3.\n\nThis is the difference between a drawing and a model: the diagram is a *function of* the computed marking, not something you hand-positioned."
1663
+ },
1664
+ {
1665
+ "slug": "petri-mutual-exclusion",
1666
+ "diagram": "petri",
1667
+ "title": "Mutual exclusion with a shared resource",
1668
+ "description": "The canonical concurrency pattern as a Petri net \u2014 two processes competing for a single Mutex token. Either process may enter its critical section, but the shared token guarantees they never do so at the same time. Both entry transitions show as enabled until one fires and consumes the resource.",
1669
+ "standard": "Murata 1989 / ISO-IEC 15909-1 (place/transition net)",
1670
+ "tags": [
1671
+ "petri",
1672
+ "concurrency",
1673
+ "mutual-exclusion",
1674
+ "synchronization",
1675
+ "formal-methods"
1676
+ ],
1677
+ "complexity": 2,
1678
+ "featured": true,
1679
+ "dsl": 'petri "Mutual Exclusion \u2014 two processes, one resource"\n place idleA *1 "A idle"\n place idleB *1 "B idle"\n place mutex *1 "resource"\n place critA "A critical"\n place critB "B critical"\n transition enterA\n transition exitA\n transition enterB\n transition exitB\n idleA -> enterA\n mutex -> enterA\n enterA -> critA\n critA -> exitA\n exitA -> idleA\n exitA -> mutex\n idleB -> enterB\n mutex -> enterB\n enterB -> critB\n critB -> exitB\n exitB -> idleB\n exitB -> mutex',
1680
+ "notes": 'Mutual exclusion is the "hello world" of concurrency theory, and a Petri net states it more honestly than a flowchart can: the safety property is a structural fact about a single token, not a comment in the margin.\n\n**One token, two claimants.** The `mutex` place holds exactly one token. Both `enterA` and `enterB` are enabled \u2014 each has its process idle *and* the resource available \u2014 so the engine rings both green. The moment one fires, it consumes the mutex token, and the other entry transition is no longer enabled. There is no marking in which both `critA` and `critB` hold a token; that invariant is what "mutual exclusion" means.\n\n**Exit returns the resource.** `exitA` / `exitB` put the token back into `mutex` and the process back to idle, so the net cycles forever without ever violating the invariant \u2014 a *live* and *safe* net in Petri terminology.\n\n**Why the formalism earns its keep.** Drawn as two side-by-side flowcharts, the shared-resource constraint is invisible. Drawn as a Petri net, it is the single most important node in the diagram. That is the whole pitch: the model carries the property, and the renderer shows you which moves are currently legal.'
1681
+ },
1682
+ {
1683
+ "slug": "petri-producer-consumer",
1684
+ "diagram": "petri",
1685
+ "title": "Producer / consumer with a bounded buffer",
1686
+ "description": "The bounded-buffer producer/consumer pattern as a Petri net \u2014 complementary free/used slot places enforce the buffer size, a timed withdraw transition models the consumer's rate, and place capacity caps the buffer at three slots. Tokens flowing through free\u2192used\u2192free show backpressure as a structural property.",
1687
+ "standard": "Murata 1989 + Marsan 1995 (GSPN timed transitions)",
1688
+ "tags": [
1689
+ "petri",
1690
+ "concurrency",
1691
+ "producer-consumer",
1692
+ "bounded-buffer",
1693
+ "queueing"
1694
+ ],
1695
+ "complexity": 3,
1696
+ "featured": false,
1697
+ "dsl": 'petri "Producer / Consumer (bounded buffer)"\n place pReady *1 "producer ready"\n place free *3 "free slots"\n place used capacity: 3 "used slots"\n place cReady *1 "consumer ready"\n transition produce "deposit"\n transition consume timed rate: 0.8 "withdraw"\n pReady -> produce\n free -> produce\n produce -> used\n produce -> pReady\n used -> consume\n cReady -> consume\n consume -> free\n consume -> cReady',
1698
+ "notes": "A bounded buffer is where queueing theory meets concurrency, and the Petri net version makes the backpressure mechanism explicit instead of hiding it in a counter variable.\n\n**Free and used slots are complementary places.** The `free` place starts with three tokens (three empty slots); `used` starts empty and is capped at `capacity: 3` (the dashed border). Every `produce` firing moves one token free\u2192used; every `consume` firing moves one back used\u2192free. Their sum is invariant \u2014 the buffer can never hold more than three items, and that bound is a structural property of the net, not a runtime check.\n\n**A timed consumer.** `consume` is a `timed` transition with a rate (\u03BB = 0.8), rendered as a hollow box \u2014 the [GSPN](https://en.wikipedia.org/wiki/Stochastic_Petri_net) convention for a transition that fires after a stochastic delay. That is how you model a consumer that drains the buffer at a finite rate, the seed of any performance analysis.\n\n**Backpressure for free.** When `free` is empty (buffer full), `produce` loses its input token and is no longer enabled \u2014 the producer blocks until the consumer frees a slot. No `if buffer.full` branch; the net's enabling rule *is* the backpressure."
1699
+ },
1700
+ {
1701
+ "slug": "petri-workflow-net",
1702
+ "diagram": "petri",
1703
+ "title": "Order-fulfilment workflow net (WF-net)",
1704
+ "description": "A van der Aalst workflow net \u2014 a single source place and single sink place bracket an AND-split/join that runs picking and invoicing concurrently before shipping. The engine detects the WF-net structure and reports it in the SVG description.",
1705
+ "standard": "van der Aalst 1998 workflow nets (a Murata place/transition net)",
1706
+ "tags": [
1707
+ "petri",
1708
+ "workflow",
1709
+ "wf-net",
1710
+ "and-split",
1711
+ "business-process"
1712
+ ],
1713
+ "complexity": 2,
1714
+ "featured": false,
1715
+ "dsl": 'petri "Order workflow"\n place in *1 "received"\n transition split\n place pick\n place invoice\n transition pack\n transition bill\n place packed\n place billed\n transition ship\n place out "shipped"\n in -> split\n split -> pick\n split -> invoice\n pick -> pack\n invoice -> bill\n pack -> packed\n bill -> billed\n packed -> ship\n billed -> ship\n ship -> out',
1716
+ "notes": "A *workflow net* is the Petri-net dialect business-process people actually use: exactly one **source** place (no incoming arcs \u2014 where a case enters) and one **sink** place (no outgoing arcs \u2014 where it leaves). Schematex detects this structure and notes it in the diagram's `<desc>`.\n\n**Concurrency by construction.** `split` forks the order into a `pick` branch and an `invoice` branch \u2014 warehouse and finance work in parallel. `ship` is the AND-join: it needs both `packed` and `billed`, so an order can't ship before it's been both packed and billed. No status flags, no polling \u2014 the net enforces the ordering.\n\n**Why not a flowchart?** A flowchart would draw the same boxes but couldn't express \"these two run concurrently and rejoin\" without ambiguity. The Petri net's token flow makes the AND-split/join unambiguous and analysable. This is the same reduction BPMN uses internally."
1717
+ },
1236
1718
  {
1237
1719
  "slug": "phylo-bacterial-diversity",
1238
1720
  "diagram": "phylo",
@@ -1251,6 +1733,93 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1251
1733
  "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"',
1252
1734
  "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.'
1253
1735
  },
1736
+ {
1737
+ "slug": "phylo-sars-cov-2-chronogram",
1738
+ "diagram": "phylo",
1739
+ "title": "SARS-CoV-2 variant chronogram",
1740
+ "description": "Chronogram-style phylogenetic tree for major SARS-CoV-2 variants with clade highlights and a year scale.",
1741
+ "standard": "Newick / phylogram conventions",
1742
+ "tags": [
1743
+ "phylo",
1744
+ "chronogram",
1745
+ "newick",
1746
+ "variants",
1747
+ "clades"
1748
+ ],
1749
+ "complexity": 3,
1750
+ "featured": false,
1751
+ "dsl": 'phylo "SARS-CoV-2 Variants" [mode: chronogram, layout: rectangular, mrsd: "2023"]\n newick: "((Alpha:0.45,(Beta:0.35,Gamma:0.38):0.12):0.25,(Delta:0.42,(BA1:0.28,(BA2:0.18,XBB:0.12):0.1):0.22):0.3);"\n clade VOC = (Alpha, Beta, Gamma, Delta) [color: "#1E88E5", label: "Early VOCs", highlight: branch]\n clade Omicron = (BA1, BA2, XBB) [color: "#E53935", label: "Omicron lineages", highlight: both]\n scale "years before 2023"',
1752
+ "notes": "## Scenario\n\nChronograms use branch lengths as time. For variant surveillance, that means the horizontal distance conveys approximate divergence time rather than only genetic distance.\n\n## Annotation key\n\n- `mode: chronogram` and `mrsd` tell the renderer how to interpret branch lengths.\n- `clade` highlights named groups from the Newick leaves.\n- `scale` labels the time axis."
1753
+ },
1754
+ {
1755
+ "slug": "pid-distillation-column-reboiler-condenser",
1756
+ "diagram": "pid",
1757
+ "title": "Distillation column with reboiler and condenser",
1758
+ "description": "P&ID for a distillation overhead loop with condenser, reflux drum, reflux pump, reboiler, and ISA instrument bubbles.",
1759
+ "standard": "ANSI/ISA-5.1 + ISO 10628",
1760
+ "tags": [
1761
+ "pid",
1762
+ "distillation",
1763
+ "column",
1764
+ "condenser",
1765
+ "reboiler",
1766
+ "instrumentation"
1767
+ ],
1768
+ "complexity": 3,
1769
+ "featured": false,
1770
+ "dsl": 'pid "Distillation Column T-201"\n\nequip T-201 : column_tray [tag: "T-201"]\nequip E-201 : condenser [tag: "Overhead Condenser"]\nequip D-201 : vessel_h [tag: "Reflux Drum"]\nequip P-201 : pump_centrifugal [tag: "Reflux Pump"]\nequip E-202 : reboiler [tag: "Reboiler"]\nequip PSV-201 : valve_psv [tag: "PSV-201", set_pressure: "150 psig"]\n\nline L1 from T-201.top to E-201.shell_in [size: "8in", service: "overhead vapor", type: "process"]\nline L2 from E-201.shell_out to D-201.in [size: "8in", service: "condensate", type: "process"]\nline L3 from D-201.bottom to P-201.in [size: "3in", service: "reflux", type: "process"]\nline L4 from P-201.out to T-201.reflux [size: "3in", service: "reflux", type: "process"]\nline L5 from T-201.bottom to E-202.in [size: "6in", service: "bottoms", type: "process"]\nline L6 from T-201.top to PSV-201.in [size: "2in", service: "relief", type: "process_minor"]\n\ninst PT-201 : field_discrete\n measures T-201\ninst LIC-201 : cr_shared\n measures D-201\ninst TIC-201 : cr_shared\n measures T-201',
1771
+ "notes": "## Scenario\n\nDistillation columns are a canonical P&ID use case because the physical process and the control loops must be legible together. This example shows the main process path and three instrument bubbles without pretending to be a full plant drawing.\n\n## Annotation key\n\n- `equip` lines select ISO process equipment symbols.\n- `line ... from ... to ...` connects equipment ports and carries line tags.\n- `inst` declarations create ISA-5.1 instrument bubbles tied to measured equipment."
1772
+ },
1773
+ {
1774
+ "slug": "pid-pump-flow-control",
1775
+ "diagram": "pid",
1776
+ "title": "Pump with flow control loop (P&ID)",
1777
+ "description": "Classic centrifugal pump pulling from an atmospheric tank, with a flow transmitter (FT-101), flow indicating controller (FIC-101), and a fail-closed pneumatic control valve \u2014 the minimum viable P&ID that every process engineer recognises at a glance.",
1778
+ "standard": "ANSI/ISA-5.1-2009 + ISO 10628-1:2014",
1779
+ "tags": [
1780
+ "pid",
1781
+ "isa-5.1",
1782
+ "flow-control",
1783
+ "centrifugal-pump",
1784
+ "control-valve",
1785
+ "instrument-loop"
1786
+ ],
1787
+ "complexity": 1,
1788
+ "featured": true,
1789
+ "dsl": 'pid "Water Pump Flow Control"\n\nequip T-101 : tank_atm [tag: "Feed Tank"]\nequip P-101 : pump_centrifugal [tag: "Feed Pump"]\nequip V-101 : valve_control [actuator: "diaphragm", fail: "FC"]\nequip V-100 : valve_gate [tag: "Isolation"]\n\nline L1 from T-101.bottom to P-101.in [size: "4\\"", service: "water", type: "process"]\nline L2 from P-101.out to V-101.in [size: "4\\"", service: "water", type: "process"]\nline L3 from V-101.out to dest [size: "4\\"", type: "process"]\n\ninst FT-101 : field_discrete\n measures L2\ninst FIC-101 : cr_shared\n controls V-101\n\nline s1 from FT-101 to FIC-101 [type: "electric"]\nline s2 from FIC-101 to V-101 [type: "pneumatic"]',
1790
+ "notes": 'The pump-with-flow-control loop is to P&ID what the "hello world" program is to software: every process engineer has drawn one, every piping textbook uses it as chapter one, and once you understand it you understand the grammar of every more complex diagram.\n\n**What the diagram shows.** T-101 is an atmospheric tank (dome-roof symbol, open to atmosphere). The centrifugal pump P-101 pulls fluid from the bottom nozzle and pushes it downstream through a 4-inch water service line. The flow transmitter FT-101 \u2014 a field-mounted discrete instrument, drawn as a plain circle per ISA-5.1 \xA74.1 \u2014 sits on the discharge line L2 and measures the volumetric flow rate. Its 4\u201320 mA electric signal travels to FIC-101, the flow indicating controller in the main control room (drawn as a circle with a horizontal line through it, indicating it is panel-mounted and has a display). FIC-101 computes the error between the setpoint and the measured flow and drives V-101, a diaphragm-actuated control valve, open or closed via a pneumatic signal. V-100 is a manual gate valve upstream \u2014 the isolation valve you close when you need to pull the pump for maintenance.\n\n**ISA-5.1 signal line types.** The two non-process lines in this diagram are drawn differently from the main pipe: the electric signal between FT-101 and FIC-101 is a dashed line (`stroke-dasharray: 6 4`), and the pneumatic signal between FIC-101 and V-101 is a solid line with small perpendicular slash marks at regular intervals. These are not stylistic choices \u2014 they are ISA-5.1 \xA75.2 mandatory line type codes. An instrument technician reading the diagram needs to know, at a glance, whether a run of wire failed or an air supply pressure dropped. The line type tells them which.\n\n**Fail-safe position.** V-101 is marked `fail: "FC"` \u2014 fail-closed. When the air supply to the diaphragm actuator is lost (instrument air header trips, tubing rupture), the spring return pushes the valve shut. This is the correct failure mode for a pump discharge valve: loss of control should stop flow, not open it fully. If this were a cooling-water valve on a reactor, you\'d want fail-open instead. The choice of fail position is one of the first decisions in a safety instrumented system review (HAZOP, SIL assessment) and it belongs on the P&ID from day one.\n\n**Why not draw.io.** Process engineers currently draw P&IDs in AutoCAD P&ID, SmartPlant P&ID, or draw.io with stencil libraries \u2014 all of which require manual symbol placement and wiring. The Schematex DSL lets you describe the topology and generate a standards-compliant SVG. This is particularly useful when a process is described in prose (from a process description document or a licensor\'s PFD) and needs to be turned into a P&ID for HAZOP review \u2014 the conversion becomes a text transformation task that LLMs can assist with, producing a reviewable first draft rather than a blank canvas.'
1791
+ },
1792
+ {
1793
+ "slug": "pid-reactor-feed-system",
1794
+ "diagram": "pid",
1795
+ "title": "Reactor feed with multi-loop control and pressure safety (P&ID)",
1796
+ "description": "CSTR reactor system with centrifugal pump, shell-and-tube pre-heater, flow control on the feed line, temperature control on the product outlet, and a PSHH pressure switch interlock \u2014 four instrument loops in one diagram, covering the core vocabulary of ISA-5.1 P&ID engineering.",
1797
+ "standard": "ANSI/ISA-5.1-2009 + ISO 10628-1:2014",
1798
+ "tags": [
1799
+ "pid",
1800
+ "isa-5.1",
1801
+ "reactor",
1802
+ "heat-exchanger",
1803
+ "multi-loop",
1804
+ "pshh",
1805
+ "interlock",
1806
+ "safety"
1807
+ ],
1808
+ "complexity": 3,
1809
+ "featured": false,
1810
+ "dsl": 'pid "High-Pressure Reactor Feed"\n\nequip T-201 : tank_atm [tag: "Raw Material Tank"]\nequip P-201 : pump_centrifugal [tag: "Feed Pump P-201A/B"]\nequip E-201 : hx_shell_tube [tag: "Feed Pre-heater"]\nequip R-201 : reactor_cstr [tag: "Reactor R-201"]\nequip V-201 : valve_control [actuator: "diaphragm", fail: "FC"]\nequip V-202 : valve_control [actuator: "diaphragm", fail: "FO"]\nequip V-203 : valve_psv [set_pressure: "150 psig"]\n\nline L1 from T-201.bottom to P-201.in [size: "6\\"", service: "feed", type: "process"]\nline L2 from P-201.out to E-201.shell_in [size: "6\\"", service: "feed", type: "process"]\nline L3 from E-201.shell_out to V-201.in [size: "6\\"", service: "feed", type: "process"]\nline L4 from V-201.out to R-201.in [size: "6\\"", service: "feed", type: "process"]\nline L5 from R-201.out to V-202.in [size: "4\\"", service: "product", type: "process"]\n\ninst FT-201 : field_discrete\n measures L2\ninst FIC-201 : cr_shared\n controls V-201\n\ninst TT-201 : field_discrete\n measures R-201\ninst TIC-201 : cr_shared\n controls V-202\n\ninst PT-201 : field_discrete\n measures R-201\ninst PSHH-201 : field_discrete\n measures R-201\n\nline s1 from FT-201 to FIC-201 [type: "electric"]\nline s2 from FIC-201 to V-201 [type: "pneumatic"]\nline s3 from TT-201 to TIC-201 [type: "electric"]\nline s4 from TIC-201 to V-202 [type: "pneumatic"]\nline s5 from PT-201 to PSHH-201 [type: "electric"]',
1811
+ "notes": `Real process plants don't have one control loop \u2014 they have a web of them. This diagram shows a CSTR reactor system with four instrument loops and a safety instrument, which is about the minimum complexity for a unit operation that would appear in a HAZOP study. Understanding how to read this diagram cold is a core skill for every process, instrumentation, and safety engineer on the project team.
1812
+
1813
+ **The process path.** Raw material is stored in T-201 (atmospheric tank). The centrifugal pump P-201A/B (the A/B suffix is conventional for spared pumps \u2014 one online, one standby) pulls from the bottom nozzle and pushes through 6-inch feed line L2 to the shell-and-tube heat exchanger E-201, which pre-heats the feed before it enters the reactor. From E-201, the line continues through the feed control valve V-201 into the reactor R-201. The reactor product exits through L5 and the product control valve V-202.
1814
+
1815
+ **Loop 201 \u2014 flow control.** FT-201 (flow transmitter, field-mounted) sits on the pump discharge line L2 and sends a 4\u201320 mA signal to FIC-201 (flow indicating controller, control-room mounted, DCS shared \u2014 note the horizontal line through the circle). FIC-201 closes or opens V-201 to maintain the feed flow setpoint. V-201 is fail-closed: if instrument air is lost, the feed to the reactor stops. Starving a reactor on air loss is usually safer than flooding it.
1816
+
1817
+ **Loop 201T \u2014 temperature control.** TT-201 (temperature transmitter) measures the reactor body temperature and signals TIC-201, which throttles V-202 \u2014 the product outlet valve \u2014 to regulate residence time and therefore heat generation in the reactor. V-202 is fail-open: losing air means the product continues to drain out, preventing dangerous temperature accumulation inside the vessel. The fail-safe position is always chosen by answering the question: "which state causes less harm if control is lost?"
1818
+
1819
+ **Loop 201P \u2014 pressure monitoring and safety.** PT-201 is a field-mounted pressure transmitter \u2014 it sends the continuous pressure reading to the DCS historian. PSHH-201 is a pressure switch, high-high: a discrete field-mounted device that trips at the maximum allowable working pressure (MAWP). It is wired to the safety interlock system (SIS), not the DCS. The distinction matters: DCS loops control the process; SIS loops protect equipment and people. OSHA PSM (29 CFR 1910.119) requires the two to be functionally independent. V-203 is the pressure safety valve \u2014 a spring-loaded valve that opens automatically at the set pressure of 150 psig regardless of any control signal, providing the last line of mechanical protection.
1820
+
1821
+ **Why P&ID, not PFD.** A process flow diagram (PFD) shows the same equipment but only the major process streams, mass balances, and operating conditions. A P&ID adds every instrument, every valve, every signal line, and every utility connection \u2014 it is the engineering document used by instrument engineers to write I/O lists, by safety engineers to perform HAZOP, and by construction teams to verify field installation. The DSL lets you build this level of detail from text, making it tractable for AI-assisted first drafts and version-controlled review cycles.`
1822
+ },
1254
1823
  {
1255
1824
  "slug": "prisma-dual-pipeline",
1256
1825
  "diagram": "prisma",
@@ -1289,6 +1858,101 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1289
1858
  "dsl": "prisma\nmode: 2020-single\ntitle: Effect of exercise on chronic low-back pain \u2014 SR\n\nidentification:\n databases:\n n: 1418\n sources: PubMed=600, Embase=450, Cochrane=184, Web of Science=184\n duplicates-removed: 318\n\nscreening:\n records-screened: 1100\n excluded:\n n: 870\n reasons: irrelevant title=750, non-English=120\n\neligibility:\n full-text-assessed: 230\n excluded:\n n: 195\n reasons: wrong population=80, wrong intervention=60, wrong outcome=55\n\nincluded:\n studies: 35\n reports: 38",
1290
1859
  "notes": '## Scenario\n\nA research librarian produces the PRISMA 2020 flow diagram for a Cochrane review submission. The journal **requires** the diagram in the exact four-row structure \u2014 Identification \u2192 Screening \u2192 Eligibility \u2192 Included \u2014 with the count `(n = \u2026)` shown in every box and the excluded boxes itemizing reasons. With the dedicated `prisma` engine the librarian writes only counts and reasons; the rigid layout, the "Records removed before screening" side-box, and the exclusion side-boxes are produced automatically and are correct by construction.\n\n## Annotation key\n\n- **`mode: 2020-single`** \u2014 one Identification column (databases & registers). Use `mode: 2020-dual` to add the "other methods" column.\n- **`identification.databases`** \u2014 `n:` is the mandatory total; `sources:` renders the per-database breakdown; `duplicates-removed:` splits out into the right-column "Records removed before screening" box automatically.\n- **`screening` / `eligibility` `excluded:` blocks** \u2014 each has its own `n:` total and an optional `reasons:` breakdown rendered in a side-box to the right, connected by a horizontal arrow.\n- **`included.studies` / `reports`** \u2014 one study can yield several reports, so both counts render.\n\n## How to read\n\nTop to bottom mirrors the PRISMA 2020 template exactly. The left capsule bands label the three canonical stages; the orange bar spans the Identification column group. Counts reconcile across stages (1418 \u2212 318 = 1100 screened; 1100 \u2212 870 = 230 assessed; 230 \u2212 195 = 35 included) \u2014 the engine warns if they don\'t.\n\n## Standard reference\n\nPage MJ, McKenzie JE, Bossuyt PM, et al. *The PRISMA 2020 statement: an updated guideline for reporting systematic reviews.* BMJ 2021;372:n71. Template: [prisma-statement.org/prisma-2020-flow-diagram](https://www.prisma-statement.org/prisma-2020-flow-diagram).'
1291
1860
  },
1861
+ {
1862
+ "slug": "sequence-combined-fragments-gallery",
1863
+ "diagram": "sequence",
1864
+ "title": "Combined fragments gallery",
1865
+ "description": "UML sequence diagram demonstrating alt, opt, loop, par, critical, and ref frames in one realistic API interaction.",
1866
+ "standard": "OMG UML 2.5.1",
1867
+ "tags": [
1868
+ "sequence",
1869
+ "uml",
1870
+ "combined-fragments",
1871
+ "alt",
1872
+ "loop",
1873
+ "par"
1874
+ ],
1875
+ "complexity": 3,
1876
+ "featured": false,
1877
+ "dsl": 'sequence "API request with combined fragments"\n autonumber 1 1\n actor Client\n boundary API as "API Gateway"\n control Auth\n control Orders\n database DB\n queue Events\n\n Client ->+ API : POST /orders\n ref over API, Auth : Validate token\n alt [token valid]\n API -> Auth : checkScopes()\n Auth --> API : scopes\n opt [idempotency key present]\n API -> DB : lookupRequest(key)\n DB --> API : previous result?\n end\n loop (1,3)\n API -> Orders : reserveInventory()\n Orders --> API : reservation result\n end\n par\n API ->> Events : OrderRequested\n and\n API -> DB : persistOrder()\n end\n critical\n DB --> API : commit ok\n API -->- Client : 201 Created\n end\n else [token invalid]\n API -->- Client : 401 Unauthorized\n end',
1878
+ "notes": "## Scenario\n\nThis is a compact tour of UML sequence constructs that matter in real service diagrams. The request validates auth, checks idempotency, retries a reservation, publishes asynchronously, persists state, and returns a response.\n\n## Annotation key\n\n- `alt`, `opt`, `loop`, `par`, and `critical` are first-class combined fragments.\n- `ref over API, Auth` references a reusable interaction instead of inlining it.\n- `->>` distinguishes asynchronous event publication from blocking calls."
1879
+ },
1880
+ {
1881
+ "slug": "sequence-login-flow",
1882
+ "diagram": "sequence",
1883
+ "title": "Login flow with an alt fragment",
1884
+ "description": "An authentication handshake across an actor, a Jacobson control object, and a database lifeline \u2014 synchronous calls open activation bars, a dashed reply returns the row, and an alt combined fragment splits the valid/invalid branches. A note records the session-cookie side effect.",
1885
+ "standard": "OMG UML 2.5.1 \xA717 (Interactions)",
1886
+ "tags": [
1887
+ "sequence",
1888
+ "uml",
1889
+ "authentication",
1890
+ "alt-fragment",
1891
+ "activation",
1892
+ "lifeline-kinds"
1893
+ ],
1894
+ "complexity": 2,
1895
+ "featured": true,
1896
+ "dsl": 'sequence "Login flow"\n actor User\n participant Web as "Web App"\n control Auth\n database DB\n User -> Web : submit(credentials)\n activate Web\n Web ->+ Auth : verify(credentials)\n Auth ->+ DB : SELECT user\n DB --> Auth : row\n deactivate DB\n alt [credentials valid]\n Auth --> Web : token\n Web --> User : 200 OK\n else [invalid]\n Auth --> Web : 401\n Web --> User : error\n end\n deactivate Auth\n deactivate Web\n note over User, Web : session cookie set',
1897
+ "notes": 'The canonical "who calls whom, in what order" diagram \u2014 and a good tour of the features that separate a UML sequence diagram from a generic flow.\n\n**Lifeline kinds carry meaning.** `actor`, `control`, and `database` render their distinct UML/Jacobson heads (stick figure, circled arrow, cylinder), so the diagram tells you *what kind of thing* each participant is, not just its name.\n\n**Activation and replies.** `->+` opens an activation bar on the callee; `--` draws the dashed reply that returns control (and the `row`). Bars nest, so you can see `Auth` is active while it waits on `DB`.\n\n**Branching is a real fragment.** The `alt` block with its `[credentials valid]` / `[invalid]` guards is a UML *combined fragment*, drawn as a labelled box spanning the lifelines \u2014 not two separate arrows you have to mentally group.'
1898
+ },
1899
+ {
1900
+ "slug": "sequence-microservices-saga",
1901
+ "diagram": "sequence",
1902
+ "title": "Microservices order saga",
1903
+ "description": "A distributed order-processing saga showing the parts of UML sequence notation that generic tools omit \u2014 a ref interaction-use frame, a par fragment for concurrent service calls, asynchronous event-bus messages, and an alt fragment with a compensating rollback on payment failure.",
1904
+ "standard": "OMG UML 2.5.1 \xA717 (Interactions)",
1905
+ "tags": [
1906
+ "sequence",
1907
+ "uml",
1908
+ "microservices",
1909
+ "saga",
1910
+ "async",
1911
+ "event-driven"
1912
+ ],
1913
+ "complexity": 3,
1914
+ "featured": false,
1915
+ "dsl": 'sequence "Order processing (saga)"\n actor Customer\n participant Gateway as "API Gateway"\n control Orders as "Order Service"\n participant Payment as "Payment Service"\n participant Inventory as "Inventory Service"\n queue Bus as "Event Bus"\n\n Customer -> Gateway : POST /orders\n Gateway ->+ Orders : createOrder()\n ref over Orders, Bus : Validate cart & price\n\n par\n Orders ->> Payment : charge(card)\n and\n Orders ->> Inventory : reserve(items)\n end\n\n alt [payment captured && stock reserved]\n Payment --> Orders : paid\n Inventory --> Orders : reserved\n Orders ->> Bus : OrderConfirmed\n Orders -->- Gateway : 201 Created\n Gateway --> Customer : confirmation\n else [payment failed]\n Orders ->> Inventory : release(items)\n Orders ->> Bus : OrderCancelled\n Orders -->- Gateway : 402 Payment Required\n Gateway --> Customer : declined\n end',
1916
+ "notes": "This is the case that separates a real UML engine from a flowchart with stick figures. A saga is concurrent, asynchronous, and has a compensating path \u2014 and Schematex has dedicated notation for each.\n\n**`ref` keeps the diagram composable.** \"Validate cart & price\" is its own interaction; inlining it here would bury the saga in detail. The `ref over Orders, Bus : \u2026` frame references it instead \u2014 notation most text-to-diagram tools simply don't have.\n\n**`par` shows true concurrency.** The order service charges the card and reserves stock *at the same time*. The `par` fragment's two operands say these run concurrently, not in sequence \u2014 a distinction a top-to-bottom message list cannot express.\n\n**Async vs. reply arrows are not cosmetic.** `->>` (open head) marks fire-and-forget calls and event-bus publishes; `-->` (dashed) marks the replies that come back. Reading the arrowheads tells you which steps block and which don't.\n\n**The compensation lives in the `alt`.** When payment fails, the second operand *releases* the previously reserved inventory and emits `OrderCancelled` \u2014 the saga's rollback, drawn right beside the happy path instead of in a separate diagram."
1917
+ },
1918
+ {
1919
+ "slug": "sequence-oauth-login",
1920
+ "diagram": "sequence",
1921
+ "title": "OAuth 2.0 authorization-code login",
1922
+ "description": "The canonical browser-based sign-in handshake as a UML sequence diagram \u2014 redirect to the auth server, user consent, code-for-token exchange, and an alt fragment for the success vs. failure branch, with activation bars tracking who is busy at each step.",
1923
+ "standard": "OMG UML 2.5.1 \xA717 (Interactions)",
1924
+ "tags": [
1925
+ "sequence",
1926
+ "uml",
1927
+ "oauth",
1928
+ "authentication",
1929
+ "api"
1930
+ ],
1931
+ "complexity": 2,
1932
+ "featured": true,
1933
+ "dsl": 'sequence "OAuth 2.0 Authorization Code"\n actor User\n boundary Browser\n control App as "Web App"\n participant Auth as "Auth Server"\n database DB as "User Store"\n\n User -> Browser : click "Sign in"\n Browser ->+ App : GET /login\n App --> Browser : 302 \u2192 Auth Server\n Browser ->+ Auth : GET /authorize\n Auth --> User : consent screen\n User -> Auth : approve\n Auth -->- Browser : 302 + auth code\n\n Browser ->+ App : GET /callback?code\n App ->+ Auth : POST /token (code)\n Auth ->+ DB : load user\n DB -->- Auth : profile\n Auth -->- App : access + refresh token\n alt [token exchange ok]\n App --> Browser : Set-Cookie: session\n Browser --> User : signed in\n else [exchange failed]\n App --> Browser : 401 Unauthorized\n Browser --> User : retry\n end\n deactivate App',
1934
+ "notes": "Almost every product ships this flow, and almost every whiteboard drawing of it is subtly wrong about *who is active when*. A sequence diagram makes the call order and the active spans explicit.\n\n**Lifeline kinds carry meaning.** The `boundary` Browser, the `control` App, and the `entity`/`database` user store render as their UML symbols, so a reviewer reads the architecture at a glance: the browser is the UI edge, the app orchestrates, the auth server and store are collaborators.\n\n**Activation bars track the redirect dance.** OAuth bounces the user between the app and the auth server twice. The `+`/`-` suffixes open and close execution-specification bars exactly where each party becomes busy and hands control back \u2014 the `Auth -->- Browser` reply both returns the auth code *and* closes the auth server's first activation.\n\n**The branch is a real fragment, not two diagrams.** The `alt` frame captures the success and failure paths in one place: a valid token exchange sets the session cookie; a failed one returns `401`. That is the single most common place sequence diagrams earn their keep \u2014 showing the unhappy path next to the happy one instead of hiding it."
1935
+ },
1936
+ {
1937
+ "slug": "sequence-object-lifecycle",
1938
+ "diagram": "sequence",
1939
+ "title": "Object lifecycle \u2014 create, found, lost, destroy",
1940
+ "description": "The four UML interaction lifecycle markers in one short diagram \u2014 a create message draws its arrow to the new participant's box, a found message starts from a filled circle, a lost message ends at one, and destroy terminates a lifeline with an \u2715.",
1941
+ "standard": "OMG UML 2.5.1 \xA717 (Interactions)",
1942
+ "tags": [
1943
+ "sequence",
1944
+ "uml",
1945
+ "create",
1946
+ "destroy",
1947
+ "found-message",
1948
+ "lost-message",
1949
+ "lifecycle"
1950
+ ],
1951
+ "complexity": 1,
1952
+ "featured": false,
1953
+ "dsl": "sequence\n participant Factory\n Factory -> *Worker : \xABcreate\xBB\n o-> Worker : external trigger\n Worker -x : fire-and-forget\n destroy Worker",
1954
+ "notes": 'UML interactions model not just messages but the *birth and death* of participants \u2014 the part most text-to-diagram tools skip. This tiny diagram shows all four markers.\n\n**Create.** `Factory -> *Worker` \u2014 the `*` prefix creates `Worker`; its arrow lands on the *side of the new participant\'s box*, which appears partway down rather than at the top, the UML convention for "instantiated here."\n\n**Found and lost.** `o-> Worker` is a *found* message \u2014 it starts from a filled circle, meaning "from outside the modelled scope" (an external trigger). `Worker -x` is a *lost* message \u2014 it ends at a circle, a fire-and-forget whose receiver isn\'t shown.\n\n**Destroy.** `destroy Worker` terminates the lifeline with the \u2715 marker, so the diagram shows exactly when the object ceases to exist.'
1955
+ },
1292
1956
  {
1293
1957
  "slug": "sfc-bake-cool-concurrent",
1294
1958
  "diagram": "sfc",
@@ -1345,6 +2009,43 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1345
2009
  "dsl": 'sfc "Order routing"\n\nvar ProductOrdered: bool\nvar IsExpressShipping: bool\nvar IsStandardShipping: bool\nvar Shipped: bool\n\nstep S0 [initial]\n\nstep S_Pick\n N PickFromBin\n\nalt from: S_Pick:\n branch [priority: 1]:\n transition: IsExpressShipping\n step S_Express\n N PrepExpressBox\n transition: TRUE\n branch [priority: 2]:\n transition: IsStandardShipping\n step S_Standard\n N PrepStandardBox\n transition: TRUE\nmerge_to: S_Ship\n\nstep S_Ship\n N CarrierPickup\n\ntransition from: S0 to: S_Pick: ProductOrdered\ntransition from: S_Ship to: S0: Shipped',
1346
2010
  "notes": "A modern fulfillment center handles both express (next-day) and standard (3\u20135 day) shipping on the same physical line, with the same picking robots and the same carrier-pickup conveyor. The only divergence is the boxing step: express boxes are different sizes, use rigid corrugate, and get extra tracking labels. SFC's alternative branch is the right shape for this: one step (picking) splits into two paths (express boxing OR standard boxing) and they converge back to a common end step (carrier pickup).\n\n**Single horizontal bar = alternative.** IEC 61131-3 \xA76.5.4 uses a **single horizontal line** for alternative (OR-semantic) divergence and convergence. Visually it's the simpler cousin of the double-bar simultaneous fork; semantically it's the opposite \u2014 only *one* branch fires per scan, picked by which entry transition's condition evaluates true first under the priority order.\n\n**Priority annotation matters.** Both branches have `[priority: N]` markers \u2014 express is priority 1, standard is priority 2. If both `IsExpressShipping` and `IsStandardShipping` were somehow true at the same time (a bug, but a common one in early integration), the priority forces the express path. The renderer puts a small red number near each branch entry to make the priority visible at review time.\n\n**Per-branch entry and exit transitions.** Inside each branch, there are two `transition: ...` lines \u2014 the first is the **entry transition** (fires when control reaches the divergence bar; renders between the bar and the first step in that branch), and the second is the **exit transition** (fires when the branch's last step completes; renders between that step and the convergence bar). Most exit transitions are `TRUE` (unconditional) because the *step* itself is what's gating the work \u2014 once the step completes, you want to leave the branch. The entry transitions are where the actual decision logic lives.\n\n**Why not flowchart?** A flowchart of this would render the routing as a diamond with two branches and labels on each branch. You'd lose the explicit step semantics \u2014 in flowchart, the diamond *is* the decision point and there's no notion of \"the picking step is currently active and the next step depends on shipping type.\" That distinction matters for PLC code: while the picking step is active, the picking robot is physically holding the product, and you need to know exactly when the routing decision happens (at step exit, not step entry) to coordinate the conveyor handoff. SFC's \"step active \u2192 transition \u2192 next step active\" semantics are what real PLC scan engines do; flowchart's diamond/box semantics are not.\n\n**The shared S_Ship destination.** Both branches converge to S_Ship, where `CarrierPickup` runs. This is one of the strengths of alternative-branch SFC: the post-routing step is shared infrastructure, drawn exactly once. If you moved to multiple destinations (e.g. express has its own carrier door, standard has another), you'd skip the convergence and let each branch end at its own terminal step \u2014 also a valid SFC pattern."
1347
2011
  },
2012
+ {
2013
+ "slug": "sld-commercial-solar-pv",
2014
+ "diagram": "sld",
2015
+ "title": "Commercial solar PV interconnection",
2016
+ "description": "Single-line diagram for a commercial PV array feeding a combiner, inverter, AC disconnect, production meter, and main switchboard.",
2017
+ "standard": "IEEE 315 / NEC 690",
2018
+ "tags": [
2019
+ "sld",
2020
+ "solar",
2021
+ "pv",
2022
+ "inverter",
2023
+ "interconnection"
2024
+ ],
2025
+ "complexity": 2,
2026
+ "featured": false,
2027
+ "dsl": 'sld "Commercial PV Interconnection"\nPV1 = solar [rating: "250 kWdc", label: "PV Array"]\nCMB = hub [rating: "600 Vdc", label: "DC Combiner"]\nDISC_DC = switch_load [rating: "600 Vdc", label: "DC Disconnect"]\nINV = load [rating: "200 kWac", label: "Grid-tie Inverter"]\nDISC_AC = switch_load [rating: "480V 400A", label: "AC Disconnect"]\nMTR = watthour_meter [label: "Production Meter"]\nMSB = bus [voltage: "480V", label: "Main Switchboard"]\nUTIL = utility [voltage: "480V", label: "Utility"]\n\nPV1 -> CMB [cable: "PV string conductors"]\nCMB -> DISC_DC\nDISC_DC -> INV\nINV -> DISC_AC\nDISC_AC -> MTR\nMTR -> MSB\nUTIL -> MSB',
2028
+ "notes": "## Scenario\n\nCommercial PV one-lines have a recognizable chain: DC array, combiner, inverter, AC disconnect, meter, and point of interconnection. This example uses standard SLD equipment while preserving PV-specific ratings in labels.\n\n## Annotation key\n\n- `solar` marks the generation source.\n- `hub` stands in for the combiner point.\n- `watthour_meter` captures the production metering requirement."
2029
+ },
2030
+ {
2031
+ "slug": "sld-data-center-2n-ups",
2032
+ "diagram": "sld",
2033
+ "title": "Data center 2N UPS single-line",
2034
+ "description": "Single-line diagram for a dual-path data center power train with utility feeds, UPS A/B, static transfer switch, and critical load panels.",
2035
+ "standard": "IEEE 315 / Uptime Institute topology convention",
2036
+ "tags": [
2037
+ "sld",
2038
+ "data-center",
2039
+ "ups",
2040
+ "2n",
2041
+ "sts",
2042
+ "critical-power"
2043
+ ],
2044
+ "complexity": 3,
2045
+ "featured": false,
2046
+ "dsl": 'sld "Data Center 2N UPS"\nUTIL_A = utility [voltage: "13.8kV", label: "Utility A"]\nUTIL_B = utility [voltage: "13.8kV", label: "Utility B"]\nXFMR_A = transformer [rating: "2500 kVA", voltage: "13.8kV/480V", label: "TX-A"]\nXFMR_B = transformer [rating: "2500 kVA", voltage: "13.8kV/480V", label: "TX-B"]\nSWGR_A = bus [voltage: "480V", label: "Switchgear A"]\nSWGR_B = bus [voltage: "480V", label: "Switchgear B"]\nUPS_A = ups [rating: "1 MW", label: "UPS A"]\nUPS_B = ups [rating: "1 MW", label: "UPS B"]\nSTS = ats [rating: "1600A", label: "Static Transfer Switch"]\nPDU_A = load [label: "PDU A"]\nPDU_B = load [label: "PDU B"]\nCRIT = load [label: "Critical IT Load"]\n\nUTIL_A -> XFMR_A\nUTIL_B -> XFMR_B\nXFMR_A -> SWGR_A\nXFMR_B -> SWGR_B\nSWGR_A -> UPS_A\nSWGR_B -> UPS_B\nUPS_A -> STS\nUPS_B -> STS\nSTS -> PDU_A\nSTS -> PDU_B\nPDU_A -> CRIT\nPDU_B -> CRIT',
2047
+ "notes": "## Scenario\n\nData centers are sold on redundancy. A 2N power path has two independent sources and UPS chains so either side can carry the critical load. A one-line diagram makes that redundancy auditable.\n\n## Annotation key\n\n- Two utility and transformer paths stay separate until the transfer point.\n- `ups` nodes show the energy-storage conversion stage.\n- The transfer switch feeds two PDUs before the critical IT load."
2048
+ },
1348
2049
  {
1349
2050
  "slug": "sld-generator-ats",
1350
2051
  "diagram": "sld",
@@ -1432,6 +2133,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1432
2133
  "dsl": 'sld "13.8 kV Substation"\nutility = utility [label: "Grid 138 kV"]\nxfmr1 = transformer [kva: 15000, primary: 138, secondary: 13.8]\nbus_hv = bus [voltage: 138]\nbus_mv = bus [voltage: 13.8]\nbrk1 = breaker [amps: 1200]\nbrk2 = breaker [amps: 1200]\nbrk3 = breaker [amps: 1200]\nfeeder1 = load [label: "Feeder 1"]\nfeeder2 = load [label: "Feeder 2"]\nfeeder3 = load [label: "Feeder 3"]\nutility -> bus_hv\nbus_hv -> xfmr1\nxfmr1 -> bus_mv\nbus_mv -> brk1\nbrk1 -> feeder1\nbus_mv -> brk2\nbrk2 -> feeder2\nbus_mv -> brk3\nbrk3 -> feeder3',
1433
2134
  "notes": '## Scenario\n\nA power systems engineer documents a distribution substation design for a utility interconnection application or a facility\'s electrical permit drawings. The single-line diagram is the first deliverable in any power system project \u2014 required by IEEE, NFPA 70E, and utility interconnection standards before detailed engineering begins.\n\n## Annotation key\n\n- `utility = utility [label: "..."]` \u2014 utility supply source (three-phase symbol)\n- `[type: transformer, kva:..., primary:..., secondary:...]` \u2014 step-down transformer with rated kVA and voltage levels\n- `[type: bus, voltage:...]` \u2014 horizontal bus bar at the specified voltage level\n- `[type: breaker, amps:...]` \u2014 rated circuit breaker\n- `[type: load, label:...]` \u2014 load or feeder destination\n- `->` \u2014 directed power path from source to load\n\n## How to read\n\nThe 138 kV grid source feeds the high-voltage bus, which connects to the primary of the 15 MVA step-down transformer. The transformer secondary feeds the 13.8 kV medium-voltage bus. Three 1200 A circuit breakers fan out from the MV bus to three distribution feeders \u2014 each breaker isolates its feeder independently.'
1434
2135
  },
2136
+ {
2137
+ "slug": "sociogram-criminal-network",
2138
+ "diagram": "sociogram",
2139
+ "title": "Criminal communication network",
2140
+ "description": "Investigative sociogram of a covert organization \u2014 weighted command ties between the principal and lieutenants, directed courier and associate links to external contacts, and a weak tie that hints at an unconfirmed connection. The force-directed layout surfaces the command hierarchy and the bridging couriers.",
2141
+ "standard": "Moreno 1934",
2142
+ "tags": [
2143
+ "force-directed",
2144
+ "weighted-ties",
2145
+ "directed",
2146
+ "bridging",
2147
+ "command-hierarchy"
2148
+ ],
2149
+ "complexity": 2,
2150
+ "featured": false,
2151
+ "dsl": 'sociogram "Operation Sunset - Communication Network"\n config: layout = force-directed\n boss [label: "Subject Alpha"]\n lt1 [label: "Lieutenant 1"]\n lt2 [label: "Lieutenant 2"]\n courier1 [label: "Courier A"]\n courier2 [label: "Courier B"]\n contact1 [label: "External Contact 1"]\n contact2 [label: "External Contact 2"]\n associate1 [label: "Associate 1"]\n associate2 [label: "Associate 2"]\n boss <-> lt1 [weight: 4]\n boss <-> lt2 [weight: 4]\n lt1 -> courier1\n lt1 -> courier2\n lt2 -> associate1\n lt2 -> associate2\n courier1 -> contact1 [label: "supplier"]\n courier2 -> contact2 [label: "distributor"]\n lt1 <-> lt2 [weight: 2]\n associate1 -.- courier1',
2152
+ "notes": "## Scenario\n\nA link-analysis unit reconstructs a covert organization from intercept metadata. The sociogram is the standard deliverable: who talks to whom, how strongly, and in which direction \u2014 so the analyst can identify the principal, the lieutenants who insulate him, and the couriers who bridge the organization to outside suppliers.\n\n## Annotation key\n\n- `<-> [weight: N]` \u2014 a reciprocal, weighted tie; the heavier `weight: 4` command links between `boss` and the two lieutenants render thicker than the `weight: 2` peer link\n- `->` \u2014 a directed contact (one party initiates), used for the courier and associate tasking lines\n- `-> [label: ...]` \u2014 a directed tie carrying a role annotation (`supplier`, `distributor`)\n- `-.-` \u2014 a weak/unconfirmed tie, drawn dashed: `associate1 -.- courier1` flags a suspected but unverified connection\n\n## How to read\n\nSubject Alpha sits at the center, tied strongly to both lieutenants but never directly to the couriers or external contacts \u2014 the classic insulation pattern. Lieutenant 1 controls both couriers, each of whom reaches a single external contact, so the couriers are the bridging nodes whose removal would sever the outside supply. The dashed `associate1 -.- courier1` tie is the lead worth developing: an unconfirmed cross-link between the two lieutenants' sub-networks."
2153
+ },
1435
2154
  {
1436
2155
  "slug": "sociogram-playground-dynamics",
1437
2156
  "diagram": "sociogram",
@@ -1468,6 +2187,64 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1468
2187
  "dsl": 'sociogram "Engineering team \u2014 informal influence"\n config: layout = force-directed\n group leads [label: "Tech leads", color: "#1976D2"]\n alex; sam\n group sr [label: "Senior ICs", color: "#66BB6A"]\n priya; jordan; kim; tao\n group jr [label: "Junior", color: "#FFA726"]\n lee; ravi; nina; dev\n alex <-> sam\n alex -> priya\n sam -> jordan\n priya <-> kim\n jordan <-> tao\n kim -> lee\n priya -> ravi\n tao -> nina\n dev -.- lee\n nina -.- priya',
1469
2188
  "notes": "## Scenario\n\nAn engineering manager runs an informal network analysis survey (\"Who do you go to when you're stuck?\") and maps the results to identify knowledge hubs, bridging individuals between seniority tiers, and team members who are drifting toward isolation before performance reviews surface the issue.\n\n## Annotation key\n\n- `group id [label:..., color:...]` \u2014 assigns individuals to organizational tiers, color-coded\n- `<->` \u2014 mutual influence; both nominated each other\n- `->` \u2014 one-way influence nomination\n- `-.-` \u2014 weak tie; neither party nominated the other in the survey\n- The force-directed layout clusters mutual-nomination groups and separates isolates\n\n## How to read\n\nAlex and Sam (tech leads) are mutually influential. Alex bridges down to Priya, Sam to Jordan \u2014 healthy knowledge flow across tiers. Priya and Kim form a strong senior IC hub. Dev and Nina have only weak ties (--. to the network), suggesting integration risk. Dev's only connection is a weak tie to Lee \u2014 a coaching opportunity before the next performance cycle."
1470
2189
  },
2190
+ {
2191
+ "slug": "state-checkout-composite-history",
2192
+ "diagram": "state",
2193
+ "title": "Checkout session with composite state and history",
2194
+ "description": "UML statechart for a checkout session with a composite Payment state, entry and exit activities, guards, and retry transitions.",
2195
+ "standard": "OMG UML 2.5.1",
2196
+ "tags": [
2197
+ "statechart",
2198
+ "uml",
2199
+ "composite",
2200
+ "guard",
2201
+ "checkout"
2202
+ ],
2203
+ "complexity": 3,
2204
+ "featured": false,
2205
+ "dsl": "stateDiagram-v2 [direction: LR]\n\n[*] --> Cart\nCart --> Checkout : begin_checkout\nCheckout --> Payment : submit_shipping [addressValid] / createIntent()\nCheckout --> Cart : edit_cart\nPayment --> Confirmed : captured / sendReceipt()\nPayment --> Checkout : failed [retries < 3] / showError()\nPayment --> Cancelled : failed [retries >= 3]\nConfirmed --> [*]\nCancelled --> [*]\n\nstate Payment {\n entry / startPaymentTimer()\n exit / stopPaymentTimer()\n [*] --> Authorizing\n Authorizing --> Capturing : authorized\n Authorizing --> Failed : declined\n Capturing --> Captured : capture_ok\n Capturing --> Failed : capture_error\n}\n\nnote right of Payment : Composite state hides gateway detail",
2206
+ "notes": "## Scenario\n\nCheckout is a lifecycle, not a process list: the session can move forward, retry, or terminate depending on events and guards. A composite state keeps the external lifecycle readable while still showing the internal payment substates.\n\n## Annotation key\n\n- `state Payment { ... }` creates the composite state.\n- `entry /` and `exit /` activities document side effects on state entry and exit.\n- Transition labels use the UML `trigger [guard] / action` format."
2207
+ },
2208
+ {
2209
+ "slug": "state-ecommerce-order",
2210
+ "diagram": "state",
2211
+ "title": "E-commerce order lifecycle (state diagram)",
2212
+ "description": "Full order state machine \u2014 from Pending through payment routing (choice pseudo-state), composite Processing state with Picking/Packing/Shipped sub-states, delivery, refund, and cancellation paths. Demonstrates composite states, choice pseudo-states, guard conditions, entry actions, and UML notes.",
2213
+ "standard": "OMG UML 2.5.1 \xA714",
2214
+ "tags": [
2215
+ "state",
2216
+ "uml",
2217
+ "composite",
2218
+ "choice",
2219
+ "guard",
2220
+ "order-management",
2221
+ "e-commerce",
2222
+ "lifecycle"
2223
+ ],
2224
+ "complexity": 2,
2225
+ "featured": false,
2226
+ "dsl": 'state "E-Commerce Order Lifecycle"\n\ninitial i\ni -> Pending\n\nPending -> Confirmed : place_order [items_in_stock] / reserveInventory()\nPending -> Cancelled : cancel\n\nchoice PayRoute\nConfirmed -> PayRoute : pay\nPayRoute -> Processing : [method == "card"]\nPayRoute -> Processing : [method == "wallet"]\nPayRoute -> AwaitingTransfer : [method == "bank_transfer"]\n\nAwaitingTransfer -> Processing : transfer_received [amount_correct]\nAwaitingTransfer -> Cancelled : transfer_timeout\n\ncomposite Processing {\n initial pi\n final pf\n\n pi -> Picking\n Picking -> Packing : picked / updateWarehouse()\n Packing -> Shipped : label_printed\n Shipped -> pf : carrier_confirmed\n}\n\nProcessing -> Delivered : delivered / notifyCustomer()\nProcessing -> Failed : fulfillment_error\n\nDelivered -> Refunded : return_request [within_30_days] / initiateRefund()\nFailed -> Pending : retry [attempt < 3]\nFailed -> Cancelled : retry [attempt >= 3]\n\nfinal f\nDelivered -> f\nRefunded -> f\nCancelled -> f\n\nnote right_of AwaitingTransfer : SLA: 48 h before timeout.',
2227
+ "notes": 'Order state machines are one of the most common backend design artifacts, and one of the most commonly under-specified. Teams often start with a simple enum (`PENDING / PAID / SHIPPED / DELIVERED`) and then bolt on edge cases over time: partial shipments, payment holds, carrier errors, refund windows. Six months later the enum has twelve values, the transition logic is scattered across three services, and nobody can explain what sequence of events gets an order from `FAILED` to `CANCELLED` versus from `FAILED` back to `PENDING`. A UML state diagram catches all of this upfront.\n\n**Guard conditions on `place_order`.** The transition from `Pending` to `Confirmed` carries `[items_in_stock]` \u2014 a guard. Guards are boolean predicates that must be true for the transition to fire even when the trigger event (`place_order`) occurs. If the guard is false, the trigger is silently absorbed and the system stays in `Pending`. In practice this means inventory is checked synchronously during order placement, and the transition only proceeds if stock is available. The action `/ reserveInventory()` fires when the transition does go through, atomically.\n\n**Choice pseudo-state for payment routing.** `PayRoute` is a UML choice pseudo-state (drawn as a diamond). When the `pay` trigger fires from `Confirmed`, the machine immediately evaluates the guards on all outgoing transitions from `PayRoute`. If `method == "card"` or `method == "wallet"`, control goes directly to `Processing`. If `method == "bank_transfer"`, it goes to `AwaitingTransfer` \u2014 a waiting state with its own 48-hour SLA note. Choice pseudo-states do not dwell; they route. This is the correct model for "take different paths based on a value computed at runtime."\n\n**Composite state for fulfillment.** `Processing` is a composite state \u2014 it contains its own internal state machine with `Picking`, `Packing`, and `Shipped`. From the outside, the system is "in Processing" whether it\'s picking, packing, or confirming with the carrier. From the inside, the sub-machine tracks exactly where the warehouse is. The composite state has its own `initial` and `final` pseudo-states: the machine enters at `pi` (starts picking) and exits through `pf` (carrier confirmed), which fires the outer transition to `Delivered`. Cross-composite transitions to `Failed` are also valid \u2014 a fulfillment error at any sub-state terminates the Processing phase and moves to the outer `Failed` state.\n\n**Retry with bounded attempts.** `Failed` has two outgoing transitions back to `Pending` and `Cancelled`, both triggered by `retry` but guarded on `attempt`. This is the explicit model for "retry up to N times, then give up." Without the state diagram, this logic typically lives in a cron job or a dead-letter queue processor that nobody fully understands. Here it is the specification: anyone reading the diagram knows the retry policy without digging through queue configurations.\n\n**Final state convergence.** `Delivered`, `Refunded`, and `Cancelled` all transition to the same `final` pseudo-state. In implementation, "final" might mean the order record is archived, the event bus receives an `order.closed` event, and no further state transitions are accepted. The model is silent on what happens after final \u2014 that\'s intentional. The state machine is done; downstream processes (analytics, accounting, data retention) are separate concerns.'
2228
+ },
2229
+ {
2230
+ "slug": "state-traffic-light",
2231
+ "diagram": "state",
2232
+ "title": "Traffic light (state diagram)",
2233
+ "description": "A three-state finite state machine for a traffic signal \u2014 Red, Green, Yellow \u2014 with timer-driven transitions and a power_off exit to a final state. Introduces UML initial and final pseudo-states, transition labels, and the cyclic structure that makes state diagrams the right tool for reactive systems.",
2234
+ "standard": "OMG UML 2.5.1 \xA714",
2235
+ "tags": [
2236
+ "state",
2237
+ "uml",
2238
+ "fsm",
2239
+ "state-machine",
2240
+ "embedded",
2241
+ "reactive"
2242
+ ],
2243
+ "complexity": 1,
2244
+ "featured": true,
2245
+ "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',
2246
+ "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."
2247
+ },
1471
2248
  {
1472
2249
  "slug": "timeline-company-milestones",
1473
2250
  "diagram": "timeline",
@@ -1486,6 +2263,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1486
2263
  "dsl": 'timeline "Acme \u2014 First Five Years"\nconfig: style = lollipop\n\n2020-06: "Founders meet at Y Combinator" [side: below]\n2020-11: milestone "Incorporation + pre-seed $1M" [side: above, color: #1565C0]\n2021-04: "First engineer hired" [side: below]\n2021-09: milestone "Product beta \u2014 50 design partners" [side: above, color: #2E7D32]\n2022-03: milestone "Seed round \u2014 $6M" [side: above, color: #1565C0]\n2022-11: "Team reaches 20 people" [side: below]\n2023-05: milestone "Platform v1 GA" [side: above, color: #6A1B9A]\n2023-10: milestone "Series A \u2014 $22M" [side: above, color: #1565C0]\n2024-06: "100th enterprise customer" [side: below]\n2025-01: milestone "Platform v2 launched" [side: above, color: #6A1B9A]',
1487
2264
  "notes": '## Scenario\n\nThe founder drops this into the first page of the fundraising deck. Funding rounds, product GAs, and growth markers alternate above/below the axis, which makes the parallel story \u2014 "we raised capital and shipped on time" \u2014 visible in one glance. Reviewers who only read the top of the page still get the two-line story.\n\n## Annotation key\n\n- `style = lollipop` \u2014 dot-on-stick markers alternating above/below axis\n- `milestone` \u2014 diamond marker for headline events\n- `[side: above|below]` \u2014 explicit placement\n- `[color: #hex]` \u2014 colour-code category (fundraising / product / team)\n\n## How to read\n\nTime runs left to right. Each marker is a single dated event; *milestone* markers are the diamond-shaped headline items (fundraising, GAs). Colour carries category: blue = fundraising, purple = product, green = early commercial traction. Events below the axis are supporting context (people, growth stats); events above are the announceable headlines.'
1488
2265
  },
2266
+ {
2267
+ "slug": "timeline-litigation-case",
2268
+ "diagram": "timeline",
2269
+ "title": "Litigation case timeline",
2270
+ "description": "Lollipop litigation timeline with pleadings, discovery, motions, settlement conference, and trial milestones.",
2271
+ "standard": "Legal case chronology convention",
2272
+ "tags": [
2273
+ "timeline",
2274
+ "litigation",
2275
+ "lollipop",
2276
+ "legal",
2277
+ "milestones"
2278
+ ],
2279
+ "complexity": 2,
2280
+ "featured": false,
2281
+ "dsl": 'timeline "Smith v. Acme - Case Timeline"\nconfig: style = lollipop\n\n2025-01-10: milestone "Complaint filed" [side: above, color: #1565C0]\n2025-02-07: "Answer due" [side: below]\n2025-03-01 - 2025-06-30: "Fact discovery" [category: "discovery"]\n2025-04-15: "Plaintiff deposition" [side: above]\n2025-05-22: "Expert reports exchanged" [side: below]\n2025-07-15: milestone "Summary judgment motion" [side: above, color: #C62828]\n2025-08-20: "Mediation" [side: below]\n2025-10-06: milestone "Trial setting" [side: above, color: #2E7D32]',
2282
+ "notes": "## Scenario\n\nLitigation timelines mix hard deadlines, long phases, and high-stakes milestones. The lollipop style keeps single events readable while still allowing discovery to appear as a range.\n\n## Annotation key\n\n- `config: style = lollipop` selects the narrative timeline style.\n- Date ranges render as spans.\n- `milestone` emphasizes critical procedural events."
2283
+ },
1489
2284
  {
1490
2285
  "slug": "timeline-product-launch",
1491
2286
  "diagram": "timeline",
@@ -1504,6 +2299,24 @@ If the LED doesn't light up, three things to check, in order: LED polarity (the
1504
2299
  "dsl": 'timeline "Platform v2 Launch"\nconfig: style = gantt\n\n2025-07-01 - 2025-08-15: "Engineering build" [category: "engineering"]\n2025-07-15 - 2025-08-31: "Design polish" [category: "design"]\n2025-08-01 - 2025-09-10: "Marketing collateral" [category: "marketing"]\n2025-08-20: milestone "Feature freeze" [color: #E53935]\n2025-08-20 - 2025-09-05: "QA hardening" [category: "engineering"]\n2025-09-01 - 2025-09-12: "Press embargo outreach" [category: "marketing"]\n2025-09-15: milestone "Public launch" [color: #2E7D32]',
1505
2300
  "notes": '## Scenario\n\nThe launch PM shares this in weekly exec status. Overlapping bars show where workstreams parallelize (design polishing while engineering still builds) and the feature-freeze diamond makes the handoff between build and QA unmissable. The second milestone (public launch) anchors the entire timeline and is the reason every other bar exists.\n\n## Annotation key\n\n- `DATE - DATE: "Label"` \u2014 range (bar) event\n- `DATE: milestone "Label"` \u2014 point milestone (diamond)\n- `[category: \u2026]` \u2014 group colour in the gantt legend\n- `[color: #hex]` \u2014 explicit marker colour\n\n## How to read\n\nTime flows left to right. Horizontal bars are continuous work; diamonds are instantaneous events. Overlapping bars mean two teams are working simultaneously \u2014 fine, so long as they coordinate. The red *Feature freeze* marks the transition from net-new work to hardening; any engineering bar extending past it needs an exception. The green *Public launch* is the terminal milestone every other bar is serving.'
1506
2301
  },
2302
+ {
2303
+ "slug": "timing-i2c-read-burst",
2304
+ "diagram": "timing",
2305
+ "title": "I2C read burst timing",
2306
+ "description": "WaveDrom-compatible timing diagram for an I2C register read with address, repeated start, data bytes, ACK, and NACK phases.",
2307
+ "standard": "WaveDrom signal notation",
2308
+ "tags": [
2309
+ "timing",
2310
+ "i2c",
2311
+ "waveform",
2312
+ "bus",
2313
+ "embedded"
2314
+ ],
2315
+ "complexity": 2,
2316
+ "featured": false,
2317
+ "dsl": 'timing "I2C Read Burst" [hscale: 1.2]\n[Control]\nSCL: pppppppppppp\nSTART: 100000100001\n---\n[Bus]\nSDA: x==========x data: ["ADDR+W","ACK","REG","ACK","REP START","ADDR+R","ACK","DATA0","ACK","DATA1","NACK"]\n---\n[Status]\nBUSY: 111111111110',
2318
+ "notes": "## Scenario\n\nI2C reads are visually clearer as timing diagrams than as prose. The repeated start and ACK/NACK phases are where firmware and hardware teams most often talk past each other.\n\n## Annotation key\n\n- `[Control]`, `[Bus]`, and `[Status]` group related signals.\n- `p` renders clock pulses.\n- `=` segments carry bus labels from the `data:` list."
2319
+ },
1507
2320
  {
1508
2321
  "slug": "timing-spi-transaction",
1509
2322
  "diagram": "timing",
@@ -1657,7 +2470,7 @@ var SYNTAX = {
1657
2470
  },
1658
2471
  "decisiontree": {
1659
2472
  "title": "Decision tree diagram",
1660
- "content": '## 1. Your first decision tree\n\nThe smallest useful decision tree: a root question with two branches.\n\n```\ndecisiontree "Laptop troubleshoot"\n\nquestion "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```\ndecisiontree "Triage \u2014 chest pain onset"\n\nq "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```\ndecisiontree "Pain level triage"\n\nquestion "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```\ndecisiontree:decision "Cloud vendor selection"\n\ndecision "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```\ndecisiontree:ml "Iris classification (CART)"\ndirection: top-down\nimpurity: gini\n\nsplit "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---'
2473
+ "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---'
1661
2474
  },
1662
2475
  "flowchart": {
1663
2476
  "title": "Flowchart",
@@ -1689,28 +2502,32 @@ var SYNTAX = {
1689
2502
  },
1690
2503
  "erd": {
1691
2504
  "title": "ERD (Entity-Relationship Diagram)",
1692
- "content": '## 1. Your first ERD\n\nThe smallest useful ERD: a parent table referenced by a child via foreign key.\n\n```\nerd\ntable Customer {\n customer_id int PK\n email varchar UK\n}\ntable Order {\n order_id int PK\n customer_id int FK -> Customer.customer_id\n}\nref Order.customer_id many-mandatory -- one-mandatory Customer.customer_id : "places"\n```\n\nFour rules cover 80 % of usage:\n\n1. Start with `erd`. Optional headers `title:`, `direction: LR | TB`.\n2. Each table is a block: `table Name { col type marker }`. Markers are `PK`, `FK`, `UK`, `NN` (or `*`) for NOT NULL.\n3. Inline foreign-key target: append `FK -> Other.column` to a column. The renderer adds an `FK` pill automatically.\n4. Connect tables with a `ref` line: `ref Source <left-card> -- <right-card> Target [: "label"]`. Cardinality is one of `one-mandatory`, `one-optional`, `many-mandatory`, `many-optional` (named form), `1..1` / `0..1` / `1..N` / `0..N` (Min-Max), or Mermaid glyphs (`}o--||`, etc.) as alias.\n\n> Comments use `//` or `#`. Block forms can be either multi-line (one column per line) or inline (`table A { id int PK; name varchar }`).\n\n---\n\n## 2. Cardinality glyphs\n\nCrow\'s foot encodes the cardinality at each end of the relationship line:\n\n| Glyph | Reading | Min..Max | Named token |\n|---|---|---|---|\n| `\u2500\u2503` (perpendicular bar) | Exactly one (mandatory one) | 1..1 | `one-mandatory` |\n| `\u2500\u25CB` (open circle) | Zero or one (optional one) | 0..1 | `one-optional` |\n| `\u2500\u2503<` (bar + crow\'s foot) | One or more (mandatory many) | 1..N | `many-mandatory` |\n| `\u2500\u25CB<` (circle + crow\'s foot) | Zero or more (optional many) | 0..N | `many-optional` |\n\nEach end of a line is annotated independently. A typical 1:N relationship reads "exactly one CUSTOMER places zero-or-more ORDERs":\n\n```\nref Order.customer_id many-mandatory -- one-mandatory Customer.customer_id : "places"\n```\n\nReading right-to-left: the **right end** of the line attaches to Customer with a single bar (one-mandatory); the **left end** attaches to Order with a bar + crow\'s foot (many-mandatory).\n\n---\n\n## 3. Mermaid alias\n\nIf you already use Mermaid `erDiagram`, the same glyphs work as input:\n\n```\nerd\ntable Customer { customer_id int PK; email varchar UK }\ntable Order { order_id int PK; customer_id int FK -> Customer.customer_id }\ntable Product { product_id int PK; name varchar }\n\nref Order }o--|| Customer : "places"\nref Order }o--o{ Product : "contains"\n```\n\n| Mermaid token | Schematex meaning |\n|---|---|\n| `\\|o` left / `o\\|` right | 0..1 |\n| `\\|\\|` | 1..1 |\n| `}o` left / `o{` right | 0..N |\n| `}\\|` left / `\\|{` right | 1..N |\n| `--` | identifying / solid line |\n| `..` | non-identifying / dashed line |\n\n---\n\n## 4. Identifying vs non-identifying\n\nUse `--` for identifying relationships (solid line) and `..` for non-identifying (dashed):\n\n```\nerd\ntable User { id int PK; email varchar }\ntable SessionLog { id int PK; user_id int FK -> User.id; created_at timestamp }\n\nref SessionLog.user_id many-optional .. one-optional User.id : "logs (optional FK)"\n```\n\nThe dashed line follows the Barker / IDEF1X non-identifying convention.\n\n---\n\n## 5. Composite primary keys (associative tables)\n\nAssociative ("junction") tables resolve M:N relationships. Mark each PK + FK column with both `PK` and `FK`:\n\n```\nerd\ntable Student { student_id int PK; name varchar }\ntable Course { course_id int PK; title varchar }\ntable Enrollment {\n student_id int PK FK -> Student.student_id\n course_id int PK FK -> Course.course_id\n grade char\n}\nref Enrollment.student_id many-mandatory -- one-mandatory Student.student_id\nref Enrollment.course_id many-mandatory -- one-mandatory Course.course_id\n```\n\nSchematex renders both pills (PK + FK) on the same row. The PK underline applies to the column name when any `PK` marker is present.\n\n---\n\n## 6. Layout direction\n\n`direction: LR` (default) places parent tables left, child tables right. `direction: TB` flips to top-to-bottom \u2014 useful for narrow embeds:\n\n```\nerd\ntitle: "Library (top-down)"\ndirection: TB\n\ntable Member { id int PK; name varchar; email varchar UK }\ntable Book { id int PK; title varchar; author varchar }\ntable Loan { id int PK; member_id int FK -> Member.id; book_id int FK -> Book.id; due_date date }\n\nref Loan.member_id many-mandatory -- one-mandatory Member.id : "borrowed by"\nref Loan.book_id many-mandatory -- one-mandatory Book.id : "of"\n```\n\nLayered orthogonal Manhattan routing with single-bend edges. v0.1 uses appearance-order stacking inside layers; barycenter crossing-reduction is deferred to v0.2.\n\n---\n\n## 7. Notation modes\n\nThe DSL header can pin the notation:\n\n```\nerd\nnotation: crowsfoot // default \u2014 only mode supported in v0.1\n```\n\n`notation: chen` (rectangle-diamond-oval, weak entities, ternary, ISA) and `notation: barker` (per-half dashed) are documented in `27-ERD-STANDARD.md` but rejected by the parser today. Targets v0.2.\n\n---\n\n## 8. Limitations of v0.1\n\n- **Crow\'s foot only.** Chen and Barker rendering deferred.\n- **No edge-crossing minimization.** Layered placement uses declaration order within layers; large ERDs (10+ tables, dense FKs) will show some crossings. Reorder `table` blocks if a specific layout matters.\n- **No self-referential C-loops.** Recursive relationships (`Employee.manager_id -> Employee.id`) parse but route as straight orthogonal lines, not the canonical C-shape.\n- **No M:N auto-resolution.** Express the associative entity explicitly (this is the standard practice in production schemas anyway).\n\nFor the full standard reference, see `docs/reference/27-ERD-STANDARD.md`.'
2505
+ "content": '## 1. Your first ERD\n\nThe smallest useful ERD: a parent table referenced by a child via foreign key.\n\n```\nerd\ntable Customer {\n customer_id int PK\n email varchar UK\n}\ntable Order {\n order_id int PK\n customer_id int FK -> Customer.customer_id\n}\nref Order.customer_id many-mandatory -- one-mandatory Customer.customer_id : "places"\n```\n\nFour rules cover 80 % of usage:\n\n1. Start with `erd`. Optional headers `title:`, `direction: LR | TB`.\n2. Each table is a block: `table Name { col type marker }`. Markers are `PK`, `FK`, `UK`, `NN` (or `*`) for NOT NULL.\n3. Inline foreign-key target: append `FK -> Other.column` to a column. The renderer adds an `FK` pill automatically.\n4. Connect tables with a `ref` line: `ref Source <left-card> -- <right-card> Target [: "label"]`. Cardinality is one of `one-mandatory`, `one-optional`, `many-mandatory`, `many-optional` (named form), `1..1` / `0..1` / `1..N` / `0..N` (Min-Max), or Mermaid glyphs (`}o--||`, etc.) as alias.\n\n> Comments use `//` or `#`. Block forms can be either multi-line (one column per line) or inline (`table A { id int PK; name varchar }`).\n\n---\n\n## 2. Cardinality glyphs\n\nCrow\'s foot encodes the cardinality at each end of the relationship line:\n\n| Glyph | Reading | Min..Max | Named token |\n|---|---|---|---|\n| `\u2500\u2503` (perpendicular bar) | Exactly one (mandatory one) | 1..1 | `one-mandatory` |\n| `\u2500\u25CB` (open circle) | Zero or one (optional one) | 0..1 | `one-optional` |\n| `\u2500\u2503<` (bar + crow\'s foot) | One or more (mandatory many) | 1..N | `many-mandatory` |\n| `\u2500\u25CB<` (circle + crow\'s foot) | Zero or more (optional many) | 0..N | `many-optional` |\n\nEach end of a line is annotated independently. A typical 1:N relationship reads "exactly one CUSTOMER places zero-or-more ORDERs":\n\n```\nref Order.customer_id many-mandatory -- one-mandatory Customer.customer_id : "places"\n```\n\nReading right-to-left: the **right end** of the line attaches to Customer with a single bar (one-mandatory); the **left end** attaches to Order with a bar + crow\'s foot (many-mandatory).\n\n---\n\n## 3. Mermaid alias\n\nIf you already use Mermaid `erDiagram`, the same glyphs work as input:\n\n```\nerd\ntable Customer { customer_id int PK; email varchar UK }\ntable Order { order_id int PK; customer_id int FK -> Customer.customer_id }\ntable Product { product_id int PK; name varchar }\n\nref Order }o--|| Customer : "places"\nref Order }o--o{ Product : "contains"\n```\n\n| Mermaid token | Schematex meaning |\n|---|---|\n| `\\|o` left / `o\\|` right | 0..1 |\n| `\\|\\|` | 1..1 |\n| `}o` left / `o{` right | 0..N |\n| `}\\|` left / `\\|{` right | 1..N |\n| `--` | identifying / solid line |\n| `..` | non-identifying / dashed line |\n\n---\n\n## 4. Identifying vs non-identifying\n\nUse `--` for identifying relationships (solid line) and `..` for non-identifying (dashed):\n\n```\nerd\ntable User { id int PK; email varchar }\ntable SessionLog { id int PK; user_id int FK -> User.id; created_at timestamp }\n\nref SessionLog.user_id many-optional .. one-optional User.id : "logs (optional FK)"\n```\n\nThe dashed line follows the Barker / IDEF1X non-identifying convention.\n\n---\n\n## 5. Composite primary keys (associative tables)\n\nAssociative ("junction") tables resolve M:N relationships. Mark each PK + FK column with both `PK` and `FK`:\n\n```\nerd\ntable Student { student_id int PK; name varchar }\ntable Course { course_id int PK; title varchar }\ntable Enrollment {\n student_id int PK FK -> Student.student_id\n course_id int PK FK -> Course.course_id\n grade char\n}\nref Enrollment.student_id many-mandatory -- one-mandatory Student.student_id\nref Enrollment.course_id many-mandatory -- one-mandatory Course.course_id\n```\n\nSchematex renders both pills (PK + FK) on the same row. The PK underline applies to the column name when any `PK` marker is present.\n\n---\n\n## 6. Layout direction\n\n`direction: LR` (default) places parent tables left, child tables right. `direction: TB` flips to top-to-bottom \u2014 useful for narrow embeds:\n\n```\nerd\ntitle: "Library (top-down)"\ndirection: TB\n\ntable Member { id int PK; name varchar; email varchar UK }\ntable Book { id int PK; title varchar; author varchar }\ntable Loan { id int PK; member_id int FK -> Member.id; book_id int FK -> Book.id; due_date date }\n\nref Loan.member_id many-mandatory -- one-mandatory Member.id : "borrowed by"\nref Loan.book_id many-mandatory -- one-mandatory Book.id : "of"\n```\n\nLayered orthogonal Manhattan routing with single-bend edges. v0.1 uses appearance-order stacking inside layers; barycenter crossing-reduction is deferred to v0.2.\n\n---\n\n## 7. Notation modes\n\nThe DSL header can pin the notation:\n\n```\nerd\nnotation: crowsfoot // default \u2014 only mode supported in v0.1\n```\n\n`notation: chen` (rectangle-diamond-oval, weak entities, ternary, ISA) and `notation: barker` (per-half dashed) are documented in `27-ERD-STANDARD.md` but rejected by the parser today. Targets v0.2.\n\n---\n\n## 8. Limitations of v0.1\n\n- **Crow\'s foot only.** Chen and Barker rendering deferred.\n- **No edge-crossing minimization.** Layered placement uses declaration order within layers; large ERDs (10+ tables, dense FKs) will show some crossings. Reorder `table` blocks if a specific layout matters.\n- **No self-referential C-loops.** Recursive relationships (`Employee.manager_id -> Employee.id`) parse but route as straight orthogonal lines, not the canonical C-shape.\n- **No M:N auto-resolution.** Express the associative entity explicitly (this is the standard practice in production schemas anyway).\n\nFor the full standard reference, see `docs/reference/27-ERD-STANDARD.md`.\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:'
1693
2506
  },
1694
2507
  "breadboard": {
1695
2508
  "title": "Breadboard / Physical Wiring",
1696
- "content": "## 1. Your first breadboard\n\nThree sections: a one-line `breadboard` header, a `parts` block, and a `wires` block. Optional `board:` and `title:` lines come right after the header.\n\n```\nbreadboard\nparts\n uno: mcu uno @beside-left\n r1: resistor 220 @5e..9e\n d1: led red @10e..10f\n\nwires\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D13 --yellow-- @5a\n @10j --black-- @-t1\n```\n\nEvery part is `id: kind [args] @placement`. Every wire is `<endpoint> --color-- <endpoint>`. That's the whole grammar.\n\n---\n\n## 2. Coordinates\n\nBreadboards have a 2D address grid. Schematex coordinates always start with `@`.\n\n| Form | Meaning | Example |\n|---|---|---|\n| `@<col><row>` | Main grid hole. Rows `a\u2013e` (top half), `f\u2013j` (bottom half). | `@5e`, `@12g` |\n| `@+t<col>` | Top **positive** rail (red stripe). | `@+t8` |\n| `@-t<col>` | Top **negative / GND** rail (blue stripe). | `@-t8` |\n| `@+b<col>` | Bottom positive rail. | `@+b14` |\n| `@-b<col>` | Bottom negative rail. | `@-b14` |\n| `@<a>..<b>` | Span \u2014 used in part placement (resistor, diode, LED). | `@5e..9e` |\n| `@beside-left` | Off-board placement for MCU boards. | `mcu uno @beside-left` |\n\nMini boards (`board: mini`) have **no power rails** \u2014 `@+t\u2026` / `@-b\u2026` are rejected by the parser.\n\n---\n\n## 3. Board sizes\n\n```\nbreadboard\nboard: half // default \u2014 30 columns, 400 tie-points, rails (continuous)\n```\n\n| Form | Tie points | Columns | Power rails |\n|---|---|---|---|\n| `mini` | 170 | 17 | none |\n| `half` (default) | 400 | 30 | continuous |\n| `full` | 830 | 63 | break at column 30/31 |\n\n> **Pitfall** \u2014 on full-size boards the rails break at the middle. If your circuit uses both halves you must jumper the rails together explicitly.\n\n---\n\n## 4. Parts catalog\n\nEach part is `id: <kind> [args] @<placement>`. The catalog covers the most common Arduino / ESP32 maker components:\n\n**Discrete components** (sit on the breadboard):\n\n| DSL | Args | Example |\n|---|---|---|\n| `resistor` | `value` (\u03A9; supports `k`/`M`) | `r1: resistor 220 @5e..9e` |\n| `led` | `color` (red/green/blue/yellow/white/orange) | `d1: led red @10e..10f` |\n| `cap-elec` | \u2014 | `c1: cap-elec @4e..4f` |\n| `cap-ceramic` | \u2014 | `c2: cap-ceramic @6e..6f` |\n| `diode` | \u2014 | `d2: diode @5e..8e` |\n| `button` | \u2014 | `btn: button @8e` |\n| `dip` | `pins=N` | `ic: dip pins=8 @4e` |\n| `header` | `pins=N` | `h1: header pins=4 @20a` |\n\n**Microcontroller boards** (placed beside / above / below the substrate):\n\n| DSL | Pin labels |\n|---|---|\n| `mcu uno` | `5V`, `3V3`, `GND`, `VIN`, `RST`, `D2\u2026D13`, `A0\u2026A5`, `RX`, `TX` |\n| `mcu nano` | Subset of Uno labels |\n| `mcu esp32` | `3V3`, `GND`, `VIN`, `GPIO2`, `GPIO4`, `GPIO5`, `GPIO12\u2026GPIO33` |\n| `mcu pico` | Same generic GPIO labels |\n\n**Sensors / displays / actuators** (modules sit on the breadboard with pin row anchored at the supplied coordinate):\n\n| DSL | Pins |\n|---|---|\n| `sensor hcsr04` | `VCC`, `TRIG`, `ECHO`, `GND` |\n| `sensor dht11` / `sensor dht22` | `VCC`, `DATA`, `GND` |\n| `display oled-ssd1306` | `GND`, `VCC`, `SCL`, `SDA` |\n| `display lcd-1602-i2c` | `GND`, `VCC`, `SDA`, `SCL` |\n| `module rotary-ky040` | `CLK`, `DT`, `SW`, `VCC`, `GND` |\n| `actuator servo-sg90` | `GND`, `VCC`, `SIG` |\n\nResistor color bands are decorated automatically from `value` \u2014 `220` \u2192 red-red-brown-gold, `10000` \u2192 brown-black-orange-gold.\n\n---\n\n## 5. Wires\n\nEvery wire connects two endpoints. An endpoint is either a **part pin** (`partId:pinName`) or a **breadboard coordinate** (`@\u2026`).\n\n```\nwires\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D9 --yellow-- @9c\n @9a --green-- @+t9\n```\n\n| Color | Conventional role |\n|---|---|\n| `red` | +V (5V, 3.3V, VCC) |\n| `black` / `blue` | GND |\n| `yellow` / `orange` / `green` / `white` / `purple` | Signal |\n| `brown` / `grey` | Arbitrary signal |\n\nColor is purely visual \u2014 the engine does not validate it against electrical role.\n\nFor visually crowded boards, `via @<coord>` lets you pin an intermediate hole that biases the B\xE9zier control points:\n\n```\nwires\n uno:D13 --yellow-- @9a via @8c\n```\n\nMost wires don't need `via` \u2014 the layout engine produces a natural arc on its own.\n\n---\n\n## 6. Sensor with pull-up resistor (DHT11 motif)\n\nThe iconic Arduino tutorial pattern: a 10 k\u03A9 pull-up between VCC and the sensor's data line.\n\n```\nbreadboard\nboard: half\ntitle: \"DHT11 + 10k\u03A9 pull-up\"\n\nparts\n uno: mcu uno @beside-left\n s1: sensor dht11 @6a\n r1: resistor 10000 @8e..14e\n\nwires\n s1:VCC --red-- @+t1\n s1:GND --black-- @-t1\n s1:DATA --yellow-- @8e\n @14e --red-- @+t14\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D2 --yellow-- @8a\n```\n\n---\n\n## 7. ESP32 + I\xB2C OLED\n\nESP32 runs at 3.3 V (not 5 V). I\xB2C convention: green = SDA, white = SCL.\n\n```\nbreadboard\nboard: half\ntitle: \"ESP32 + SSD1306 OLED I\xB2C\"\n\nparts\n esp: mcu esp32 @beside-left\n oled: display oled-ssd1306 @8a\n\nwires\n oled:GND --black-- @-t1\n oled:VCC --red-- @+t1\n oled:SCL --white-- @10c\n oled:SDA --green-- @11c\n esp:3V3 --red-- @+t1\n esp:GND --black-- @-t1\n esp:GPIO22 --white-- @10c\n esp:GPIO21 --green-- @11c\n```\n\n---\n\n## 8. Limitations of v0.1\n\n- **No leader-line callouts** \u2014 reference designators (R1, C2) are drawn near the part body. Off-board callout boxes are deferred.\n- **No `.fzz` import** \u2014 Schematex consumes only its own DSL; Fritzing files are not parsed.\n- **No simulation** \u2014 this is a renderer, not a Wokwi-style simulator. Component-value validation (Ohm's law, current limits) is out of scope.\n- **No PCB / schematic round-trip** \u2014 `breadboard` and `circuit` are independent engines. Authoring two views of the same prototype currently means writing two DSLs.\n- **Fixed parts catalog** \u2014 user-defined part types are deferred. v0.1 ships the maker-tutorial 80% catalog (resistors, LEDs, caps, DIPs, headers, four MCU families, six sensor / display / actuator modules).\n- **Power-rail break visual** \u2014 full-size boards mark the 30/31 break with a hole-wide gap; the rail stripes still draw through the gap (cosmetic)."
2509
+ "content": "## 1. Your first breadboard\n\nThree sections: a one-line `breadboard` header, a `parts` block, and a `wires` block. Optional `board:` and `title:` lines come right after the header.\n\n```\nbreadboard\nparts\n uno: mcu uno @beside-left\n r1: resistor 220 @5e..9e\n d1: led red @10e..10f\n\nwires\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D13 --yellow-- @5a\n @10j --black-- @-t1\n```\n\nEvery part is `id: kind [args] @placement`. Every wire is `<endpoint> --color-- <endpoint>`. That's the whole grammar.\n\n---\n\n## 2. Coordinates\n\nBreadboards have a 2D address grid. Schematex coordinates always start with `@`.\n\n| Form | Meaning | Example |\n|---|---|---|\n| `@<col><row>` | Main grid hole. Rows `a\u2013e` (top half), `f\u2013j` (bottom half). | `@5e`, `@12g` |\n| `@+t<col>` | Top **positive** rail (red stripe). | `@+t8` |\n| `@-t<col>` | Top **negative / GND** rail (blue stripe). | `@-t8` |\n| `@+b<col>` | Bottom positive rail. | `@+b14` |\n| `@-b<col>` | Bottom negative rail. | `@-b14` |\n| `@<a>..<b>` | Span \u2014 used in part placement (resistor, diode, LED). | `@5e..9e` |\n| `@beside-left` | Off-board placement for MCU boards. | `mcu uno @beside-left` |\n\nMini boards (`board: mini`) have **no power rails** \u2014 `@+t\u2026` / `@-b\u2026` are rejected by the parser.\n\n---\n\n## 3. Board sizes\n\n```\nbreadboard\nboard: half // default \u2014 30 columns, 400 tie-points, rails (continuous)\n```\n\n| Form | Tie points | Columns | Power rails |\n|---|---|---|---|\n| `mini` | 170 | 17 | none |\n| `half` (default) | 400 | 30 | continuous |\n| `full` | 830 | 63 | break at column 30/31 |\n\n> **Pitfall** \u2014 on full-size boards the rails break at the middle. If your circuit uses both halves you must jumper the rails together explicitly.\n\n---\n\n## 4. Parts catalog\n\nEach part is `id: <kind> [args] @<placement>`. The catalog covers the most common Arduino / ESP32 maker components:\n\n**Discrete components** (sit on the breadboard):\n\n| DSL | Args | Example |\n|---|---|---|\n| `resistor` | `value` (\u03A9; supports `k`/`M`) | `r1: resistor 220 @5e..9e` |\n| `led` | `color` (red/green/blue/yellow/white/orange) | `d1: led red @10e..10f` |\n| `cap-elec` | \u2014 | `c1: cap-elec @4e..4f` |\n| `cap-ceramic` | \u2014 | `c2: cap-ceramic @6e..6f` |\n| `diode` | \u2014 | `d2: diode @5e..8e` |\n| `button` | \u2014 | `btn: button @8e` |\n| `dip` | `pins=N` | `ic: dip pins=8 @4e` |\n| `header` | `pins=N` | `h1: header pins=4 @20a` |\n\n**Microcontroller boards** (placed beside / above / below the substrate):\n\n| DSL | Pin labels |\n|---|---|\n| `mcu uno` | `5V`, `3V3`, `GND`, `VIN`, `RST`, `D2\u2026D13`, `A0\u2026A5`, `RX`, `TX` |\n| `mcu nano` | Subset of Uno labels |\n| `mcu esp32` | `3V3`, `GND`, `VIN`, `GPIO2`, `GPIO4`, `GPIO5`, `GPIO12\u2026GPIO33` |\n| `mcu pico` | Same generic GPIO labels |\n\n**Sensors / displays / actuators** (modules sit on the breadboard with pin row anchored at the supplied coordinate):\n\n| DSL | Pins |\n|---|---|\n| `sensor hcsr04` | `VCC`, `TRIG`, `ECHO`, `GND` |\n| `sensor dht11` / `sensor dht22` | `VCC`, `DATA`, `GND` |\n| `display oled-ssd1306` | `GND`, `VCC`, `SCL`, `SDA` |\n| `display lcd-1602-i2c` | `GND`, `VCC`, `SDA`, `SCL` |\n| `module rotary-ky040` | `CLK`, `DT`, `SW`, `VCC`, `GND` |\n| `actuator servo-sg90` | `GND`, `VCC`, `SIG` |\n\nResistor color bands are decorated automatically from `value` \u2014 `220` \u2192 red-red-brown-gold, `10000` \u2192 brown-black-orange-gold.\n\n---\n\n## 5. Wires\n\nEvery wire connects two endpoints. An endpoint is either a **part pin** (`partId:pinName`) or a **breadboard coordinate** (`@\u2026`).\n\n```\nwires\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D9 --yellow-- @9c\n @9a --green-- @+t9\n```\n\n| Color | Conventional role |\n|---|---|\n| `red` | +V (5V, 3.3V, VCC) |\n| `black` / `blue` | GND |\n| `yellow` / `orange` / `green` / `white` / `purple` | Signal |\n| `brown` / `grey` | Arbitrary signal |\n\nColor is purely visual \u2014 the engine does not validate it against electrical role.\n\nFor visually crowded boards, `via @<coord>` lets you pin an intermediate hole that biases the B\xE9zier control points:\n\n```\nwires\n uno:D13 --yellow-- @9a via @8c\n```\n\nMost wires don't need `via` \u2014 the layout engine produces a natural arc on its own.\n\n---\n\n## 6. Sensor with pull-up resistor (DHT11 motif)\n\nThe iconic Arduino tutorial pattern: a 10 k\u03A9 pull-up between VCC and the sensor's data line.\n\n```\nbreadboard\nboard: half\ntitle: \"DHT11 + 10k\u03A9 pull-up\"\n\nparts\n uno: mcu uno @beside-left\n s1: sensor dht11 @6a\n r1: resistor 10000 @8e..14e\n\nwires\n s1:VCC --red-- @+t1\n s1:GND --black-- @-t1\n s1:DATA --yellow-- @8e\n @14e --red-- @+t14\n uno:5V --red-- @+t1\n uno:GND --black-- @-t1\n uno:D2 --yellow-- @8a\n```\n\n---\n\n## 7. ESP32 + I\xB2C OLED\n\nESP32 runs at 3.3 V (not 5 V). I\xB2C convention: green = SDA, white = SCL.\n\n```\nbreadboard\nboard: half\ntitle: \"ESP32 + SSD1306 OLED I\xB2C\"\n\nparts\n esp: mcu esp32 @beside-left\n oled: display oled-ssd1306 @8a\n\nwires\n oled:GND --black-- @-t1\n oled:VCC --red-- @+t1\n oled:SCL --white-- @10c\n oled:SDA --green-- @11c\n esp:3V3 --red-- @+t1\n esp:GND --black-- @-t1\n esp:GPIO22 --white-- @10c\n esp:GPIO21 --green-- @11c\n```\n\n---\n\n## 8. Limitations of v0.1\n\n- **No leader-line callouts** \u2014 reference designators (R1, C2) are drawn near the part body. Off-board callout boxes are deferred.\n- **No `.fzz` import** \u2014 Schematex consumes only its own DSL; Fritzing files are not parsed.\n- **No simulation** \u2014 this is a renderer, not a Wokwi-style simulator. Component-value validation (Ohm's law, current limits) is out of scope.\n- **No PCB / schematic round-trip** \u2014 `breadboard` and `circuit` are independent engines. Authoring two views of the same prototype currently means writing two DSLs.\n- **Fixed parts catalog** \u2014 user-defined part types are deferred. v0.1 ships the maker-tutorial 80% catalog (resistors, LEDs, caps, DIPs, headers, four MCU families, six sensor / display / actuator modules).\n- **Power-rail break visual** \u2014 full-size boards mark the 30/31 break with a hole-wide gap; the rail stripes still draw through the gap (cosmetic).\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:"
1697
2510
  },
1698
2511
  "bpmn": {
1699
2512
  "title": "BPMN / Business Process",
1700
- "content": '## 1. Your first BPMN diagram\n\nThree sections: a one-line `bpmn` header, one or more `pool { \u2026 }` blocks, and a `flows` block. Inside each pool you put `lane { \u2026 }` blocks, and inside each lane you list flow objects as `id: kind "label"`.\n\n```\nbpmn\npool "Service" {\n lane "Worker" {\n A: start "Request"\n B: task service "Process"\n F: end "Done"\n }\n}\n\nflows\nA --> B\nB --> F\n```\n\nEvery flow object starts with an **id**, a colon, the **kind**, an optional sub-keyword (trigger / marker / gateway type), and a quoted **label**. Ids are referenced by flow lines.\n\n---\n\n## 2. Pools and lanes\n\nA **pool** represents one participant \u2014 an organisation, a department, or a system. A **lane** subdivides a pool into roles. The pool label is rendered rotated 90\xB0 on the left edge of a horizontal pool.\n\n```\npool "Customer" blackbox // black-box pool \u2014 no internal flow\npool "Bank" {\n lane "Clerk" { \u2026 }\n lane "Underwriter" { \u2026 }\n}\n```\n\nA **black-box pool** is a participant whose internal process you don\'t model \u2014 typically an external customer or partner. Black-box pools must contain zero flow objects (Schematex enforces this in the parser).\n\nSequence flow (`-->`) is **not allowed** to cross pool boundaries. Use a message flow (`~~>`) for cross-pool communication.\n\n---\n\n## 3. Events\n\nEvents are circles. Stroke weight encodes lifecycle role:\n\n| Kind | Stroke | DSL |\n|---|---|---|\n| Start | thin (1px) | `start` |\n| Intermediate | thin double ring | `intermediate` |\n| End | thick (3px) | `end` |\n\nThe optional **trigger** keyword adds an inner glyph. v0.1 supports the three most common triggers \u2014 `none` (no glyph), `message` (envelope), and `timer` (clock face):\n\n```\nA: start // none-trigger\nA: start message "Inbound" // message-catch (unfilled envelope)\nT: intermediate timer "60 min" // timer (clock face)\nF: end "Done"\n```\n\nFilled glyph = **throw**, unfilled glyph = **catch**. Schematex picks the right fill automatically based on event kind.\n\n---\n\n## 4. Activities \u2014 tasks and subprocesses\n\nActivities are rounded rectangles. The two v0.1 forms are `task` and a **collapsed** subprocess (the `+` marker indicates expandable detail):\n\n```\nB: task "Generic abstract task"\nU: task user "User decides"\nS: task service "API call"\nSE: task send "Send email"\nRE: task receive "Wait for reply"\nM: task manual "Hand-stamp the form"\nSC: task script "Run rule engine"\n\nX: subprocess "Verify identity" collapsed\n```\n\nThe **task marker** (small icon top-left) communicates *who or what* performs the work \u2014 a person (`user`), a software service (`service`), an outbound message (`send`), an inbound wait (`receive`), an out-of-system manual step (`manual`), or an automated script (`script`). When in doubt, omit the marker \u2014 it defaults to abstract.\n\n---\n\n## 5. Gateways\n\nGateways are diamonds. The inner glyph encodes branching semantics:\n\n| Kind | Glyph | Meaning | DSL |\n|---|---|---|---|\n| Exclusive (XOR) | **X** | Take exactly one outgoing branch (data-based) | `gateway xor` |\n| Inclusive (OR) | **O** | Take one or more outgoing branches | `gateway or` |\n| Parallel (AND) | **+** | Take all outgoing branches concurrently | `gateway and` |\n| Event-based | pentagon-in-circle | Pick the branch whose event fires first | `gateway event` |\n\nSchematex uses Bruce Silver\'s **X glyph** for XOR by default \u2014 that\'s the convention real BPMN audits expect. Most diagrams use XOR (data branch) and AND (parallel split / join); OR is rare and event-based mostly appears in race-condition models.\n\n---\n\n## 6. Connectors\n\nFour connector types. Three of them stay inside a pool; only message flow is allowed to cross pool boundaries.\n\n```\nA --> B // sequence flow (default)\nG --? "yes" --> C // conditional sequence (label is a guard)\nG --* "default" --> D // default flow (one per gateway, max)\n"Customer" ~~> A : "Submit application" // message flow\nE ~~> "Customer" : "Notify approval" // message flow back\n```\n\n- `-->` is the workhorse \u2014 solid line + filled triangle arrowhead.\n- `--? "label" -->` adds a small unfilled diamond at the source. Use it when leaving an activity *directly* on a guarded outcome. (At a gateway, the conditional label suffices; the diamond glyph isn\'t drawn.)\n- `--* "label" -->` adds a slash mark at the source. **One default flow maximum per gateway** \u2014 Schematex enforces this.\n- `~~>` is dashed with an open arrowhead and a small unfilled circle at the source. Source or target may be a quoted **pool name** (for black-box participants) or an object id.\n\n---\n\n## 7. Validation\n\nThe parser refuses diagrams that violate BPMN semantics, with line-numbered errors:\n\n| Rule | Error |\n|---|---|\n| Sequence flow crosses pool | `sequence flow \'A --> B\' crosses pool boundary \u2014 use message flow (~~>)` |\n| Message flow inside one pool | `message flow \'A ~~> B\' must cross pool boundaries` |\n| Black-box pool has internals | `black-box pool "X" cannot contain lanes` |\n| Two default flows from same gateway | `gateway \'G\' has 2 default flows (max 1)` |\n| Duplicate id within a pool | `duplicate id \'A\'` |\n| Unknown source / target | `unknown source \'X\' in sequence flow` |\n\nThese checks fire during parse, so an LLM gets a usable signal before the layout pass.\n\n---\n\n## 8. Larger example \u2014 pizza order with black-box customer\n\nA canonical BPMN tutorial: an external customer (whose process we don\'t model) places an order with a pizzeria split into Clerk / Chef / Delivery lanes, with a rework loop on the chef\'s quality check.\n\n```\nbpmn\ndirection: LR\ntitle: "Pizza order"\n\npool "Customer" blackbox\n\npool "Pizzeria" {\n lane "Clerk" {\n A: start message "Order received"\n B: task user "Take order"\n }\n lane "Chef" {\n C: task manual "Make pizza"\n G1: gateway xor "Pizza ok?"\n D: task manual "Rework"\n }\n lane "Delivery" {\n E: task send "Deliver"\n F: end "Done"\n }\n}\n\nflows\nA --> B\nB --> C\nC --> G1\nG1 --? "yes" --> E\nG1 --* "no" --> D\nD --> C\nE --> F\n"Customer" ~~> A : "Place order"\nE ~~> "Customer" : "Pizza delivered"\n```\n\nThe rework loop (`D --> C`) creates a back-edge that the layout\'s cycle-break detects via DFS \u2014 the longest-path layering then proceeds on the forward DAG so columns stay sensible.\n\n---\n\n## 9. Limitations of v0.1\n\nThese are deferred to a later release. If your diagram needs one, file an issue with the use case:\n\n- **Boundary events** \u2014 events attached to an activity edge (timer, error, escalation, compensation). Currently you have to model the boundary as a free-floating intermediate event with manual flows.\n- **Expanded subprocesses** \u2014 collapsed `subprocess` works; expanded inline blocks (`subprocess "X" { \u2026 }`) are deferred.\n- **Rare event triggers** \u2014 error / escalation / cancel / compensation / signal / link / conditional / multiple / parallel-multiple. Use `none` or `message` as a placeholder.\n- **Transaction / call activities** \u2014 render as normal tasks for v0.1.\n- **Loop and multi-instance markers** \u2014 bottom-center activity glyphs; deferred.\n- **Artifacts** \u2014 data object / data store / group / text annotation. Deferred.\n- **BPMN 2.0 XML import / export** \u2014 out of scope. Schematex computes layout from DSL; no DI layer to round-trip.'
2513
+ "content": '## 1. Your first BPMN diagram\n\nThree sections: a one-line `bpmn` header, one or more `pool { \u2026 }` blocks, and a `flows` block. Inside each pool you put `lane { \u2026 }` blocks, and inside each lane you list flow objects as `id: kind "label"`.\n\n```\nbpmn\npool "Service" {\n lane "Worker" {\n A: start "Request"\n B: task service "Process"\n F: end "Done"\n }\n}\n\nflows\nA --> B\nB --> F\n```\n\nEvery flow object starts with an **id**, a colon, the **kind**, an optional sub-keyword (trigger / marker / gateway type), and a quoted **label**. Ids are referenced by flow lines.\n\n---\n\n## 2. Pools and lanes\n\nA **pool** represents one participant \u2014 an organisation, a department, or a system. A **lane** subdivides a pool into roles. The pool label is rendered rotated 90\xB0 on the left edge of a horizontal pool.\n\n```\npool "Customer" blackbox // black-box pool \u2014 no internal flow\npool "Bank" {\n lane "Clerk" { \u2026 }\n lane "Underwriter" { \u2026 }\n}\n```\n\nA **black-box pool** is a participant whose internal process you don\'t model \u2014 typically an external customer or partner. Black-box pools must contain zero flow objects (Schematex enforces this in the parser).\n\nSequence flow (`-->`) is **not allowed** to cross pool boundaries. Use a message flow (`~~>`) for cross-pool communication.\n\n---\n\n## 3. Events\n\nEvents are circles. Stroke weight encodes lifecycle role:\n\n| Kind | Stroke | DSL |\n|---|---|---|\n| Start | thin (1px) | `start` |\n| Intermediate | thin double ring | `intermediate` |\n| End | thick (3px) | `end` |\n\nThe optional **trigger** keyword adds an inner glyph. v0.1 supports the three most common triggers \u2014 `none` (no glyph), `message` (envelope), and `timer` (clock face):\n\n```\nA: start // none-trigger\nA: start message "Inbound" // message-catch (unfilled envelope)\nT: intermediate timer "60 min" // timer (clock face)\nF: end "Done"\n```\n\nFilled glyph = **throw**, unfilled glyph = **catch**. Schematex picks the right fill automatically based on event kind.\n\n---\n\n## 4. Activities \u2014 tasks and subprocesses\n\nActivities are rounded rectangles. The two v0.1 forms are `task` and a **collapsed** subprocess (the `+` marker indicates expandable detail):\n\n```\nB: task "Generic abstract task"\nU: task user "User decides"\nS: task service "API call"\nSE: task send "Send email"\nRE: task receive "Wait for reply"\nM: task manual "Hand-stamp the form"\nSC: task script "Run rule engine"\n\nX: subprocess "Verify identity" collapsed\n```\n\nThe **task marker** (small icon top-left) communicates *who or what* performs the work \u2014 a person (`user`), a software service (`service`), an outbound message (`send`), an inbound wait (`receive`), an out-of-system manual step (`manual`), or an automated script (`script`). When in doubt, omit the marker \u2014 it defaults to abstract.\n\n---\n\n## 5. Gateways\n\nGateways are diamonds. The inner glyph encodes branching semantics:\n\n| Kind | Glyph | Meaning | DSL |\n|---|---|---|---|\n| Exclusive (XOR) | **X** | Take exactly one outgoing branch (data-based) | `gateway xor` |\n| Inclusive (OR) | **O** | Take one or more outgoing branches | `gateway or` |\n| Parallel (AND) | **+** | Take all outgoing branches concurrently | `gateway and` |\n| Event-based | pentagon-in-circle | Pick the branch whose event fires first | `gateway event` |\n\nSchematex uses Bruce Silver\'s **X glyph** for XOR by default \u2014 that\'s the convention real BPMN audits expect. Most diagrams use XOR (data branch) and AND (parallel split / join); OR is rare and event-based mostly appears in race-condition models.\n\n---\n\n## 6. Connectors\n\nFour connector types. Three of them stay inside a pool; only message flow is allowed to cross pool boundaries.\n\n```\nA --> B // sequence flow (default)\nG --? "yes" --> C // conditional sequence (label is a guard)\nG --* "default" --> D // default flow (one per gateway, max)\n"Customer" ~~> A : "Submit application" // message flow\nE ~~> "Customer" : "Notify approval" // message flow back\n```\n\n- `-->` is the workhorse \u2014 solid line + filled triangle arrowhead.\n- `--? "label" -->` adds a small unfilled diamond at the source. Use it when leaving an activity *directly* on a guarded outcome. (At a gateway, the conditional label suffices; the diamond glyph isn\'t drawn.)\n- `--* "label" -->` adds a slash mark at the source. **One default flow maximum per gateway** \u2014 Schematex enforces this.\n- `~~>` is dashed with an open arrowhead and a small unfilled circle at the source. Source or target may be a quoted **pool name** (for black-box participants) or an object id.\n\n---\n\n## 7. Validation\n\nThe parser refuses diagrams that violate BPMN semantics, with line-numbered errors:\n\n| Rule | Error |\n|---|---|\n| Sequence flow crosses pool | `sequence flow \'A --> B\' crosses pool boundary \u2014 use message flow (~~>)` |\n| Message flow inside one pool | `message flow \'A ~~> B\' must cross pool boundaries` |\n| Black-box pool has internals | `black-box pool "X" cannot contain lanes` |\n| Two default flows from same gateway | `gateway \'G\' has 2 default flows (max 1)` |\n| Duplicate id within a pool | `duplicate id \'A\'` |\n| Unknown source / target | `unknown source \'X\' in sequence flow` |\n\nThese checks fire during parse, so an LLM gets a usable signal before the layout pass.\n\n---\n\n## 8. Larger example \u2014 pizza order with black-box customer\n\nA canonical BPMN tutorial: an external customer (whose process we don\'t model) places an order with a pizzeria split into Clerk / Chef / Delivery lanes, with a rework loop on the chef\'s quality check.\n\n```\nbpmn\ndirection: LR\ntitle: "Pizza order"\n\npool "Customer" blackbox\n\npool "Pizzeria" {\n lane "Clerk" {\n A: start message "Order received"\n B: task user "Take order"\n }\n lane "Chef" {\n C: task manual "Make pizza"\n G1: gateway xor "Pizza ok?"\n D: task manual "Rework"\n }\n lane "Delivery" {\n E: task send "Deliver"\n F: end "Done"\n }\n}\n\nflows\nA --> B\nB --> C\nC --> G1\nG1 --? "yes" --> E\nG1 --* "no" --> D\nD --> C\nE --> F\n"Customer" ~~> A : "Place order"\nE ~~> "Customer" : "Pizza delivered"\n```\n\nThe rework loop (`D --> C`) creates a back-edge that the layout\'s cycle-break detects via DFS \u2014 the longest-path layering then proceeds on the forward DAG so columns stay sensible.\n\n---\n\n## 9. Limitations of v0.1\n\nThese are deferred to a later release. If your diagram needs one, file an issue with the use case:\n\n- **Boundary events** \u2014 events attached to an activity edge (timer, error, escalation, compensation). Currently you have to model the boundary as a free-floating intermediate event with manual flows.\n- **Expanded subprocesses** \u2014 collapsed `subprocess` works; expanded inline blocks (`subprocess "X" { \u2026 }`) are deferred.\n- **Rare event triggers** \u2014 error / escalation / cancel / compensation / signal / link / conditional / multiple / parallel-multiple. Use `none` or `message` as a placeholder.\n- **Transaction / call activities** \u2014 render as normal tasks for v0.1.\n- **Loop and multi-instance markers** \u2014 bottom-center activity glyphs; deferred.\n- **Artifacts** \u2014 data object / data store / group / text annotation. Deferred.\n- **BPMN 2.0 XML import / export** \u2014 out of scope. Schematex computes layout from DSL; no DI layer to round-trip.\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:'
1701
2514
  },
1702
2515
  "fbd": {
1703
2516
  "title": "Function Block Diagram (FBD)",
1704
- "content": '## 1. Your first FBD network\n\nThe smallest useful FBD network: one block, two inputs, one output.\n\n```\nfbd\nnetwork 0:\n Out = AND(A, B)\n```\n\n`A` and `B` are auto-declared as BOOL inputs (left-side terminals), and `Out` is auto-wired to the AND block\'s `OUT` port (right-side terminal). The block sits between them with port stubs and labels.\n\n---\n\n## 2. Variables\n\nDeclare variables before any networks. Each variable has a name, an IEC data type, and an optional initial value.\n\n```\nfbd "Tank Control"\n\nvar StartBtn: bool\nvar TankLevel: real\nvar SetPoint: real = 80.0\nvar DwellTimer: timer\nvar Pulse: counter\n```\n\nSupported types: `bool`, `int`, `dint`, `uint`, `udint`, `real`, `lreal`, `time`, `date`, `tod`, `string`, `wstring`, `byte`, `word`, `dword`, `timer`, `counter`. Any other identifier is treated as a user-defined function-block type.\n\nOptional scope prefixes (default = local): `var_input`, `var_output`, `var_in_out`, `var_global`, `var_external`.\n\n---\n\n## 3. Networks\n\nA **network** is one independent piece of data flow, evaluated left-to-right within itself; networks evaluate top-to-bottom across the program per scan.\n\n```\nnetwork 0 "Start latch":\n ...\n\nnetwork 1:\n ...\n```\n\nThe number is optional \u2014 networks are auto-numbered if absent. The title (in quotes) renders at the top-left of the network frame.\n\n---\n\n## 4. Block calls \u2014 inline expression notation\n\nThe clearest way to write a combinational network is as a single nested expression:\n\n```\nnetwork 0:\n Out = OR(A, AND(B, ~C))\n```\n\nThe parser builds the call tree: outer OR with `A` on input 1 and the AND result on input 2; the AND has `B` and the negated `C`. The renderer lays them out left-to-right (inputs at layer 0, AND at layer 1, OR at layer 2, output at layer 3).\n\n`~C` adds a **negation bubble** (small open circle) at the input port \u2014 equivalent to inserting a NOT block on that wire, but cleaner.\n\n---\n\n## 5. Block calls \u2014 instance-named notation\n\nWhen you need to reference a block\'s outputs from elsewhere, give it an instance tag:\n\n```\nnetwork 0:\n Pulse = R_TRIG(CLK: Sensor)\n Count = CTU(CU: Pulse.Q, R: Reset, PV: 100)\n Done = GE(IN1: Count.CV, IN2: 100)\n```\n\n`Pulse.Q`, `Count.CV` reference output ports of named instances. The instance tag renders italicized above the block header.\n\nArgument lists accept **named ports** (`CU: Pulse.Q`) or **positional** (`CTU(Pulse.Q, Reset, 100)`) \u2014 named is recommended for readability and required when you skip a port.\n\n---\n\n## 6. Inline constants\n\nInput ports can take literals directly \u2014 no wire needed:\n\n```\nnetwork 0:\n Dwell = TON(IN: BottleSensor, PT: T#50ms)\n Cap = LIMIT(MN: 0.0, IN: Setpoint, MX: 95.0)\n Mode = SEL(G: ManualSwitch, IN0: AutoMode, IN1: ManualMode)\n```\n\n`T#50ms`, `0.0`, `95.0` render as small yellow boxed text to the left of their port. Time literals follow IEC 61131-3: `T#10ms`, `T#5s`, `T#3m20s`, `T#1h`. Booleans are `true` / `false` (case-insensitive).\n\n---\n\n## 7. Standard block library\n\n| Category | Blocks |\n|---|---|\n| Boolean | `AND`, `OR`, `NOT`, `NAND`, `NOR`, `XOR`, `XNOR`, `BUF` |\n| Edge detect | `R_TRIG`, `F_TRIG` |\n| Bistable | `SR`, `RS` |\n| Timer | `TON`, `TOF`, `TP` |\n| Counter | `CTU`, `CTD` |\n| Math | `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `ABS`, `NEG`, `MOVE` |\n| Comparison | `EQ`, `NE`, `GT`, `GE`, `LT`, `LE` |\n| Selection | `SEL`, `MUX`, `MAX`, `MIN`, `LIMIT` |\n\nAND, OR, NAND, NOR, ADD, MUL, MAX, MIN accept any number of inputs (default 2). Pass extra positional args or use `[inputs: N]` to extend.\n\n```\nnetwork 0:\n All4 = AND(A, B, C, D)\n Sum = ADD(X, Y, Z)\n```\n\n---\n\n## 8. Larger example \u2014 bottle counter\n\n```\nfbd "Bottle Counter"\n\nvar ConveyorRunning: bool\nvar BottleSensor: bool\nvar BatchDone: bool\nvar BatchSize: counter\nvar DwellTimer: timer\n\nnetwork 0 "Debounce sensor with 50ms dwell":\n Dwell = TON(IN: BottleSensor, PT: T#50ms)\n\nnetwork 1 "Count one bottle on rising edge of debounced signal":\n Pulse = R_TRIG(CLK: Dwell.Q)\n BatchSize = CTU(CU: Pulse.Q, R: BatchDone, PV: 24)\n\nnetwork 2 "Batch done":\n BatchDone = MOVE(BatchSize.Q)\n```\n\nThree networks: debounce \u2192 edge-detect \u2192 count \u2192 flag. Each network is one DAG; the renderer routes wires through Manhattan paths.\n\n---\n\n## 9. v0.1 limitations\n\nThe current engine implements the standard-block subset most teams use day-to-day. The following are deferred and will be added in a follow-up:\n\n- **EN/ENO power-flow rails** (`[en]` block attribute, `[rail: on]` header) \u2014 adds a top-of-network enable rail, vendor convention from Studio 5000 / TIA Portal.\n- **User-defined function blocks** with `pins_in:` / `pins_out:` declarations \u2014 for custom motor controllers, PID instances, etc.\n- **Page connectors** (`connector_out` / `connector_in`) for wires that span multiple pages.\n- **Bit-string blocks** (SHL, SHR, ROL, ROR, AND_BIT, OR_BIT, etc.).\n- **Extended math** (SQRT, LN, LOG, EXP, SIN, COS, TAN, ASIN, ACOS, ATAN).\n- **ANSI distinctive shape mode** (`[shape: ansi]`) \u2014 the `logic` engine already provides this for pure-Boolean diagrams.\n- **CTUD** bidirectional counter, **TP** retentive timer (RTO).\n\nIf you need any of these now, open an issue or use the `ladder` engine for the full IEC 61131-3 LD subset.'
2517
+ "content": '## 1. Your first FBD network\n\nThe smallest useful FBD network: one block, two inputs, one output.\n\n```\nfbd\nnetwork 0:\n Out = AND(A, B)\n```\n\n`A` and `B` are auto-declared as BOOL inputs (left-side terminals), and `Out` is auto-wired to the AND block\'s `OUT` port (right-side terminal). The block sits between them with port stubs and labels.\n\n---\n\n## 2. Variables\n\nDeclare variables before any networks. Each variable has a name, an IEC data type, and an optional initial value.\n\n```\nfbd "Tank Control"\n\nvar StartBtn: bool\nvar TankLevel: real\nvar SetPoint: real = 80.0\nvar DwellTimer: timer\nvar Pulse: counter\n```\n\nSupported types: `bool`, `int`, `dint`, `uint`, `udint`, `real`, `lreal`, `time`, `date`, `tod`, `string`, `wstring`, `byte`, `word`, `dword`, `timer`, `counter`. Any other identifier is treated as a user-defined function-block type.\n\nOptional scope prefixes (default = local): `var_input`, `var_output`, `var_in_out`, `var_global`, `var_external`.\n\n---\n\n## 3. Networks\n\nA **network** is one independent piece of data flow, evaluated left-to-right within itself; networks evaluate top-to-bottom across the program per scan.\n\n```\nnetwork 0 "Start latch":\n ...\n\nnetwork 1:\n ...\n```\n\nThe number is optional \u2014 networks are auto-numbered if absent. The title (in quotes) renders at the top-left of the network frame.\n\n---\n\n## 4. Block calls \u2014 inline expression notation\n\nThe clearest way to write a combinational network is as a single nested expression:\n\n```\nnetwork 0:\n Out = OR(A, AND(B, ~C))\n```\n\nThe parser builds the call tree: outer OR with `A` on input 1 and the AND result on input 2; the AND has `B` and the negated `C`. The renderer lays them out left-to-right (inputs at layer 0, AND at layer 1, OR at layer 2, output at layer 3).\n\n`~C` adds a **negation bubble** (small open circle) at the input port \u2014 equivalent to inserting a NOT block on that wire, but cleaner.\n\n---\n\n## 5. Block calls \u2014 instance-named notation\n\nWhen you need to reference a block\'s outputs from elsewhere, give it an instance tag:\n\n```\nnetwork 0:\n Pulse = R_TRIG(CLK: Sensor)\n Count = CTU(CU: Pulse.Q, R: Reset, PV: 100)\n Done = GE(IN1: Count.CV, IN2: 100)\n```\n\n`Pulse.Q`, `Count.CV` reference output ports of named instances. The instance tag renders italicized above the block header.\n\nArgument lists accept **named ports** (`CU: Pulse.Q`) or **positional** (`CTU(Pulse.Q, Reset, 100)`) \u2014 named is recommended for readability and required when you skip a port.\n\n---\n\n## 6. Inline constants\n\nInput ports can take literals directly \u2014 no wire needed:\n\n```\nnetwork 0:\n Dwell = TON(IN: BottleSensor, PT: T#50ms)\n Cap = LIMIT(MN: 0.0, IN: Setpoint, MX: 95.0)\n Mode = SEL(G: ManualSwitch, IN0: AutoMode, IN1: ManualMode)\n```\n\n`T#50ms`, `0.0`, `95.0` render as small yellow boxed text to the left of their port. Time literals follow IEC 61131-3: `T#10ms`, `T#5s`, `T#3m20s`, `T#1h`. Booleans are `true` / `false` (case-insensitive).\n\n---\n\n## 7. Standard block library\n\n| Category | Blocks |\n|---|---|\n| Boolean | `AND`, `OR`, `NOT`, `NAND`, `NOR`, `XOR`, `XNOR`, `BUF` |\n| Edge detect | `R_TRIG`, `F_TRIG` |\n| Bistable | `SR`, `RS` |\n| Timer | `TON`, `TOF`, `TP` |\n| Counter | `CTU`, `CTD` |\n| Math | `ADD`, `SUB`, `MUL`, `DIV`, `MOD`, `ABS`, `NEG`, `MOVE` |\n| Comparison | `EQ`, `NE`, `GT`, `GE`, `LT`, `LE` |\n| Selection | `SEL`, `MUX`, `MAX`, `MIN`, `LIMIT` |\n\nAND, OR, NAND, NOR, ADD, MUL, MAX, MIN accept any number of inputs (default 2). Pass extra positional args or use `[inputs: N]` to extend.\n\n```\nnetwork 0:\n All4 = AND(A, B, C, D)\n Sum = ADD(X, Y, Z)\n```\n\n---\n\n## 8. Larger example \u2014 bottle counter\n\n```\nfbd "Bottle Counter"\n\nvar ConveyorRunning: bool\nvar BottleSensor: bool\nvar BatchDone: bool\nvar BatchSize: counter\nvar DwellTimer: timer\n\nnetwork 0 "Debounce sensor with 50ms dwell":\n Dwell = TON(IN: BottleSensor, PT: T#50ms)\n\nnetwork 1 "Count one bottle on rising edge of debounced signal":\n Pulse = R_TRIG(CLK: Dwell.Q)\n BatchSize = CTU(CU: Pulse.Q, R: BatchDone, PV: 24)\n\nnetwork 2 "Batch done":\n BatchDone = MOVE(BatchSize.Q)\n```\n\nThree networks: debounce \u2192 edge-detect \u2192 count \u2192 flag. Each network is one DAG; the renderer routes wires through Manhattan paths.\n\n---\n\n## 9. v0.1 limitations\n\nThe current engine implements the standard-block subset most teams use day-to-day. The following are deferred and will be added in a follow-up:\n\n- **EN/ENO power-flow rails** (`[en]` block attribute, `[rail: on]` header) \u2014 adds a top-of-network enable rail, vendor convention from Studio 5000 / TIA Portal.\n- **User-defined function blocks** with `pins_in:` / `pins_out:` declarations \u2014 for custom motor controllers, PID instances, etc.\n- **Page connectors** (`connector_out` / `connector_in`) for wires that span multiple pages.\n- **Bit-string blocks** (SHL, SHR, ROL, ROR, AND_BIT, OR_BIT, etc.).\n- **Extended math** (SQRT, LN, LOG, EXP, SIN, COS, TAN, ASIN, ACOS, ATAN).\n- **ANSI distinctive shape mode** (`[shape: ansi]`) \u2014 the `logic` engine already provides this for pure-Boolean diagrams.\n- **CTUD** bidirectional counter, **TP** retentive timer (RTO).\n\nIf you need any of these now, open an issue or use the `ladder` engine for the full IEC 61131-3 LD subset.\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:'
1705
2518
  },
1706
2519
  "sfc": {
1707
2520
  "title": "Sequential Function Chart (SFC)",
1708
- "content": '## 1. Your first chart\n\nTwo steps, one transition, an initial marker:\n\n```\nsfc\nstep S0 [initial]\nstep S1\ntransition from: S0 to: S1: Trigger\n```\n\n`S0` renders as a **double-bordered rectangle** (the IEC initial-step convention); `S1` as a single-border rectangle. Between them is a horizontal **transition bar** with the condition text `Trigger` to its right.\n\nIf you forget `[initial]`, the first declared step is auto-promoted to initial.\n\n---\n\n## 2. Steps\n\n```\nstep S_Filling [label: "Filling tank"]\n N FillValve_Open\n D Mixer_Run T#30s\n P StartChime\n```\n\nA step has:\n\n- An **id** (unique across the chart) \u2014 used in transitions and jumps.\n- An optional `[label: "..."]` for display.\n- An optional `[initial]` (one allowed) or `[final]` (vendor stop step, three borders).\n- Zero or more **action blocks**, indented one level, each with a qualifier letter.\n\n---\n\n## 3. Transitions\n\nA **transition** declares a directed link between two steps with a boolean condition:\n\n```\ntransition from: S0 to: S1: StartBtn\ntransition from: S1 to: S2: TankLevel >= 80.0 AND NOT EmergencyStop\ntransition T_Reset from: S5 to: S0: ResetBtn\n```\n\nThe condition text is opaque \u2014 Schematex stores it verbatim and renders it next to the bar. Every transition must have a non-empty condition; use `TRUE` for unconditional links.\n\nTransitions whose `from` and `to` are linearly adjacent in the body render as inline bars between the steps. Transitions whose pair is *not* linearly adjacent (e.g. a jump back to an earlier step) render as **margin arrows** on the left or right side of the chart.\n\n---\n\n## 4. Action qualifiers\n\nActions attach to the right side of a step and run according to their qualifier letter:\n\n| Qualifier | Behavior |\n|---|---|\n| `N` | Active while step is active (most common) |\n| `S` | Stored \u2014 set true on entry, stays until matching `R` |\n| `R` | Reset \u2014 clears a previously-stored action |\n| `L` | Time-Limited \u2014 active up to T after step entry |\n| `D` | Time-Delayed \u2014 activates T after entry |\n| `P` | Pulse \u2014 true for one PLC scan only |\n| `P0` | Pulse on deactivate (Siemens) |\n| `P1` | Synonym for `P` (Siemens) |\n| `SD` | Stored & Delayed |\n| `DS` | Delayed & Stored |\n| `SL` | Stored & Time-Limited |\n\nTime-parameterized qualifiers (L, D, SD, DS, SL) take a duration literal:\n\n```\nstep S1\n L LimitedRun T#5s\n D DelayedRun T#2s\n```\n\n---\n\n## 5. Alternative branches (single bar \u2014 OR)\n\nOnly **one** branch fires per scan, picked by transition condition:\n\n```\nstep S0 [initial]\nstep S_Pick\n\nalt from: S_Pick:\n branch [priority: 1]:\n transition: IsExpressShipping\n step S_Express\n N PrepExpressBox\n transition: TRUE\n branch [priority: 2]:\n transition: IsStandardShipping\n step S_Standard\n N PrepStandardBox\n transition: TRUE\nmerge_to: S_Ship\n\nstep S_Ship\n\ntransition from: S0 to: S_Pick: ProductOrdered\ntransition from: S_Ship to: S0: Shipped\n```\n\nThe single horizontal lines above and below the branches are the divergence and convergence bars. Each branch starts with its **entry transition** (between div bar and first step) and ends with an **exit transition** (between last step and conv bar).\n\n---\n\n## 6. Simultaneous branches (double bar \u2014 AND)\n\n**All** branches run concurrently; the chart waits at the convergence until every branch finishes:\n\n```\nsim from: S_Heat: TRUE\n branch:\n step S_Bake\n D Oven_Run T#15m\n branch:\n step S_Cool\n L Cooler_On T#5m\nmerge_to: S_Done: Bake_Done AND Cool_Done\n```\n\nThe two parallel horizontal lines (gap 4px) above and below the branches are the simultaneous bars. The shared **transition above** (`TRUE` here) triggers the divergence; the shared **transition below** (`Bake_Done AND Cool_Done`) is checked before convergence fires.\n\n---\n\n## 7. Jumps (loops)\n\nA transition whose target is an earlier step renders as a margin arrow:\n\n```\nstep S0 [initial]\nstep S1\nstep S2\ntransition from: S0 to: S1: A\ntransition from: S1 to: S2: B\ntransition T_Reset from: S2 to: S0: ResetBtn\ntransition from: S2 to: S1: NOT ResetBtn\n```\n\nThe forward `S2 \u2192 S1` (back-edge) gets the margin arrow on the right; the `T_Reset` jump back to `S0` goes on the left. Each margin arrow shows its target id and condition.\n\n---\n\n## 8. Variables\n\nReused from `ladder` and `fbd`:\n\n```\nvar StartBtn: bool\nvar TankLevel: real\nvar BakeReady: bool\nvar Counter: counter\nvar T1: timer\n```\n\nVariables declared in conditions and actions are not validated \u2014 Schematex treats condition / action body text as opaque strings, matching how `state` handles guards and actions.\n\n---\n\n## 9. v0.1 limitations\n\n- **Nested branches** (alt-in-sim, sim-in-alt) parse but layout collapse heuristics are basic; deep nests may overlap.\n- **S/R action-pair dashed connectors** (visually link an `S` action with its matching `R` elsewhere) are deferred.\n- **Active-step runtime indicator** (yellow fill on the currently active step) is deferred \u2014 useful for debugging integrations that surface PLC runtime state.\n- **GRAFCET forcing orders** (out-of-scope per IEC 60848-only feature).\n- **Final step** parses with `[final]` but renders with the same double border as initial; the IEC triple-border convention is deferred.'
2521
+ "content": '## 1. Your first chart\n\nTwo steps, one transition, an initial marker:\n\n```\nsfc\nstep S0 [initial]\nstep S1\ntransition from: S0 to: S1: Trigger\n```\n\n`S0` renders as a **double-bordered rectangle** (the IEC initial-step convention); `S1` as a single-border rectangle. Between them is a horizontal **transition bar** with the condition text `Trigger` to its right.\n\nIf you forget `[initial]`, the first declared step is auto-promoted to initial.\n\n---\n\n## 2. Steps\n\n```\nstep S_Filling [label: "Filling tank"]\n N FillValve_Open\n D Mixer_Run T#30s\n P StartChime\n```\n\nA step has:\n\n- An **id** (unique across the chart) \u2014 used in transitions and jumps.\n- An optional `[label: "..."]` for display.\n- An optional `[initial]` (one allowed) or `[final]` (vendor stop step, three borders).\n- Zero or more **action blocks**, indented one level, each with a qualifier letter.\n\n---\n\n## 3. Transitions\n\nA **transition** declares a directed link between two steps with a boolean condition:\n\n```\ntransition from: S0 to: S1: StartBtn\ntransition from: S1 to: S2: TankLevel >= 80.0 AND NOT EmergencyStop\ntransition T_Reset from: S5 to: S0: ResetBtn\n```\n\nThe condition text is opaque \u2014 Schematex stores it verbatim and renders it next to the bar. Every transition must have a non-empty condition; use `TRUE` for unconditional links.\n\nTransitions whose `from` and `to` are linearly adjacent in the body render as inline bars between the steps. Transitions whose pair is *not* linearly adjacent (e.g. a jump back to an earlier step) render as **margin arrows** on the left or right side of the chart.\n\n---\n\n## 4. Action qualifiers\n\nActions attach to the right side of a step and run according to their qualifier letter:\n\n| Qualifier | Behavior |\n|---|---|\n| `N` | Active while step is active (most common) |\n| `S` | Stored \u2014 set true on entry, stays until matching `R` |\n| `R` | Reset \u2014 clears a previously-stored action |\n| `L` | Time-Limited \u2014 active up to T after step entry |\n| `D` | Time-Delayed \u2014 activates T after entry |\n| `P` | Pulse \u2014 true for one PLC scan only |\n| `P0` | Pulse on deactivate (Siemens) |\n| `P1` | Synonym for `P` (Siemens) |\n| `SD` | Stored & Delayed |\n| `DS` | Delayed & Stored |\n| `SL` | Stored & Time-Limited |\n\nTime-parameterized qualifiers (L, D, SD, DS, SL) take a duration literal:\n\n```\nstep S1\n L LimitedRun T#5s\n D DelayedRun T#2s\n```\n\n---\n\n## 5. Alternative branches (single bar \u2014 OR)\n\nOnly **one** branch fires per scan, picked by transition condition:\n\n```\nstep S0 [initial]\nstep S_Pick\n\nalt from: S_Pick:\n branch [priority: 1]:\n transition: IsExpressShipping\n step S_Express\n N PrepExpressBox\n transition: TRUE\n branch [priority: 2]:\n transition: IsStandardShipping\n step S_Standard\n N PrepStandardBox\n transition: TRUE\nmerge_to: S_Ship\n\nstep S_Ship\n\ntransition from: S0 to: S_Pick: ProductOrdered\ntransition from: S_Ship to: S0: Shipped\n```\n\nThe single horizontal lines above and below the branches are the divergence and convergence bars. Each branch starts with its **entry transition** (between div bar and first step) and ends with an **exit transition** (between last step and conv bar).\n\n---\n\n## 6. Simultaneous branches (double bar \u2014 AND)\n\n**All** branches run concurrently; the chart waits at the convergence until every branch finishes:\n\n```\nsim from: S_Heat: TRUE\n branch:\n step S_Bake\n D Oven_Run T#15m\n branch:\n step S_Cool\n L Cooler_On T#5m\nmerge_to: S_Done: Bake_Done AND Cool_Done\n```\n\nThe two parallel horizontal lines (gap 4px) above and below the branches are the simultaneous bars. The shared **transition above** (`TRUE` here) triggers the divergence; the shared **transition below** (`Bake_Done AND Cool_Done`) is checked before convergence fires.\n\n---\n\n## 7. Jumps (loops)\n\nA transition whose target is an earlier step renders as a margin arrow:\n\n```\nstep S0 [initial]\nstep S1\nstep S2\ntransition from: S0 to: S1: A\ntransition from: S1 to: S2: B\ntransition T_Reset from: S2 to: S0: ResetBtn\ntransition from: S2 to: S1: NOT ResetBtn\n```\n\nThe forward `S2 \u2192 S1` (back-edge) gets the margin arrow on the right; the `T_Reset` jump back to `S0` goes on the left. Each margin arrow shows its target id and condition.\n\n---\n\n## 8. Variables\n\nReused from `ladder` and `fbd`:\n\n```\nvar StartBtn: bool\nvar TankLevel: real\nvar BakeReady: bool\nvar Counter: counter\nvar T1: timer\n```\n\nVariables declared in conditions and actions are not validated \u2014 Schematex treats condition / action body text as opaque strings, matching how `state` handles guards and actions.\n\n---\n\n## 9. v0.1 limitations\n\n- **Nested branches** (alt-in-sim, sim-in-alt) parse but layout collapse heuristics are basic; deep nests may overlap.\n- **S/R action-pair dashed connectors** (visually link an `S` action with its matching `R` elsewhere) are deferred.\n- **Active-step runtime indicator** (yellow fill on the currently active step) is deferred \u2014 useful for debugging integrations that surface PLC runtime state.\n- **GRAFCET forcing orders** (out-of-scope per IEC 60848-only feature).\n- **Final step** parses with `[final]` but renders with the same double border as initial; the IEC triple-border convention is deferred.\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:'
1709
2522
  },
1710
2523
  "usecase": {
1711
2524
  "title": "UML Use Case Diagram",
1712
2525
  "content": '## 1. Your first diagram\n\nEvery document starts with the `usecase` keyword, then a header, then declarations and relationships:\n\n```\nusecase\nsystem: "Library"\n\nactor: Member\nusecase: "Borrow Book" as Borrow\n\nMember -- Borrow\n```\n\n`actor:` declares an actor (a stick figure), `usecase:` declares a use case (an ellipse), and `Member -- Borrow` draws a plain association between them. `system:` wraps the use cases in a labelled **subject** rectangle. The primary actor is placed on the left; supporting actors on the right.\n\nThe header accepts:\n\n- `title: "\u2026"` \u2014 a heading drawn above the diagram.\n- `system: "\u2026"` \u2014 the subject (system boundary) name. Omit it to let the use cases float free (Schematex warns if you omit it with \u22653 use cases).\n- `direction: LR | TB` \u2014 default `LR` (actors flank the subject horizontally).\n- `generalization: tree | individual` \u2014 whether to merge sibling generalization arrows (default `tree`).\n\n---\n\n## 2. Actors\n\n```\nactor: Customer\nactor: "Payment Gateway" as PG (external)\nactor: "Warehouse Staff" as WH\nactor: Admin (left)\n```\n\n- A bare or `"quoted"` name becomes the label; `as ID` gives it a short identifier for relationships.\n- `(external)` (or `(system)`) renders the actor as a **rectangle with an `\xABactor\xBB` stereotype** instead of a stick figure \u2014 use it for other software systems (payment gateways, third-party APIs).\n- `(business)` adds the Bittner & Spence diagonal slash across the stick figure.\n- `(left)` / `(right)` pins the actor to a side. By default the first actor goes left and the rest go right.\n\nCustom stereotypes go in guillemets after the declaration: `actor: "Audit Service" as Audit (external) \xABsystem\xBB`. The parser also accepts ASCII `<<system>>` and normalises it to `\xABsystem\xBB`.\n\n---\n\n## 3. Use cases\n\n```\nusecase: "Checkout" as Checkout {\n extension point: payment failed\n extension point: stock depleted\n}\n```\n\nA use case is an ellipse sized to fit its text. The optional `{ \u2026 }` block lists **extension points** in a compartment below the name, separated by a divider line. Stereotypes work here too: `usecase: "Validate Card" as ValidateCard \xABsecured\xBB`.\n\n---\n\n## 4. Relationships\n\nFour line types connect actors and use cases:\n\n| DSL | Meaning | Rendering |\n|-----|---------|-----------|\n| `A -- B` | association | solid line, no arrow |\n| `A --> B` | directed association | solid line, open arrow at B |\n| `A ..> B` | `A` **includes** `B` | dashed line, open arrow \u2192 B, `\xABinclude\xBB` pill |\n| `A <.. B` | `A` **extends** `B` | dashed line, open arrow \u2192 B (the base), `\xABextend\xBB` pill |\n| `A --\\|> B` | `A` is a specialisation of `B` | solid line, hollow triangle \u2192 parent B |\n\n```\nCustomer -- Checkout\nCheckout ..> Pay : \xABinclude\xBB\nPay ..> ValidateCard : \xABinclude\xBB\nCancel <.. Checkout : \xABextend\xBB [payment failed] (extension point: payment failed)\n```\n\n- **`\xABinclude\xBB`** points *toward the included use case* \u2014 the reusable behavior `A` always runs. Source includes target.\n- **`\xABextend\xBB`** points *toward the base* \u2014 `A` is the optional behavior, `B` is the base it extends. You can attach a `[condition]` and reference one of the base\'s `(extension point: \u2026)` entries. (The arrowhead is always drawn toward the base regardless of how you order the endpoints.)\n- An `\xABextend\xBB` line is drawn in the theme **accent color** so the rarer, more surprising relationship stands out.\n\nThe parser rejects the high-confidence mistakes humans and LLMs both make, with the offending line number: association between two actors or two use cases, `include`/`extend` touching an actor, generalization across metaclasses (actor \u2192 use case), reused identifiers, and extension-point references that don\'t exist on the base.\n\n---\n\n## 5. Generalization\n\n`--|>` works between two actors **or** between two use cases (never across the two \u2014 that\'s a hard error):\n\n```\nactor: User as U\nactor: "Premium User" as PU\nPU --|> U\n\nusecase: "Pay by Card" as PayCard\nusecase: "Pay by PayPal" as PayPaypal\nusecase: "Pay" as Pay\nPayCard --|> Pay\nPayPaypal --|> Pay\n```\n\nThe arrow carries a **hollow triangle** pointing at the parent. When three or more siblings share one parent, the arrows merge into a single shared head (UML 2.5 Figure 18.5 convention); set `generalization: individual` in the header to keep them separate. Actor hierarchies route as a clean bus on the outer edge of the actor stack.\n\n---\n\n## 6. Multiplicity\n\nQuote a multiplicity string immediately beside the endpoint it belongs to:\n\n```\nCustomer "1" -- "*" Checkout\nCashier "1..*" -- "1" Register\n```\n\n---\n\n## 7. PlantUML-style inline form\n\nComing from PlantUML? The inline declaration form works and mixes freely with the declarative form:\n\n```\nusecase\n:Customer: as C\n(Browse Catalog) as Browse\n(Add to Cart) as AddCart\n\nC -- Browse\nC -- AddCart\nBrowse ..> AddCart : \xABinclude\xBB\n```\n\n`:Name:` declares an actor, `(Name)` declares a use case, and `as ID` aliases either. Schematex is *inspired by* PlantUML, not a 1:1 transpiler \u2014 multiplicity and relationship syntax differ slightly.\n\n---\n\n## 8. Grammar (EBNF)\n\n```text\ndocument = "usecase" NEWLINE header_prop* statement*\nheader_prop = ("title:" | "system:") quoted_string\n | "direction:" ("LR" | "TB")\n | "generalization:" ("tree" | "individual")\n\nstatement = actor_decl | usecase_decl | plantuml_inline | relation | note\n\nactor_decl = "actor" ":" name ("as" IDENT)? actor_kind? stereotype?\nactor_kind = "(" ("external" | "system" | "business" | "left" | "right") ")"\nusecase_decl = "usecase" ":" quoted ("as" IDENT)? stereotype? extpoints?\nextpoints = "{" ("extension point:" TEXT)+ "}"\nplantuml_inline = ":" name ":" ("as" IDENT)? ; actor\n | "(" name ")" ("as" IDENT)? ; use case\n\nrelation = endpoint relop endpoint label_clause?\nendpoint = (IDENT | quoted) multiplicity? | multiplicity? (IDENT | quoted)\nrelop = "--" | "-->" | "..>" | "<.." | "--|>"\nlabel_clause = ":" stereotype? condition? extpoint_ref?\nstereotype = "\xAB" TEXT "\xBB" | "<<" TEXT ">>"\ncondition = "[" TEXT "]"\nextpoint_ref = "(extension point:" TEXT ")"\nmultiplicity = quoted_string ; "1", "*", "0..1", "1..*"\n```\n\n---'
1713
2526
  },
2527
+ "sequence": {
2528
+ "title": "UML Sequence Diagram",
2529
+ "content": '## 1. Your first diagram\n\nEvery document starts with the `sequence` keyword and an optional `"title"`. Participants don\'t need to be declared \u2014 the first time you mention one in a message, it becomes a lifeline:\n\n```\nsequence\n Alice -> Bob : Authentication Request\n Bob --> Alice : Authentication Response\n```\n\nLifelines appear left-to-right in first-use order. `->` is a synchronous call (solid line, filled arrowhead); `-->` is a reply (dashed line, open arrowhead). Text after `:` is the message label.\n\n---\n\n## 2. Participants\n\nDeclare a participant explicitly to set its **kind**, give it an **alias**, or fix its **order**:\n\n```\nsequence\n actor User\n participant Web as "Web App"\n boundary LoginUI\n control Auth\n entity Account\n database DB\n collections Sessions\n queue Events\n```\n\n| Kind | Rendered as |\n|------|-------------|\n| `participant` (default) | rounded classifier box |\n| `actor` | stick figure |\n| `boundary` / `control` / `entity` | the Jacobson robustness icons (\u22A2\u25EF / \u25EF with arrow / \u25EF with underline) |\n| `database` | cylinder |\n| `collections` / `queue` | box with the kind as a `\xABstereotype\xBB` |\n\n- `as "Label"` sets the display name (quotes optional; `\u300C\u2026\u300D` CJK quotes accepted).\n- A custom **stereotype** goes in guillemets or ASCII angle brackets after the declaration \u2014 it overrides the default label: `actor Printer \xABsystem\xBB`, `participant Bus as "Event Bus" <<service>>`.\n\n---\n\n## 3. Messages\n\nThe arrow token chooses the UML semantics:\n\n| DSL | Meaning | Rendering |\n|-----|---------|-----------|\n| `A -> B` | synchronous call | solid line, **filled** arrowhead |\n| `A ->> B` | asynchronous signal | solid line, **open** arrowhead |\n| `A --> B` | reply / return | **dashed** line, open arrowhead |\n| `A -x B` | lost message | line ending at a filled circle |\n| `o-> B` | found message | line starting from a filled circle |\n| `A -> A` | self message | bent loop back to the same lifeline |\n\nWhitespace around arrows is optional (`A->B` works), and labels are free text \u2014 including a return value, e.g. `aHotel -> aHotel : available(roomId, date): isRoom`.\n\n---\n\n## 4. Activations (execution specifications)\n\nThe thin bar on a lifeline shows it is active. Open and close it with explicit statements, or with `+` / `-` suffixes on the messages themselves:\n\n```\nsequence\n participant Client\n participant Server\n Client ->+ Server : request()\n Server ->> Server : validate()\n Server -->- Client : response\n```\n\n`+` after the arrow activates the **receiver** on arrival; `-` deactivates the **sender** after the message is sent. The explicit form is `activate X` / `deactivate X`. Overlapping bars on one lifeline nest with a horizontal offset.\n\n---\n\n## 5. Object creation & destruction\n\n```\nsequence\n participant Factory\n Factory -> *Worker : \xABcreate\xBB\n Factory -> Worker : work()\n destroy Worker\n```\n\nPrefix the receiver with `*` to make the message **instantiate** it \u2014 the new lifeline\'s head is drawn at the arrival row, not at the top. `destroy X` ends a lifeline with a \u2715 and stops its time axis.\n\n---\n\n## 6. Combined fragments\n\nA combined fragment is a labelled frame around a region. Schematex implements the full UML `InteractionOperatorKind` set:\n\n| Operator | Meaning | Operands |\n|----------|---------|----------|\n| `alt` | alternatives (if/else-if/else) | `else`, each guarded |\n| `opt` | runs iff the guard holds | one, guarded |\n| `loop` | repeat | one; guard may be `(min,max)` |\n| `par` | concurrent operands | `and` |\n| `break` | exceptional exit | one, guarded |\n| `critical` | atomic / no interleaving | one |\n| `seq` / `strict` | weak / strict sequencing | `and` |\n| `neg` | invalid traces (rendered tinted) | one |\n| `ignore` / `consider` | message-set filter `{m1, m2}` | one |\n| `assert` | the only valid continuation | one |\n\n```\nsequence\n actor User\n participant API\n User -> API : GET /resource\n alt [authorized]\n API --> User : 200 + body\n else [forbidden]\n API --> User : 403\n end\n```\n\nGuards go in `[brackets]` after the operator (and after `else`). Fragments nest, and inner frames inset automatically so they sit cleanly inside their parent.\n\n---\n\n## 7. Interaction use (`ref`)\n\nReference another interaction instead of inlining it \u2014 the way UML keeps large diagrams composable:\n\n```\nsequence\n participant A\n participant B\n ref over A, B : Establish session\n A -> B : poll()\n```\n\n`ref over <lifelines> : Name` draws a framed box across the named lifelines. Most tools omit this; it\'s how real systems decompose long flows.\n\n---\n\n## 8. Notes, dividers, invariants, numbering\n\n```\nsequence\n autonumber 1 1\n actor Shopper\n participant Cart\n == Phase 1: review ==\n Shopper -> Cart : view items\n note over Cart : cart persisted in Redis\n state Cart : ready\n```\n\n- `note over A` / `note over A, B` / `note left of A` / `note right of A` \u2014 folded-corner annotations.\n- `== text ==` \u2014 a full-width section divider.\n- `state X : text` \u2014 a state-invariant capsule on a lifeline.\n- `autonumber [start] [step]` \u2014 prefix every message with an incrementing number.\n\n---\n\n## 9. Grammar (EBNF)\n\n```text\ndiagram = "sequence" [ string ] NEWLINE statement*\nstatement = participant | message | activation | note\n | fragment | ref | divider | invariant | destroy | autonumber\n\nparticipant = kind IDENT ("as" label)? stereotype?\nkind = "participant" | "actor" | "boundary" | "control"\n | "entity" | "database" | "collections" | "queue"\nstereotype = "\xAB" TEXT "\xBB" | "<<" TEXT ">>"\n\nmessage = IDENT? act? arrow act? ("*")? IDENT? (":" TEXT)?\narrow = "->" | "->>" | "-->" | "-x" | "o->"\nact = "+" | "-"\nactivation = ("activate" | "deactivate") IDENT\n\nfragment = ("alt"|"opt"|"loop"|"par"|"break"|"critical"|"seq"\n |"strict"|"neg"|"ignore"|"consider"|"assert") guard? NEWLINE\n statement* (("else"|"and") guard? NEWLINE statement*)* "end"\nguard = "[" TEXT "]" | "(" NUMBER ("," NUMBER)? ")"\nref = "ref" "over" IDENT ("," IDENT)* ":" TEXT\nnote = "note" ("over"|"left of"|"right of") IDENT ("," IDENT)? ":" TEXT\ndivider = "==" TEXT "=="\ninvariant = "state" IDENT ":" TEXT\ndestroy = "destroy" IDENT\nautonumber = "autonumber" NUMBER? NUMBER?\n```\n\n---'
2530
+ },
1714
2531
  "prisma": {
1715
2532
  "title": "PRISMA 2020 flow diagram",
1716
2533
  "content": '## 1. Your first diagram\n\nThe minimum is the four stage blocks. Counts are mandatory; the parser refuses to lay out a diagram with a missing total.\n\n```\nprisma\n\nidentification:\n databases:\n n: 1000\n\nscreening:\n records-screened: 900\n excluded:\n n: 600\n\neligibility:\n full-text-assessed: 300\n excluded:\n n: 250\n\nincluded:\n studies: 50\n```\n\nIndentation is significant \u2014 **two spaces per level**, like genogram and SLD. The first non-blank line must be `prisma`. Comments use `#` or `//`.\n\n---\n\n## 2. Meta lines\n\nTop-level `key: value` lines, written before the stage blocks:\n\n```\nprisma\nmode: 2020-single\nkind: systematic-review\ntitle: My review\nvalidate-counts: warn\n```\n\n| Key | Values | Default | Meaning |\n|---|---|---|---|\n| `mode` | `2020-single` \xB7 `2020-dual` \xB7 `2009` | `2020-single` | Single column, or dual ("other methods") column. |\n| `kind` | `systematic-review` \xB7 `scoping-review` \xB7 `ipd` \xB7 `nma` | `systematic-review` | Swaps stage vocabulary (see \xA76). |\n| `title` | string | \u2014 | Rendered above the diagram. |\n| `validate-counts` | `warn` \xB7 `strict` \xB7 `off` | `warn` | Arithmetic checking (see \xA77). |\n| `direction` | `TB` / `TD` | `TB` | PRISMA is vertical by standard; horizontal is rejected. |\n\n---\n\n## 3. Identification\n\nThe `identification:` block holds a `databases:` sub-block (always) and an optional `other:` sub-block (dual mode).\n\n```\nidentification:\n databases:\n n: 1418\n sources: PubMed=600, Embase=450, Cochrane=184\n duplicates-removed: 318\n ineligible-automation: 0\n other-removed: 0\n```\n\n- `n:` \u2014 total records identified (**mandatory**).\n- `sources:` \u2014 `name=count` pairs, comma-separated. Rendered as an indented breakdown. Names with spaces or punctuation can be quoted: `"Web of Science"=184`.\n- `duplicates-removed:`, `ineligible-automation:`, `other-removed:` \u2014 optional removal counts. When any are present they render as a separate **"Records removed before screening"** box in the right column, connected by a horizontal arrow.\n\nLarge numbers may use commas: `n: 1,418` is the same as `n: 1418`.\n\n---\n\n## 4. Screening & Eligibility\n\nBoth stages carry a main count plus an `excluded:` block. The excluded block has its own `n:` and an optional `reasons:` breakdown.\n\n```\nscreening:\n records-screened: 1100\n excluded:\n n: 870\n reasons: irrelevant title=750, non-English=120\n reports-sought: 226 # optional\n reports-not-retrieved: 12 # optional\n\neligibility:\n full-text-assessed: 230\n excluded:\n n: 195\n reasons: wrong population=80, wrong intervention=60, wrong outcome=55\n```\n\n`reasons:` are `name=count` pairs. If you list more than 8, the renderer sorts them descending and aggregates the tail as `Other (n = \u2026)` so the side-box stays readable.\n\n---\n\n## 5. Included\n\n```\nincluded:\n studies: 35\n reports: 38 # one study may yield several reports\n participants: 28741 # PRISMA-IPD only\n```\n\n`studies:` is mandatory. `reports:` and `participants:` are optional extra count lines.\n\n---\n\n## 6. Dual pipeline & review kinds\n\n**Dual pipeline** \u2014 the PRISMA 2020 update added a second "Identification via other methods" column (citation searching, hand searches, expert recommendations). Add an `other:` block; the two columns merge into Screening via a Y-junction.\n\n```\nprisma\nmode: 2020-dual\n\nidentification:\n databases:\n n: 1234\n duplicates-removed: 254\n other:\n n: 56\n sources: citation-search=30, hand-search=20, expert-recommendation=6\n\nscreening:\n records-screened: 1036\n excluded:\n n: 810\n\neligibility:\n full-text-assessed: 226\n excluded:\n n: 195\n\nincluded:\n studies: 31\n```\n\n**Scoping review** \u2014 `kind: scoping-review` swaps "studies" \u2192 "sources of evidence" and re-labels the stages per Tricco et al. 2018, without changing geometry.\n\n**Updated review** \u2014 an optional `previous-studies:` block draws a dashed box on top that feeds into the identification section:\n\n```\nprevious-studies:\n n: 19\n sources: previous review=19\n```\n\n---\n\n## 7. Count arithmetic validation\n\nWith `validate-counts: warn` (default) the engine checks that the counts reconcile across stages \u2014 e.g. `databases.n + other.n \u2212 duplicates-removed = records-screened`, and that source/reason breakdowns sum to their totals. Mismatches render a small warning under the diagram (also surfaced in the SVG `<desc>` for screen readers).\n\n`validate-counts: strict` turns a mismatch into a parse error with an "off by N" message. `off` skips checking entirely.\n\n---\n\n## 8. Grammar (EBNF)\n\n```\nprisma-document = "prisma", { meta-line }, stage-block, { stage-block } ;\nmeta-line = ("mode:" | "kind:" | "title:" | "review-id:" | "validate-counts:" | "direction:") value ;\n\nstage-block = previous-block | identification-block | screening-block | eligibility-block | included-block ;\n\nprevious-block = "previous-studies:" , indent, "n:" int, [ "reports:" int ], { "sources:" pairs } ;\nidentification-block = "identification:" , indent,\n "databases:" , indent, "n:" int, { "sources:" pairs },\n [ "duplicates-removed:" int ], [ "ineligible-automation:" int ], [ "other-removed:" int ],\n [ "other:" , indent, "n:" int, { "sources:" pairs } ] ;\nscreening-block = "screening:" , indent, "records-screened:" int,\n "excluded:" , indent, "n:" int, { "reasons:" pairs },\n [ "reports-sought:" int ], [ "reports-not-retrieved:" int ] ;\neligibility-block = "eligibility:" , indent, "full-text-assessed:" int,\n "excluded:" , indent, "n:" int, { "reasons:" pairs } ;\nincluded-block = "included:" , indent, "studies:" int, [ "reports:" int ], [ "participants:" int ] ;\n\npairs = pair, { "," pair } ;\npair = (string | quoted) "=" int ;\nint = digit, { digit | "," } ; (* commas stripped: 1,234 == 1234 *)\n```\n\nIndentation is two spaces per level. Unknown keys inside a stage block are a parse error, keeping each stage well-defined.\n\n---'
@@ -1718,6 +2535,14 @@ var SYNTAX = {
1718
2535
  "pert": {
1719
2536
  "title": "PERT / CPM Network",
1720
2537
  "content": '## 1. Your first diagram\n\nEvery document starts with the `pert` keyword, an optional header, then one `task` line per activity:\n\n```\npert\nunit: days\n\ntask A "Market research" duration: 5\ntask B "Design mockups" duration: 8 after: A\ntask C "Backend API" duration: 15 after: A\ntask D "Frontend build" duration: 10 after: B, C\n```\n\nEach task carries an `<id>`, a quoted `<label>`, a `duration:`, and an optional `after:` list of predecessors. The engine adds an invisible Start and Finish, runs the schedule, and draws the six-field activity box for every task. You never type ES/EF/LS/LF \u2014 they are computed.\n\nThe header accepts:\n\n- `title: "\u2026"` \u2014 a heading drawn above the diagram.\n- `unit: days | weeks | hours | abstract` \u2014 the time unit (default `days`; purely a label, the engine is calendar-agnostic).\n- `direction: LR | TB` \u2014 left-to-right (default) or top-to-bottom.\n- `layout: network | timescaled` \u2014 the layered network (default) or a time-proportional view (\xA76).\n- `critical-tolerance: <n>` \u2014 slack \u2264 this counts as critical (default `0`; set `0.001` for three-point projects).\n- `show-sentinels: true` \u2014 draw the synthetic Start / Finish nodes (hidden by default).\n\n---\n\n## 2. The six-field activity box\n\nEvery task renders as the canonical 3\xD72 PERT/CPM rectangle:\n\n```\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 ES \u2502 Duration \u2502 EF \u2502\n\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 Task Name (ID) \u2502\n\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n\u2502 LS \u2502 Slack \u2502 LF \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n- **ES / EF** \u2014 Early Start / Early Finish, from the forward pass.\n- **LS / LF** \u2014 Late Start / Late Finish, from the backward pass.\n- **Slack** (total float) = LS \u2212 ES = LF \u2212 EF. Zero slack means the activity is on the critical path.\n\nCritical activities get a **red border** and a bold `0` slack; the project\'s critical chain reads as an unbroken red line. Every field is also mirrored onto `data-*` attributes (`data-es`, `data-slack`, `data-critical`, \u2026) so you can query the SVG without re-running the math.\n\n**On the two-colour palette.** A PERT chart is deliberately drawn in just two colours. Red for the critical path is a genuine industry convention \u2014 MS Project, Oracle Primavera P6, and PMBOK figures all use it, because "critical vs. not" is the one distinction the diagram exists to surface. Everything else stays a neutral house-blue. Adding more colours would imply categories that PERT\'s semantics don\'t have; if you need to group by team or phase, use [swimlanes](#7-swimlanes-tags-classes-and-comments) (`lane:`) instead of colour.\n\n---\n\n## 3. Dependencies (FS / SS / FF / SF + lag/lead)\n\n`after:` takes a comma-separated list of predecessor references. The default relationship is **Finish-to-Start (FS)** \u2014 a task starts once its predecessor finishes:\n\n```\ntask D "Frontend build" duration: 10 after: B, C\n```\n\nModern scheduling needs the other three Precedence-Diagramming relationships and **lag** (delay) or **lead** (negative lag):\n\n```\ntask B "Stakeholder interviews" duration: 6 after: A SS+1 # start 1d after A starts\ntask I "Documentation" duration: 4 after: D+3 # FS with a 3-day lag\ntask J "Translation" duration: 3 after: I SS-1 # start 1d before I starts (lead)\ntask K "Sign-off" duration: 1 after: I FF # finishes when I finishes\ntask L "Press release" duration: 2 after: G SF+1 # start-to-finish (rare)\n```\n\n| Form | Meaning |\n|------|---------|\n| `after: A` | Finish-to-Start, no lag (the common case) |\n| `after: A+2` or `after: A+2d` | FS with a 2-unit lag |\n| `after: A SS` / `A FF` / `A SF` | Start-to-Start / Finish-to-Finish / Start-to-Finish |\n| `after: A SS+2d` / `A FF-1d` | any type with lag (`+`) or lead (`-`) |\n\nA lag unit suffix (`d` / `w` / `h`) must match the diagram\'s `unit:` or be omitted; mixed units are rejected. FS with zero lag is unlabelled; SS/FF/SF always show their type on the edge.\n\n---\n\n## 4. Three-point (PERT) estimation\n\nWrite a duration as `O/M/P` (optimistic / most-likely / pessimistic) and the engine computes the beta-distribution expected duration **te = (O + 4M + P) / 6** and the variance **\u03C3\xB2 = ((P \u2212 O)/6)\xB2**:\n\n```\npert\ncritical-tolerance: 0.01\ntask A "Spec" duration: 2/3/5 # te = 3.17, \u03C3\xB2 = 0.25\ntask B "Build" duration: 5/8/14 after: A # te = 8.50, \u03C3\xB2 = 2.25\ntask C "Test" duration: 3/4/6 after: B\ntask D "Deploy" duration: 1/2/3 after: C\n```\n\nThe Duration field shows `te`; a small `\u03C3=\u2026` annotation appears under the name; and the project-level standard deviation (\u221A of the summed critical-path variances) is reported in the footer. Use `critical-tolerance: 0.01` so floating-point `te` values don\'t displace the visible critical path.\n\n---\n\n## 5. Milestones\n\nA milestone is a zero-duration checkpoint, drawn as a diamond. Use the `milestone` flag or `duration: 0`:\n\n```\ntask P "Cutover weekend" milestone after: O\ntask Q "Go-live" duration: 0 after: P\n```\n\n---\n\n## 6. Time-scaled layout\n\n`layout: timescaled` switches from the layered network to a network-Gantt hybrid: each activity\'s **x-position is proportional to its ES** and its **width is proportional to its duration**, with a unit time axis along the bottom. Activities are packed into lanes so nothing overlaps.\n\n```\npert\nlayout: timescaled\nunit: days\n\ntask A "Inventory" duration: 5\ntask B "Interviews" duration: 6 after: A SS+1\ntask C "Design" duration: 10 after: A, B\ntask D "Build" duration: 15 after: C\ntask E "Pilot" duration: 7 after: D\n```\n\nThis is a bridge to a Gantt view, not a replacement: there\'s no calendar, working-time mask, or resource swimlane.\n\n### Activity-on-arrow (`layout: aoa`)\n\n`layout: aoa` renders the older **activity-on-arrow** (ADM) notation you\'ll find in textbooks and on Investopedia: numbered **event** circles, **activities as labelled arrows**, and dotted **dummy activities** auto-inserted wherever an activity has two or more predecessors. You write the same activity-on-node DSL \u2014 Schematex builds the event graph and numbers the events for you.\n\n```\npert\nlayout: aoa\nunit: days\n\ntask A "create schedule" duration: 10\ntask B "buy hardware" duration: 5\ntask C "programming" duration: 20 after: A\ntask D "installation" duration: 5 after: B\ntask E "conversion" duration: 15 after: D\ntask F "test code" duration: 20 after: C, E\ntask G "write manual" duration: 15 after: E\n```\n\nAOA is a **legacy** notation (PMBOK 7 dropped it; AON is the modern standard) and it can only express **finish-to-start** logic \u2014 SS/FF/SF and lag/lead are flattened to FS with a warning. Use it for teaching, exam prep, or matching an existing textbook figure; use the default `network` (AON) layout for real scheduling.\n\n---\n\n## 7. Swimlanes, tags, classes, and comments\n\nAdd `lane: "\u2026"` to any task and the network re-groups into horizontal swimlanes \u2014 by responsible team, phase, or owner \u2014 while still computing the schedule exactly as before:\n\n```\ntask T1 "Support Account Deletion" duration: 3 lane: "Customer Account"\ntask T2 "Design a New Theme" duration: 8 lane: "Shopping Site"\ntask T3 "Apply New Theme" duration: 15 after: T2 lane: "Shopping Site"\n```\n\nLanes appear in first-declared order, each as a banded row with a label gutter on the left. Swimlanes are a presentation of the same AON network, not a different layout mode \u2014 they activate automatically when any task declares a lane.\n\n```\ntask X "External vendor work" duration: 10 after: A tags: vendor, external class: secondary\n# this is a comment\n```\n\n`tags:` emit `data-tag="\u2026"` on the node group and `class:` adds a CSS class for downstream theming. `#` and `//` start a comment to end of line.\n\n---\n\n## 8. Grammar (EBNF)\n\n```text\ndocument = "pert" NEWLINE header* task+\nheader = "title:" quoted\n | "unit:" ("days" | "weeks" | "hours" | "abstract")\n | "direction:" ("LR" | "TB")\n | "layout:" ("network" | "timescaled" | "aoa")\n | "critical-tolerance:" number\n | "show-sentinels:" boolean\n\ntask = "task" IDENT quoted "duration:" duration ("after:" reflist)? "milestone"? attrs?\n | "task" IDENT quoted "milestone" ("after:" reflist)? attrs?\nduration = number | number "/" number "/" number ; deterministic or O/M/P\nreflist = ref ("," ref)*\nref = IDENT (WS deptype)? laglead?\n | IDENT "+" number unit? ; attached FS lag sugar\ndeptype = "FS" | "SS" | "FF" | "SF"\nlaglead = ("+" | "-") number unit?\nunit = "d" | "w" | "h"\nattrs = ("tags:" idlist)? ("class:" IDENT)? ("lane:" quoted)?\n```\n\n---'
2538
+ },
2539
+ "petri": {
2540
+ "title": "Petri Net",
2541
+ "content": '## 1. Your first net\n\nEvery document starts with the `petri` keyword and an optional title, then declares **places** and **transitions** before connecting them with **arcs**:\n\n```\npetri "Minimal"\n place P1 *1\n transition T1\n place P2\n P1 -> T1\n T1 -> P2\n```\n\n- `place <id>` \u2014 a circle. `*1` sets its initial token count (the marking).\n- `transition <id>` \u2014 a bar (an event/action).\n- `<a> -> <b>` \u2014 a directed arc. Arcs are **bipartite**: every arc goes place\u2192transition or transition\u2192place, never place\u2192place or transition\u2192transition. If you write one wrong, the engine tells you which line.\n\nUnlike some Schematex diagrams, nodes are **not** auto-declared from arcs \u2014 because an undeclared id can\'t be safely typed as a place or a transition. An arc that references an unknown node is a readable error.\n\n---\n\n## 2. Marking & tokens\n\nThe **marking** is how many tokens each place holds. Three equivalent ways to set it:\n\n```\nplace P1 *3 # *n shorthand\nplace P2 tokens: 3 # explicit\nplace P3 \u2022\u2022\u2022 # literal dots (1\u20134)\n```\n\nOr set several at once with a `marking:` line:\n\n```\nmarking: P1=3, P3=2\n```\n\nTokens render as dots (up to 4, laid out in a grid) and as a numeral beyond that. Force one style with `tokens: dots | count | auto` (default `auto`).\n\n---\n\n## 3. Transitions: immediate vs timed\n\n```\ntransition fast # immediate \u2014 a solid bar (the default)\ntransition slow timed rate: 0.8 # timed \u2014 a hollow box with a rate label \u03BB\n```\n\nImmediate transitions fire in zero time and render as the classic filled bar; **timed** transitions (the [GSPN](https://en.wikipedia.org/wiki/Stochastic_Petri_net) convention) render as a hollow box and carry an optional `rate:` (\u03BB). A transition with a `rate:` is treated as timed automatically. `prio: n` sets a priority, and a `[guard]` is rendered as a label (not evaluated in v0.1).\n\n---\n\n## 4. Arc types\n\nFour arc heads cover the standard concurrency vocabulary:\n\n```\nP -> T # standard arc (filled arrowhead)\nP -> T weight: 2 # weight > 1 is labelled (weight: n or *n)\nP -o T # inhibitor \u2014 enabled only while the place is empty (hollow-circle head)\nP -- T # read / test \u2014 tests presence without consuming (no head)\nP => T # reset \u2014 empties the place when the transition fires (double head)\n```\n\nInhibitor and reset arcs are **place\u2192transition only** \u2014 the parser rejects the reverse direction.\n\n---\n\n## 5. Capacity\n\nA place can be capped. Firing that would overflow it is disabled, and the place is drawn with a dashed border and a `K=n` label:\n\n```\nplace Buffer capacity: 3\n```\n\n---\n\n## 6. The dynamics: enabled & fire\n\nThe engine computes the semantics on every render:\n\n- **Enabled** transitions (every input satisfied; inhibitor inputs empty; no output would overflow capacity) get a green ring.\n- **Dead** transitions \u2014 those that can never fire from the current marking \u2014 are muted.\n- A **`fire:`** line replays a firing sequence and renders the *resulting* marking:\n\n```\npetri\n place P1 *1\n transition T1\n place P2\n transition T2\n place P3\n P1 -> T1\n T1 -> P2\n P2 -> T2\n T2 -> P3\n fire: T1\n```\n\nAfter firing `T1`, the token has moved P1 \u2192 P2, and now **T2** is the enabled transition. The SVG `<desc>` records the marking, the enabled set, and any detected subclass (state machine / marked graph / workflow net).\n\n---\n\n## 7. Layout & themes\n\n```\nlayout: lr # left-to-right (default)\nlayout: tb # top-to-bottom\n```\n\nPlaces and transitions land on alternating layers automatically; cycles are detected and their feedback arcs routed as back-edge curves. Three themes:\n\n- **`default`** \u2014 house blue-grey, with green reserved for *enabled* and red for *inhibitor*.\n- **`monochrome`** \u2014 the faithful Murata-1989 textbook look; enabled shows as a doubled black ring (colour falls back to shape).\n- **`dark`** \u2014 Catppuccin Mocha.\n\nCJK labels and `\u300C\u2026\u300D` / `"\u2026"` quotes parse cleanly:\n\n```\npetri "\u751F\u4EA7\u6D41\u7A0B"\n place \u539F\u6599 *2 \u300C\u539F\u6750\u6599\u300D\n transition \u52A0\u5DE5\n place \u6210\u54C1\n \u539F\u6599 -> \u52A0\u5DE5 weight: 2\n \u52A0\u5DE5 -> \u6210\u54C1\n```\n\n---\n\nFull specification: [Petri Net Standard Reference](https://github.com/schematex/schematex/blob/main/docs/reference/34-PETRINET-STANDARD.md).\n\n---\n\n## Related examples\n\nReady-to-use scenarios from the examples gallery:'
2542
+ },
2543
+ "network": {
2544
+ "title": "Network Topology",
2545
+ "content": '## 1. Your first diagram\n\nEvery document starts with the `network` keyword and an optional title, then declares **devices** before connecting them with **links**:\n\n```\nnetwork "Home"\n layout: star\n router gw "Gateway"\n pc pc1\n laptop lt1\n gw -- pc1\n gw -- lt1 : wireless\n```\n\n- `<kind> <id> ["label"]` \u2014 a typed device. The kind picks the icon.\n- `<a> -- <b>` \u2014 an undirected link. `->` is directed; `==` is a LAG (aggregated) link.\n- Anything after `:` on a link is the **link spec** (type, mode, VLAN, speed, ports).\n\nDevices are **not** auto-declared from links \u2014 an undeclared id can\'t be safely typed, so a link to an unknown device is a readable error. Use `;` to put several statements on one line, and `a b c : kind` shorthand to declare several same-kind devices at once.\n\n---\n\n## 2. Device kinds\n\nPick the kind that matches the box; the icon follows the Cisco-convention silhouette.\n\n- **Infrastructure** \u2014 `router`, `switch`, `l3switch`, `firewall`, `loadbalancer`, `ap`, `wlc`, `gateway`, `modem`, `ids`, `proxy`, `vpngw`\n- **Endpoints** \u2014 `server`, `serverfarm` (`count: n`), `pc`, `laptop`, `mobile`, `ipphone`, `printer`, `storage`\n- **CCTV / security** \u2014 `camera` (with `type: fixed | bullet | dome | ptz | turret`), `nvr`, `dvr`, `poeswitch`, `encoder`, `monitor`\n- **Clouds** \u2014 `internet`, `wan`, `pstn`, `cloud`, plus `lan` (a bus bar)\n\nAliases are accepted: `multilayer`\u2192`l3switch`, `workstation`\u2192`pc`, `wifi`\u2192`ap`, `nas`/`san`\u2192`storage`, `voip`\u2192`ipphone`.\n\n```\ncamera cam1 type: dome ip: 192.168.20.11\nserverfarm farm "Server Farm" count: 4\nl3switch core1 tier: core model: "C9500"\n```\n\n---\n\n## 3. Links & annotations\n\nA link\'s appearance follows its type; everything after `:` is order-free.\n\n```\na -- b # copper / ethernet (default solid)\na -- b : fiber 10G # fiber \u2014 orange with slash ticks\na -- b : wireless # dashed\na -- b : serial # leased / WAN circuit\na -- b : poe # Power-over-Ethernet (green + tag)\na -- b : vpn "site-to-site" # dashed tunnel\na == b : lag 40G # aggregated / EtherChannel (double line)\na -- b : trunk vlan: 10,20 1G port: Gi0/1>Gi1/0/24\n```\n\n- `trunk` / `access` \u2014 port mode (a trunk should connect switch-class devices).\n- `vlan: 10` or `vlan: 10,20` \u2014 a single VLAN tints the link (skipping the reserved alarm-red).\n- `1G` / `10G` / `100M` / `40G` \u2014 speed, shown mid-link.\n- `port: near>far` \u2014 interface labels at each end.\n\n---\n\n## 4. Layout modes\n\n```\nlayout: tiered # default \u2014 band by tier: edge \u2192 core \u2192 distribution \u2192 access\nlayout: tree # hierarchical from the root\nlayout: star # hub at center, spokes on a ring\nlayout: ring # nodes on a circle\nlayout: bus # shared backbone\nlayout: mesh # full/partial mesh on a circle\nlayout: spine-leaf # two rows, every leaf auto-meshed to every spine\nlayout: manual # explicit at: x,y per device\ndirection: tb | lr # flow axis for tiered/tree\n```\n\nFor `tiered`, set `tier:` (`edge` / `core` / `distribution` / `access`) on infrastructure; untiered endpoints are placed below their switch. For `spine-leaf`, declare `spines:` and `leaves:` and the spine\u2194leaf links are generated for you.\n\n---\n\n## 5. Boundaries: sites, racks, subnets, VLANs\n\nA device can live inside nested boundary blocks. **Physical** containers (site/rack) draw a solid border; **logical** overlays (subnet/VLAN/zone/DMZ) draw a dashed tinted region.\n\n```\nnetwork "Branch"\n site hq "HQ Building" {\n rack mdf "MDF Rack" {\n firewall fw1 tier: edge\n l3switch core1 tier: core\n }\n }\n subnet lan "10.0.10.0/24" {\n switch a1 tier: access\n pc u1 "User PC" ip: 10.0.10.50\n }\n zone dmz "DMZ" {\n server web\n }\n fw1 -- core1 : 10G\n core1 -- a1 : trunk vlan: 10\n a1 -- u1\n```\n\nA device declared inside a `subnet` whose label is a CIDR has its `ip:` validated \u2014 an address outside the range is a readable error. A bare id on its own line inside a block adds an already-declared device to that group.\n\n---\n\n## 6. Validation & the no-drop guarantee\n\nThe engine guarantees every declared device and link renders \u2014 the dropped-device failure of generic tools is structurally impossible. It also checks:\n\n- **duplicate id** \u2192 error;\n- **unknown kind** \u2192 error with the nearest suggestion (`"swtich" \u2192 did you mean "switch"?`);\n- **link to an undeclared device** \u2192 error;\n- **VLAN id outside 1\u20134094** \u2192 warning (still renders);\n- **device IP outside its subnet CIDR** \u2192 error.\n\nThe SVG `<desc>` records device/link counts, the detected topology class (star / ring / bus / mesh / tree / hierarchical / spine-leaf), and any warnings.\n\n---\n\n## 7. Themes\n\n```\ntheme: default # house "network blue" Cisco-style bodies\ntheme: monochrome # clean line-art for print/audit (link meaning via line-style + tags)\ntheme: dark # Catppuccin Mocha\n```\n\nCJK labels and `\u300C\u2026\u300D` / `"\u2026"` quotes parse cleanly:\n\n```\nnetwork "\u529E\u516C\u5BA4"\n multilayer core1 \u300C\u6838\u5FC3\u4EA4\u6362\u673A\u300D\n poeswitch poe1\n camera cam1 type: dome\n core1 -- poe1 : trunk vlan: 10\n poe1 -- cam1 : poe\n```\n\n---'
1721
2546
  }
1722
2547
  };
1723
2548
 
@@ -1748,11 +2573,398 @@ function getExamplesForType(type, opts = {}) {
1748
2573
  return sorted.slice(0, limit);
1749
2574
  }
1750
2575
 
2576
+ // src/ai/profiles.ts
2577
+ var COMMON_GENERATION_RULES = [
2578
+ "Generate one diagram document with one selected diagram type.",
2579
+ "Use the canonical header and canonical forms from the generation profile first.",
2580
+ 'Use ASCII double quotes (") for generated labels and titles.',
2581
+ "Do not emit DSL comments unless the user explicitly asks for annotated source.",
2582
+ "Prefer explicit IDs and declarations when they make validation less ambiguous.",
2583
+ "Call validateDsl with the explicit selected type, fix reported errors, and validate again before returning DSL."
2584
+ ];
2585
+ var PROFILES = {
2586
+ genogram: {
2587
+ type: "genogram",
2588
+ header: 'genogram "Title"',
2589
+ mode: "family declarations + indented children",
2590
+ forms: [
2591
+ "personId [sex, birthYear, optionalAttrs]",
2592
+ 'parentA -- parentB "optional relationship label"',
2593
+ "indent children under the couple line"
2594
+ ],
2595
+ prefer: ["Declare people before emotional relationships.", 'Use `[label: "..."]` only where the syntax reference shows a label attribute.'],
2596
+ avoid: ["Avoid inline comments and speculative relationship operators."],
2597
+ repair: ["Unknown individuals usually need a declaration before the relationship line."]
2598
+ },
2599
+ ecomap: {
2600
+ type: "ecomap",
2601
+ header: 'ecomap "Title"',
2602
+ mode: "center + external systems",
2603
+ forms: [
2604
+ 'center: client [label: "Client"]',
2605
+ 'systemId [label: "System", category: family]',
2606
+ 'systemId === client [label: "support"]'
2607
+ ],
2608
+ prefer: ["Declare exactly one center.", "Declare outside systems before their connection lines."],
2609
+ avoid: ["Avoid borrowing genogram operators such as `--`."],
2610
+ repair: ["A valid render with a missing center is semantically wrong; add `center:` first."]
2611
+ },
2612
+ pedigree: {
2613
+ type: "pedigree",
2614
+ header: 'pedigree "Title"',
2615
+ mode: "clinical pedigree",
2616
+ forms: [
2617
+ "personId [sex, generationAttrs]",
2618
+ "parentA -- parentB",
2619
+ "indent offspring under the couple line"
2620
+ ],
2621
+ prefer: ["Use pedigree status/trait attributes from the syntax reference.", "Keep generation structure explicit."],
2622
+ avoid: ["Avoid genogram emotional-relationship lines in pedigree output."],
2623
+ repair: ["Declare every referenced individual before relationships."]
2624
+ },
2625
+ phylo: {
2626
+ type: "phylo",
2627
+ header: 'phylo "Title"',
2628
+ mode: "quoted Newick",
2629
+ forms: ['newick: "((A:0.1,B:0.2),C:0.3);"', 'clade Group = (A, B) [label: "Group"]'],
2630
+ prefer: ["Use Newick for first-shot generation.", "Quote the Newick string."],
2631
+ avoid: ["Avoid indent-tree mode unless the user wants a hand-authored tree."],
2632
+ repair: ["A phylo document needs exactly one tree definition: Newick or indent tree."]
2633
+ },
2634
+ sociogram: {
2635
+ type: "sociogram",
2636
+ header: 'sociogram "Title"',
2637
+ mode: "declared nodes + social ties",
2638
+ forms: ['nodeId [label: "Person"]', 'nodeA -> nodeB [label: "choice"]', "config: layout = circular"],
2639
+ prefer: ["Declare actors first.", "Use one supported layout/config value at a time."],
2640
+ avoid: ["Avoid unknown config values; some adapters keep defaults."],
2641
+ repair: ["Unknown edge endpoints need matching node declarations."]
2642
+ },
2643
+ timing: {
2644
+ type: "timing",
2645
+ header: 'timing "Title"',
2646
+ mode: "WaveDrom-compatible signals",
2647
+ forms: ["CLK: pppppppp", 'DATA: x======x data: ["A", "B"]'],
2648
+ prefer: ["Keep wave strings contiguous with no internal spaces.", "Use `data:` labels for bus segments."],
2649
+ avoid: ["Avoid unsupported WaveDrom annotation syntax."],
2650
+ repair: ["Invalid wave strings usually contain a character outside the timing wave table."]
2651
+ },
2652
+ logic: {
2653
+ type: "logic",
2654
+ header: 'logic "Title"',
2655
+ mode: "logic netlist",
2656
+ forms: ["INPUT A, B", "G1 = AND(A, B)", "OUTPUT Y = G1"],
2657
+ prefer: ["Use canonical gate names and explicit inputs/outputs."],
2658
+ avoid: ["Avoid circuit component names inside logic diagrams."],
2659
+ repair: ["Unknown gate kinds should be replaced with the closest listed logic primitive."]
2660
+ },
2661
+ circuit: {
2662
+ type: "circuit",
2663
+ header: 'circuit "Title" netlist',
2664
+ mode: "SPICE-style netlist",
2665
+ forms: ["V1 vcc 0 5V", "R1 vcc out 10k", "C1 out 0 100n"],
2666
+ prefer: ["Use netlist mode for generated schematics unless geometry is the task.", "Add explicit `type=` when an ID prefix is ambiguous."],
2667
+ avoid: ["Avoid positional cursor routing (`wire`, `at:`) for first-shot output."],
2668
+ repair: ["If a component type or pin count is ambiguous, make the symbol type explicit."]
2669
+ },
2670
+ blockdiagram: {
2671
+ type: "blockdiagram",
2672
+ header: 'blockdiagram "Title"',
2673
+ mode: "blocks, sums, signals",
2674
+ forms: ['ctrl = block("PID") [role: controller]', "err = sum(+r, -y)", "err -> ctrl -> plant"],
2675
+ prefer: ["Use named blocks and directed `->` chains."],
2676
+ avoid: ["Avoid unlabeled feedback intent; model the summing junction."],
2677
+ repair: ["Connections must point at declared blocks, sums, signals, or boundary IDs."]
2678
+ },
2679
+ ladder: {
2680
+ type: "ladder",
2681
+ header: 'ladder "Title"',
2682
+ mode: "rungs + IEC/Rockwell elements",
2683
+ forms: ['rung 1 "Run motor":', " XIC(START_PB)", " OTE(MOTOR_RUN)"],
2684
+ prefer: ["Use uppercase element names.", "Use `parallel:` + `branch:` only for OR branches."],
2685
+ avoid: ["Avoid variable declarations and ST syntax."],
2686
+ repair: ["Element typos are repairable from parser suggestions; keep the tag in parentheses."]
2687
+ },
2688
+ sld: {
2689
+ type: "sld",
2690
+ header: 'sld "Title"',
2691
+ mode: "equipment assignments + power-flow edges",
2692
+ forms: ['util = utility [label: "Grid"]', 'xfmr = transformer [rating: "500 kVA"]', "util -> xfmr -> load is not allowed; use one edge per line"],
2693
+ prefer: ["Declare equipment as `id = nodeType [attrs]`.", "Use one `from -> to` connection per line."],
2694
+ avoid: ["Avoid generic flowchart node syntax."],
2695
+ repair: ["Unknown equipment types often have a suggested canonical type or alias."]
2696
+ },
2697
+ entity: {
2698
+ type: "entity",
2699
+ header: 'entity-structure "Title"',
2700
+ mode: "legal entities + ownership edges",
2701
+ forms: ['entity holdco "HoldCo" corp@US', 'entity opco "OpCo" llc@DE', "holdco -> opco : 100%"],
2702
+ prefer: ["Use `entity` declarations before ownership edges.", "Keep legal form and jurisdiction explicit when known."],
2703
+ avoid: ["Avoid database schema terminology; use `erd` for tables and FKs."],
2704
+ repair: ["Unknown ownership endpoints need entity declarations."]
2705
+ },
2706
+ fishbone: {
2707
+ type: "fishbone",
2708
+ header: 'fishbone "Title"',
2709
+ mode: "effect + cause categories",
2710
+ forms: ['effect "Late Delivery"', 'category process "Process"', 'process: "Handoff delay"'],
2711
+ prefer: ["Use one effect and structured categories for generated DSL."],
2712
+ avoid: ["Avoid mixing compact alien syntax when a structured category form works."],
2713
+ repair: ["If a cause lands nowhere, add or reference its category explicitly."]
2714
+ },
2715
+ venn: {
2716
+ type: "venn",
2717
+ header: 'venn "Title"',
2718
+ mode: "declared-set region counts",
2719
+ forms: ['set A "Group A"', 'set B "Group B"', "A & B : 120", "A only : 80"],
2720
+ prefer: ["Use declared sets plus region counts for first-shot Venn output.", "Use Euler relations only when subset/disjoint structure is the request."],
2721
+ avoid: ["Avoid mixing enumeration mode with explicit region counts."],
2722
+ repair: ["Declare every set before region or Euler relation lines."]
2723
+ },
2724
+ flowchart: {
2725
+ type: "flowchart",
2726
+ header: 'flowchart TD "Title"',
2727
+ mode: "Mermaid-compatible nodes + edges",
2728
+ forms: ["start([Start]) --> check{Decision?}", "check -->|Yes| done([Done])"],
2729
+ prefer: ["Choose one direction in the header.", "Declare shapes explicitly when shape matters."],
2730
+ avoid: ["Avoid subgraph complexity unless grouping is part of the request."],
2731
+ repair: ["A bad header direction fails early; use TD, TB, BT, LR, or RL."]
2732
+ },
2733
+ mindmap: {
2734
+ type: "mindmap",
2735
+ header: "mindmap",
2736
+ mode: "Markdown headings + bullets",
2737
+ forms: ["# Root", "## Branch", "- Child item"],
2738
+ prefer: ["Use exactly one `#` root heading.", "Use bullets/headings instead of graph edges."],
2739
+ avoid: ["Avoid comments in the body."],
2740
+ repair: ["An orphan branch means the root `#` heading is missing."]
2741
+ },
2742
+ matrix: {
2743
+ type: "matrix",
2744
+ header: 'matrix "Title"',
2745
+ mode: "quadrant scatter",
2746
+ forms: ["x-axis: Low -> High", "y-axis: Low -> High", '"Item" at (0.25, 0.8)'],
2747
+ prefer: ["Use quadrant scatter for first-shot prioritization/portfolio requests.", "Use built-in templates when the framework is named."],
2748
+ avoid: ["Avoid heatmap, correlation, and table modes unless the user asks for that form."],
2749
+ repair: ["Point coordinates are normalized fractions; keep them in `[0,1]`."]
2750
+ },
2751
+ orgchart: {
2752
+ type: "orgchart",
2753
+ header: 'orgchart "Title"',
2754
+ mode: "indented hierarchy",
2755
+ forms: ['ceo: "Name" | CEO', ' cto: "Name" | CTO', ' eng: "Name" | Engineer'],
2756
+ prefer: ["Use indentation for the reporting tree.", "Use dotted explicit edges only for matrix reporting."],
2757
+ avoid: ["Avoid mixing explicit report edges with indentation unless needed."],
2758
+ repair: ["Duplicate IDs and unknown explicit edge endpoints are parse failures."]
2759
+ },
2760
+ decisiontree: {
2761
+ type: "decisiontree",
2762
+ header: 'decisiontree "Title"',
2763
+ mode: "taxonomy questions",
2764
+ forms: ['question "Question?"', ' yes: answer "Outcome"', ' no: answer "Other outcome"'],
2765
+ prefer: ["Use taxonomy mode for troubleshooting and triage.", "Select `decisiontree:decision` or `decisiontree:ml` only when expected value or ML is explicit."],
2766
+ avoid: ["Avoid mixing mode-specific keywords."],
2767
+ repair: ["Indent each child under its parent and use the branch prefix required by the selected mode."]
2768
+ },
2769
+ timeline: {
2770
+ type: "timeline",
2771
+ header: 'timeline "Title"',
2772
+ mode: "dated events",
2773
+ forms: ['2026-05-22: "Event"', '2026-06-01 - 2026-06-30: "Phase"', '2026-07-01: milestone "Launch"'],
2774
+ prefer: ["Use dated events/ranges and `milestone` for important points."],
2775
+ avoid: ["Avoid custom row keys when a date is known."],
2776
+ repair: ["Quote labels and keep the colon after the date/range."]
2777
+ },
2778
+ state: {
2779
+ type: "state",
2780
+ header: 'state "Title"',
2781
+ mode: "Schematex statechart core",
2782
+ forms: ["initial Start", "Start --> Running : event", "Running --> Done", "final Done"],
2783
+ prefer: ["Use `state` header for generated DSL.", "Use Mermaid `stateDiagram-v2` only when adapting Mermaid input."],
2784
+ avoid: ["Avoid composite/concurrent-state syntax until the request needs it."],
2785
+ repair: ["Use `-->` transitions and a named state between initial/final aliases."]
2786
+ },
2787
+ pid: {
2788
+ type: "pid",
2789
+ header: 'pid "Title"',
2790
+ mode: "equipment + process lines",
2791
+ forms: ["equip T-101 : tank_atm", "equip P-101 : pump_centrifugal", "line L1 from T-101.bottom to P-101.in"],
2792
+ prefer: ["Declare equipment first, then process/signal lines."],
2793
+ avoid: ["Avoid SLD electrical nodes in a P&ID."],
2794
+ repair: ["Unknown equipment and instrument categories must come from the catalog."]
2795
+ },
2796
+ erd: {
2797
+ type: "erd",
2798
+ header: "erd",
2799
+ mode: "table blocks + named cardinality refs",
2800
+ forms: ["table User { id int PK; email varchar }", "table Order { id int PK; user_id int FK -> User.id }", "ref Order.user_id many-mandatory -- one-mandatory User.id"],
2801
+ prefer: ["Use named cardinality tokens for generated refs.", "Keep FK targets explicit."],
2802
+ avoid: ["Avoid Mermaid cardinality glyphs unless converting Mermaid input."],
2803
+ repair: ["Unknown cardinality tokens and unterminated table blocks fail validation."]
2804
+ },
2805
+ breadboard: {
2806
+ type: "breadboard",
2807
+ header: "breadboard",
2808
+ mode: "parts + wires blocks",
2809
+ forms: ['title: "Prototype"', "parts", "wires"],
2810
+ prefer: ["Use physical parts and addressable tie points.", "Keep schematic tasks on `circuit` instead."],
2811
+ avoid: ["Avoid abstract netlist syntax in breadboard output."],
2812
+ repair: ["Missing parts or invalid breadboard connection endpoints need the breadboard syntax reference."]
2813
+ },
2814
+ bpmn: {
2815
+ type: "bpmn",
2816
+ header: "bpmn",
2817
+ mode: "pool/lane objects + flows",
2818
+ forms: ['pool "Service" {', ' lane "Worker" { A: start "Request" }', "flows", "A --> B"],
2819
+ prefer: ["Use pools, lanes, and a `flows` block.", "Use `~~>` only for cross-pool message flow."],
2820
+ avoid: ["Avoid generic flowchart syntax for BPMN semantics."],
2821
+ repair: ["Sequence flows cannot cross pools; switch to message flow when needed."]
2822
+ },
2823
+ fbd: {
2824
+ type: "fbd",
2825
+ header: 'fbd "Title"',
2826
+ mode: "networks + blocks",
2827
+ forms: ["var Start: bool", 'network "Logic":', "Motor = AND(Start, Safe)"],
2828
+ prefer: ["Use network/expression forms from the FBD syntax guide."],
2829
+ avoid: ["Avoid ladder rung syntax in FBD output."],
2830
+ repair: ["Invalid block calls need a supported block and valid arguments."]
2831
+ },
2832
+ sfc: {
2833
+ type: "sfc",
2834
+ header: 'sfc "Title"',
2835
+ mode: "steps + transitions",
2836
+ forms: ["step Idle [initial]", "transition Idle -> Run : Start", "step Run"],
2837
+ prefer: ["Use explicit steps and transitions before alternative/simultaneous branches."],
2838
+ avoid: ["Avoid UML state syntax in SFC output."],
2839
+ repair: ["Every transition step reference must resolve to a declared step."]
2840
+ },
2841
+ prisma: {
2842
+ type: "prisma",
2843
+ header: "prisma",
2844
+ mode: "PRISMA 2020 single pipeline",
2845
+ forms: ["identification:", " databases:", " n: 1000", "screening:", "included:"],
2846
+ prefer: ["Use the required four stages and mandatory counts.", "Use single-pipeline 2020 mode unless other-methods data is present."],
2847
+ avoid: ["Avoid generic flowchart boxes for PRISMA requests."],
2848
+ repair: ["Missing stage blocks or mandatory `n` fields are intentional parser errors."]
2849
+ },
2850
+ usecase: {
2851
+ type: "usecase",
2852
+ header: "usecase",
2853
+ mode: "declarative UML use cases",
2854
+ forms: ['system: "System"', "actor: Customer", 'usecase: "Checkout" as Checkout', "Customer -- Checkout"],
2855
+ prefer: ["Use declarative actor/usecase lines for generated DSL."],
2856
+ avoid: ["Avoid PlantUML inline form unless converting PlantUML-like input."],
2857
+ repair: ["Include/extend relations must connect use cases, not actors."]
2858
+ },
2859
+ pert: {
2860
+ type: "pert",
2861
+ header: "pert",
2862
+ mode: "AON tasks + dependencies",
2863
+ forms: ['task A "Spec" duration: 3', 'task B "Build" duration: 8 after: A'],
2864
+ prefer: ["Use AON network tasks first.", "Use time-scaled/AOA layouts only when requested."],
2865
+ avoid: ["Avoid writing computed ES/EF/LS/LF fields yourself."],
2866
+ repair: ["Task IDs must exist before they are used in `after:` lists."]
2867
+ },
2868
+ sequence: {
2869
+ type: "sequence",
2870
+ header: 'sequence "Title"',
2871
+ mode: "participants + messages",
2872
+ forms: ["actor User", 'participant API as "API"', "User -> API : request", "API --> User : response"],
2873
+ prefer: ["Start with participants/messages, then add fragments only if control flow matters."],
2874
+ avoid: ["Avoid Mermaid `sequenceDiagram` header; this parser uses `sequence`."],
2875
+ repair: ["Unmatched `end`, `else`, or activation statements are validation failures."]
2876
+ },
2877
+ petri: {
2878
+ type: "petri",
2879
+ header: 'petri "Title"',
2880
+ mode: "declared places + transitions + arcs",
2881
+ forms: [
2882
+ "place P1 *1",
2883
+ "transition T1",
2884
+ "P1 -> T1",
2885
+ "T1 -> P2",
2886
+ "P3 -> T2 weight: 2"
2887
+ ],
2888
+ prefer: [
2889
+ "Declare every place and transition before any arc references it.",
2890
+ "Use `*n` or `tokens: n` for the initial marking and `weight: n` for arc multiplicity > 1.",
2891
+ "Keep arcs bipartite: every arc goes place\u2192transition or transition\u2192place."
2892
+ ],
2893
+ avoid: [
2894
+ "Avoid place\u2192place or transition\u2192transition arcs.",
2895
+ "Avoid `-o`/`=>` arcs from a transition; inhibitor and reset arcs are place\u2192transition only."
2896
+ ],
2897
+ repair: [
2898
+ "An 'unknown node' error means an arc references an undeclared place/transition \u2014 declare it first.",
2899
+ "Set the initial marking so the transitions you intend to be enabled actually have enough input tokens."
2900
+ ]
2901
+ },
2902
+ network: {
2903
+ type: "network",
2904
+ header: 'network "Title"',
2905
+ mode: "typed device declarations + links + optional boundaries",
2906
+ forms: [
2907
+ "layout: tiered",
2908
+ 'router r1 "Edge Router"',
2909
+ "switch core1 tier: core",
2910
+ "camera cam1 type: dome ip: 192.168.20.11",
2911
+ "core1 -- poe1 : trunk vlan: 20 1G",
2912
+ "poe1 -- cam1 : poe",
2913
+ 'subnet cams "192.168.20.0/24" { cam1 poe1 }'
2914
+ ],
2915
+ prefer: [
2916
+ "Declare every device with its kind before any link references it.",
2917
+ "Pick one `layout:` \u2014 tiered (default), tree, star, ring, bus, mesh, spine-leaf, or manual.",
2918
+ "Set `tier:` (edge/core/distribution/access) on infrastructure to drive the hierarchical bands.",
2919
+ "Annotate links after `:` with link-type (fiber/wireless/serial/poe/vpn/lag), mode (trunk/access), `vlan:`, `port:`, and a speed like 1G/10G."
2920
+ ],
2921
+ avoid: [
2922
+ "Avoid linking to an undeclared device id.",
2923
+ "Avoid VLAN ids outside 1\u20134094 and device IPs outside their subnet's CIDR."
2924
+ ],
2925
+ repair: [
2926
+ "An 'undeclared device' error means a link references an id with no `kind id` declaration \u2014 declare it first.",
2927
+ "A subnet-membership error means a device `ip:` falls outside the subnet label CIDR \u2014 fix the IP or the CIDR."
2928
+ ]
2929
+ }
2930
+ };
2931
+ function getGenerationProfile(type) {
2932
+ return PROFILES[type];
2933
+ }
2934
+
1751
2935
  // src/ai/syntax.ts
1752
- function getSyntaxForType(syntaxKey) {
2936
+ function getSyntaxForType(syntaxKey, type, detail = "canonical") {
1753
2937
  const s = SYNTAX[syntaxKey];
1754
2938
  if (!s) return void 0;
1755
- return { key: syntaxKey, ...s };
2939
+ return {
2940
+ key: syntaxKey,
2941
+ title: s.title,
2942
+ detail,
2943
+ content: detail === "reference" ? s.content : buildCanonicalSyntax(getGenerationProfile(type))
2944
+ };
2945
+ }
2946
+ function buildCanonicalSyntax(profile) {
2947
+ return [
2948
+ "# Canonical generation syntax",
2949
+ "",
2950
+ 'Use this compact path for new DSL generation. Ask for `detail: "reference"` only when the request needs advanced forms or an imported adapter.',
2951
+ "",
2952
+ "## Generation profile",
2953
+ "",
2954
+ `- Canonical type: \`${profile.type}\``,
2955
+ `- Canonical header: \`${profile.header}\``,
2956
+ `- Preferred mode: ${profile.mode}`,
2957
+ bulletSection("Core forms", profile.forms),
2958
+ bulletSection("Prefer", profile.prefer),
2959
+ bulletSection("Avoid by default", profile.avoid),
2960
+ bulletSection("Repair checks", profile.repair),
2961
+ "## Shared generation rules",
2962
+ "",
2963
+ ...COMMON_GENERATION_RULES.map((rule) => `- ${rule}`)
2964
+ ].filter((part) => part !== "").join("\n");
2965
+ }
2966
+ function bulletSection(title, items) {
2967
+ return [`### ${title}`, "", ...items.map((item) => `- ${item}`), ""].join("\n");
1756
2968
  }
1757
2969
 
1758
2970
  // src/ai/tools.ts
@@ -1766,14 +2978,14 @@ function listDiagrams() {
1766
2978
  standard: d.standard
1767
2979
  }));
1768
2980
  }
1769
- function getSyntax(type) {
2981
+ function getSyntax(type, opts = {}) {
1770
2982
  const meta = getDiagramMeta(type);
1771
2983
  if (!meta) {
1772
2984
  throw new Error(
1773
2985
  `Unknown diagram type '${type}'. Call listDiagrams() for valid types.`
1774
2986
  );
1775
2987
  }
1776
- const syntax = getSyntaxForType(meta.syntaxKey);
2988
+ const syntax = getSyntaxForType(meta.syntaxKey, meta.type, opts.detail);
1777
2989
  if (!syntax) {
1778
2990
  throw new Error(`No syntax doc available for '${type}' (key: ${meta.syntaxKey}).`);
1779
2991
  }
@@ -1795,47 +3007,78 @@ function getExamples(type, opts = {}) {
1795
3007
  return { type: meta.type, count: examples.length, examples };
1796
3008
  }
1797
3009
  function validateDsl(type, dsl) {
1798
- const config = type ? { type } : void 0;
1799
- try {
1800
- chunk3YUUC6RN_cjs.parse(dsl, config);
1801
- return { ok: true, type: type ?? resolveTypeFromText(dsl) };
1802
- } catch (err) {
1803
- return {
1804
- ok: false,
1805
- type: type ?? resolveTypeFromText(dsl),
1806
- errors: [extractError(err)]
1807
- };
3010
+ const resolvedType = type ? resolveDiagramType(type) : void 0;
3011
+ const config = type ? { type: resolvedType ?? type } : void 0;
3012
+ const result = chunk25ZON47K_cjs.parseResult(dsl, config);
3013
+ if (result.ok) {
3014
+ return { ok: true, type: result.type };
1808
3015
  }
3016
+ return {
3017
+ ok: false,
3018
+ type: result.type ?? resolvedType ?? resolveTypeFromText(dsl),
3019
+ errors: result.diagnostics.map(
3020
+ (diagnostic) => toValidationError(diagnostic, result.type ?? resolvedType)
3021
+ )
3022
+ };
1809
3023
  }
1810
3024
  function renderDsl(type, dsl, options = {}) {
3025
+ const resolvedType = type ? resolveDiagramType(type) : void 0;
1811
3026
  const config = {
1812
3027
  ...options,
1813
- ...type ? { type } : {}
3028
+ ...type ? { type: resolvedType ?? type } : {}
1814
3029
  };
1815
- try {
1816
- const svg = chunk3YUUC6RN_cjs.render(dsl, config);
1817
- return { ok: true, type: type ?? resolveTypeFromText(dsl), svg };
1818
- } catch (err) {
3030
+ const result = chunk25ZON47K_cjs.renderResult(dsl, config);
3031
+ if (result.ok) {
1819
3032
  return {
1820
- ok: false,
1821
- type: type ?? resolveTypeFromText(dsl),
1822
- errors: [extractError(err)]
3033
+ ok: true,
3034
+ status: result.status,
3035
+ type: result.type,
3036
+ svg: result.svg
1823
3037
  };
1824
3038
  }
3039
+ return {
3040
+ ok: false,
3041
+ status: result.status,
3042
+ type: result.type ?? resolvedType ?? resolveTypeFromText(dsl),
3043
+ svg: result.svg,
3044
+ errors: result.diagnostics.map(
3045
+ (diagnostic) => toValidationError(diagnostic, result.type ?? resolvedType)
3046
+ )
3047
+ };
1825
3048
  }
1826
3049
  function resolveTypeFromText(text) {
1827
3050
  const first = text.trim().split(/\s+|\n/)[0]?.toLowerCase() ?? "";
1828
- const meta = DIAGRAM_REGISTRY.find((d) => d.type === first);
1829
- return meta?.type ?? null;
3051
+ return resolveDiagramType(first) ?? null;
3052
+ }
3053
+ function toValidationError(diagnostic, type) {
3054
+ return {
3055
+ line: diagnostic.line,
3056
+ column: diagnostic.column,
3057
+ source: diagnostic.source,
3058
+ message: diagnostic.message,
3059
+ hint: diagnostic.hint ?? repairHint(type)
3060
+ };
3061
+ }
3062
+ function repairHint(type) {
3063
+ const resolved = type ? resolveDiagramType(type) : void 0;
3064
+ const profile = resolved ? getGenerationProfile(resolved) : void 0;
3065
+ const typeHint = profile?.repair[0];
3066
+ return [
3067
+ typeHint,
3068
+ "Fix the reported DSL error, then call validateDsl again before rendering or returning DSL."
3069
+ ].filter(Boolean).join(" ");
1830
3070
  }
1831
3071
 
1832
3072
  exports.DIAGRAM_REGISTRY = DIAGRAM_REGISTRY;
3073
+ exports.DIAGRAM_SINCE = DIAGRAM_SINCE;
1833
3074
  exports.getAllDiagramTypes = getAllDiagramTypes;
1834
3075
  exports.getDiagramMeta = getDiagramMeta;
3076
+ exports.getDiagramSince = getDiagramSince;
1835
3077
  exports.getExamples = getExamples;
1836
3078
  exports.getSyntax = getSyntax;
1837
3079
  exports.listDiagrams = listDiagrams;
1838
3080
  exports.renderDsl = renderDsl;
3081
+ exports.resolveDiagramType = resolveDiagramType;
1839
3082
  exports.validateDsl = validateDsl;
1840
- //# sourceMappingURL=chunk-NWPCY65Z.cjs.map
1841
- //# sourceMappingURL=chunk-NWPCY65Z.cjs.map
3083
+ //# sourceMappingURL=chunk-YVDUEUFV.cjs.map
3084
+ //# sourceMappingURL=chunk-YVDUEUFV.cjs.map