@stacksjs/defaults 0.75.361 → 0.75.362

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 (208) hide show
  1. package/ai/skills/stacks-ai/SKILL.md +22 -0
  2. package/ai/skills/stacks-native/CATALOG.md +2 -2
  3. package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
  4. package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
  5. package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
  6. package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
  7. package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
  8. package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
  9. package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
  10. package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
  11. package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
  12. package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
  13. package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
  14. package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
  15. package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
  16. package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
  17. package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
  18. package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
  19. package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
  20. package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
  21. package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
  22. package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
  23. package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
  24. package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
  25. package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
  26. package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
  27. package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
  28. package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
  29. package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
  30. package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
  31. package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
  32. package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
  33. package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
  34. package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
  35. package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
  36. package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
  37. package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
  38. package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
  39. package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
  40. package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
  41. package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
  42. package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
  43. package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
  44. package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
  45. package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
  46. package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
  47. package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
  48. package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
  49. package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
  50. package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
  51. package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
  52. package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
  53. package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
  54. package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
  55. package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
  56. package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
  57. package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
  58. package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
  59. package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
  60. package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
  61. package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
  62. package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
  63. package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
  64. package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
  65. package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
  66. package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
  67. package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
  68. package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
  69. package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
  70. package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
  71. package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
  72. package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
  73. package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
  74. package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
  75. package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
  76. package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
  77. package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
  78. package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
  79. package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
  80. package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
  81. package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
  82. package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
  83. package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
  84. package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
  85. package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
  86. package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
  87. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
  88. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
  89. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
  90. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
  91. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
  92. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
  93. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
  94. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
  95. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
  96. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
  97. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
  98. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
  99. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
  100. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
  101. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
  102. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
  103. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
  104. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
  105. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
  106. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
  107. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
  108. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
  109. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
  110. package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
  111. package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
  112. package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
  113. package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
  114. package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
  115. package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
  116. package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
  117. package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
  118. package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
  119. package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
  120. package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
  121. package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
  122. package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
  123. package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
  124. package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
  125. package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
  126. package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
  127. package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
  128. package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
  129. package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
  130. package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
  131. package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
  132. package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
  133. package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
  134. package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
  135. package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
  136. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
  137. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
  138. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
  139. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
  140. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
  141. package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
  142. package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
  143. package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
  144. package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
  145. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
  146. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
  147. package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
  148. package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
  149. package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
  150. package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
  151. package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
  152. package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
  153. package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
  154. package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
  155. package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
  156. package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
  157. package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
  158. package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
  159. package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
  160. package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
  161. package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
  162. package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
  163. package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
  164. package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
  165. package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
  166. package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
  167. package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
  168. package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
  169. package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
  170. package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
  171. package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
  172. package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
  173. package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
  174. package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
  175. package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
  176. package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
  177. package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
  178. package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
  179. package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
  180. package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
  181. package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
  182. package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
  183. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
  184. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
  185. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
  186. package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
  187. package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
  188. package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
  189. package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
  190. package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
  191. package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
  192. package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
  193. package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
  194. package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
  195. package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
  196. package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
  197. package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
  198. package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
  199. package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
  200. package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
  201. package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
  202. package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
  203. package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
  204. package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
  205. package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
  206. package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
  207. package/ide/vscode/package.json +1 -1
  208. package/package.json +2 -2
@@ -0,0 +1,246 @@
1
+ # Entity-Relationship Renderer
2
+
3
+ Render `diagram_type: "erd"` JSON files into the standard Archify HTML template.
4
+
5
+ ```bash
6
+ node archify/renderers/erd/render-erd.mjs input.erd.json output.html
7
+ ```
8
+
9
+ The renderer validates input against `archify/schemas/erd.schema.json` with the
10
+ bundled standalone validator. No dependency installation is required.
11
+
12
+ If `output.html` is omitted, the renderer uses `meta.output` from the JSON file
13
+ or falls back to `erd.html` in the current working directory.
14
+
15
+ After rendering, run the artifact checker:
16
+
17
+ ```bash
18
+ node archify/scripts/check-render-output.mjs output.html
19
+ ```
20
+
21
+ ## Input
22
+
23
+ ERD JSON files must set:
24
+
25
+ ```json
26
+ {
27
+ "schema_version": 1,
28
+ "diagram_type": "erd",
29
+ "meta": { "title": "Order management schema" },
30
+ "entities": [],
31
+ "relationships": [],
32
+ "cards": []
33
+ }
34
+ ```
35
+
36
+ An entity carries the table name in `label`, its attributes in `attributes`, and a
37
+ `row`/`col` cell in the banded grid. The box height follows the declared attribute
38
+ count, so the layout never needs hand-measured sizes. An attribute's `key` accepts
39
+ `pk`, `fk`, or `uk`, or a list of them: a junction table's column is often part of
40
+ the primary key and a foreign key to the same parent, and stating one role would
41
+ hide the other. Each role draws its own badge, tinted in its legend's family, a
42
+ role named twice draws once, and the key column is measured from the widest badge
43
+ run in the table — never narrower than the shared width, wider when the badges
44
+ need it. Every `fk`
45
+ role — alone or inside a list — must also name its target as `"entity.attribute"`
46
+ in `references`, and that target is checked against the declared entities and
47
+ attributes.
48
+
49
+ A relationship reads `from` -> `to` and is drawn with crow's foot notation at
50
+ both ends. The foot opens toward the entity whose cardinality it describes: its
51
+ toes stand on that table's edge and its apex sits one foot back along the line,
52
+ so the glyph cannot be read as an arrowhead pointing at the table. Both ends
53
+ declare their own maximum: `fromCardinality`/`toCardinality` take `one` or
54
+ `many` and are required, because a maximum the author never stated is a fact the
55
+ diagram would be inventing; `fromOptional`/`toOptional` add the optional circle
56
+ past the apex and lower that end's minimum from one to zero.
57
+ `identifying: false` draws the non-identifying dashed line, and it owns the dash:
58
+ a `variant` that would draw the relationship solid alongside it is rejected by
59
+ the schema, because the solid line would state the opposite of the fact. The
60
+ variant still selects the accent for every identifying relationship.
61
+
62
+ ## Layout and routing
63
+
64
+ `layout.mode: "grid"` places each entity by `row`/`col`. Every raw attribute index
65
+ owns the width of its widest entity and every raw row index owns the height of
66
+ its tallest, so boxes in one band line up without hand measurement and an empty
67
+ index leaves a routing channel. `pos` overrides the cell for a bounded
68
+ exception.
69
+
70
+ Relationships route through the orthogonal router the other typed renderers use
71
+ (`../architecture/routing.mjs`), so the same contracts apply: automatic port
72
+ spreading on a shared entity, the endpoint-side contract, the route-rhythm
73
+ floors, and the universal Clean Flow gate. The ERD is a caller, not a fork: it
74
+ passes `sideFor`, `maxPortSpacing`, `preferredCandidates`, and
75
+ `compositionFloors: false`, and reads the planning-free `inferredSides` and the
76
+ `ports` map. Each option is inert when omitted, which is how architecture and
77
+ lifecycle keep their routing byte for byte. Five differences are specific to this
78
+ renderer:
79
+
80
+ - Endpoints resolve horizontal-first, like every other non-architecture type: a
81
+ relationship leaves and enters on the left/right whenever the tables sit side
82
+ by side, and only falls back to a top/bottom port when they share a column. The
83
+ shared router's default is architecture's dominant-axis inference, which would
84
+ split one fan-in into two groups whenever a target happened to sit further away
85
+ vertically than horizontally.
86
+ - Automatic ports on one side spread up to 24 units apart, because a cardinality
87
+ glyph is 14 units tall: at the shared 14-unit spread two crow's feet on one table
88
+ edge read as a single smudge. That spacing is a cap, not a floor — a side only
89
+ fits `(extent - 32) / (ends - 1)` — so a table with more relationship ends than
90
+ its side has room for fails with `layout/marker-capacity`, which names the
91
+ table, the side, the spacing, and the three ways out.
92
+ - Relationships that would share one corridor get a deterministic lane offset,
93
+ so parallel foreign keys never sit on the identical channel line.
94
+ - Relationships that share one entity side (a fan-in of foreign keys, or a
95
+ fan-out parent) and one marker style bundle onto a single trunk just outside
96
+ that side: one bus line with a short branch per relationship, instead of a
97
+ sheaf of parallel lanes. The trunk bridges one port step of uncovered bus, so
98
+ the port spread above still reads as one line. The trunk is only a preferred
99
+ route — a branch that would break the endpoint contract or clip an entity falls
100
+ back to the ordinary families and stays unbundled, and anything authored
101
+ (`route`, `via`, `labelAt`) keeps its own line. Logical routes still traverse
102
+ the trunk, so every gate, label, and the layout report see the full geometry.
103
+ 自动补出的主干桥接还会检查无关关系及已接受的其他桥接;出现交叉、接触或
104
+ 重叠时保留分离的主干段,避免把独立关系画成同一条总线。检查按双方描边的
105
+ 半宽之和留出间隔,并计入无关关系的显式 `width`;中心线未相交但描边接触
106
+ 也会阻止桥接。这个回退不移动
107
+ 实体、显式途经点或端口,也不改变关系基数;无障碍的同簇分支仍可共享主干。
108
+ - Entity boxes are opaque obstacles. When a third entity sits between two
109
+ aligned anchors, the shared obstacle grid routes around it, and a route that
110
+ still cannot clear an unrelated entity fails with
111
+ `clean-flow/edge-through-node` rather than being drawn through the box.
112
+
113
+ The renderer also reports ER-specific diagnostics: unknown entities in
114
+ `from`/`to`, unknown attribute names and unresolved `references`, duplicate attribute
115
+ names, self-referencing relationships, overlapping entity boxes, and rows whose
116
+ text cannot fit inside the declared entity width.
117
+
118
+ ## Domain bands
119
+
120
+ `tag` names a table's functional domain, and a domain is drawn as a band when the
121
+ tables carrying that tag fill a solid rectangle of grid cells — every cell of
122
+ their bounding box holds one of them. A single row, a single column, and a filled
123
+ rectangular block are all that same claim (an L or diagonal is not); a one-table
124
+ domain is that claim at its smallest. The
125
+ band is measured from the placed boxes and carries the tag as its caption, which
126
+ is what makes a grouped schema readable at a glance: the reader sees blocks
127
+ instead of six equally weighted boxes.
128
+
129
+ A tag whose members do not fill one rectangle earns no band, because a band
130
+ around them would enclose cells the domain does not own and claim a grouping the
131
+ placement contradicts. The band is the only place a domain name is drawn, so the
132
+ renderer returns `erd/domain-not-drawn` rather than publishing a diagram whose
133
+ tables are complete and whose domain cannot be named. Move the members into one
134
+ rectangle, split the tag, or drop it. The same diagnostic covers the other ways the
135
+ name can go missing: a member placed outside the grid, because a band is measured
136
+ from cells and absolute coordinates have none; two members in one cell, which the
137
+ grid placement check reports instead so the author gets the pair that has to move;
138
+ a band whose padding would cover an unrelated table, which happens when
139
+ `layout.gapX`/`gapY` are smaller than the padding; and a caption the canvas does
140
+ not reach, which a `layout.origin` closer to the edge than the padding produces.
141
+
142
+ The header carries the table title alone. It is centered on the table's own
143
+ center line and held inside the interval it is measured against — the sigil porch
144
+ on the left, the padding on the right — so it does not run past either while the
145
+ shared text model's advance estimate holds. A title that cannot fit the header at
146
+ a readable size raises `erd/header-text-capacity`; widen the table or shorten the
147
+ title. Existing header and entity dimensions are preserved unless the author
148
+ changes them. An authored `sublabel` is not drawn: it stays on the node as
149
+ `data-node-sublabel` and in the accessible name.
150
+
151
+ ## Cardinality ink
152
+
153
+ The crow's foot, the single bar, and the optional circle are drawn in the theme's
154
+ muted text ink rather than the shared arrow token: at the light theme's arrow
155
+ colour a glyph over a table fill sits near 2:1 contrast and reads as a smudge,
156
+ while the muted text ink clears the 3:1 non-text floor in both themes. The legend
157
+ repeats the same ink and stroke weight, and its labels render one step larger and
158
+ heavier than the shared legend default, so the legend shows the reader the exact
159
+ glyph the diagram draws.
160
+
161
+ ## The legend band
162
+
163
+ The default legend is part of the generated content, so an automatic canvas is
164
+ sized to hold it: the shared measurement decides whether the reserved strip
165
+ fits, and the drawing area grows a band at a time until it does. A route or a
166
+ relationship label that still collides with the placed band is a real capacity
167
+ failure and raises the diagnostic instead of dropping the key, and an authored
168
+ `meta.viewBox` — a fixed drawing area — names the capacity it lacks
169
+ (`legend/vertical-overflow`, `legend/label-too-wide`, or `legend/content-overlap`,
170
+ each with the fix that resolves it).
171
+
172
+ The legend has two groups split by a divider. Key entries draw the same badge a
173
+ field row draws; relationship entries draw a short route meeting a table edge
174
+ with the marker's own glyph. ERD kinds are column keys and relationship ends,
175
+ not node kinds, so the entries stay out of the Viewer's counted node lens, which
176
+ would badge each one with a false zero. Hovering or keyboard-focusing an entry
177
+ highlights its matches in the SVG itself: a key fades the other field rows and
178
+ outlines the matching badges; a relationship end recolours that glyph wherever
179
+ it is drawn and fades routes that do not carry it. Static exports keep the plain
180
+ legend.
181
+
182
+ A canvas taller than one screen is a normal schema, not a defect. An automatic
183
+ canvas declares `data-reader-fit="intrinsic-height"` and
184
+ `data-reader-min-text="7.5"`, which is the Reader's permission to narrow a tall
185
+ schema down to the point where the page fits — never below that declared minimum,
186
+ so the field rows stay readable and anything taller than that scrolls; an
187
+ authored `meta.viewBox` keeps the authored fit contract. Field rows, their types,
188
+ and the key glyph carry `data-detail="context"` on the text itself, because the
189
+ Reader sizes the diagram from the texts that declare a level — a field that is
190
+ only context inside a row group would be shrunk past the readable floor on a tall
191
+ schema.
192
+
193
+ Narrowing is a floor, not a goal. The bundled eleven-table example measures, at
194
+ 1440x900 and with the same tables and typography, as a 1230x678 canvas the Reader
195
+ renders at the full 1346px (scale 1.09), so its field types land at 11.5px. The
196
+ same tables grouped as four horizontal domains instead form a 910x874 canvas that
197
+ the Reader narrows to 777px (scale 0.85), landing the same types at 9.0px. A
198
+ horizontal arrangement that reaches the reader width instead of being narrowed
199
+ still trades text size for the columns it needs, which is why the measurement
200
+ here is the pair and not a rule about which axis is right.
201
+
202
+ ## Typography
203
+
204
+ Table rows are text a reader scans, so the ERD's rhythm is looser than the generic
205
+ diagram rhythm: a 30-unit header, 20-unit rows, a 13-unit table name, 11-unit
206
+ field names, and a 10.5-unit type. The type keeps the same ink as the field name
207
+ because it is data, not an aside: at the muted token it measured 3.98:1 against a
208
+ table fill, under the 4.5:1 text floor, which is why it read as faint in review.
209
+ A row whose text cannot fit is a diagnostic rather than a silently truncated
210
+ cell, so a narrow table names the field it cannot show and asks for a wider box.
211
+
212
+ Field rows, their types, and the key glyph each carry `data-detail="context"` on
213
+ the text itself, not only on the row group: the Viewer's reader sizes the diagram
214
+ from the texts that declare a level, so a tall schema would otherwise be shrunk
215
+ past the readable floor. An automatic canvas also declares
216
+ `data-reader-fit="intrinsic-height"`, which is what lets a schema taller than one
217
+ screen scroll at a readable size instead of being squeezed to fit; an authored
218
+ `meta.viewBox` keeps the authored fit contract.
219
+
220
+ Relationship labels are set at 9 units in the relationship's own accent, with the
221
+ mask measured from the same factor, so a labelled schema keeps its semantic text
222
+ above the readable floor. They are the smallest text the diagram carries, and
223
+ they are semantic data: a collision is a repair (move the label, widen the
224
+ corridor), never a reason to drop the wording.
225
+
226
+ ## Reading a schema
227
+
228
+ An ERD is also a table catalogue. For a schema with more than 8 tables or 50
229
+ fields, start with `meta.quality_profile: "standard"` so complete field
230
+ visibility remains the priority; promote to `showcase` only after the grouped
231
+ layout is genuinely readable. Include every physical column in
232
+ `entities[].attributes`, keep its SQL type, and preserve the source comment in the
233
+ type string as `SQL_TYPE|中文备注` when the schema has no separate comment field.
234
+ The renderer shows ERD attribute rows at the default `read` level, so fields do
235
+ not depend on hover or focus. Use cards only for supplementary constraints,
236
+ inferred relationships, and domain rules; never use them to hide the full table.
237
+
238
+ Group tables by functional domain before assigning grid cells. Give members of one
239
+ domain the same concise `tag` and keep them in one contiguous, non-interleaved run
240
+ — a row with adjacent columns or a column with adjacent rows; that run is what the
241
+ band is measured from. Give each domain a column of its own when the whole schema
242
+ has to be visible at once: the domain then reads top to bottom, the relationships
243
+ between neighbouring domains run left to right, and the canvas stays wide enough
244
+ for the Reader to show it at full text size. A wide entity is a taller wall for
245
+ the router to get around, and the projected text minimum still applies at
246
+ 1440x900, so prefer a compact grouped grid over shrinking or hiding fields.
@@ -0,0 +1,150 @@
1
+ /** Banded grid placement for the ER IR. Not auto-layout: column widths and row
2
+ * band heights are measured from the boxes the author declares, so the same
3
+ * spec always produces the same coordinates. */
4
+
5
+ export const DEFAULT_ER_GRID = {
6
+ mode: 'grid',
7
+ // The top origin leaves room for a domain caption above the first band, and
8
+ // the left origin starts the first column inside the page margin so the
9
+ // canvas is not edge-to-edge. Row bands are taller than the generic rhythm
10
+ // because a table row is text a reader has to scan, not a node label.
11
+ origin: [32, 40],
12
+ gapX: 56,
13
+ gapY: 44,
14
+ // Wide enough for a long field name plus its SQL type at the legibility
15
+ // sizes below (a 20-character name and a numeric(12,2) type).
16
+ entityW: 240,
17
+ // Header and row bands are sized for the field type legibility the reader
18
+ // asked for: an 11-unit field name and a 10.5-unit type sit on a 20-unit
19
+ // row, and the table name leads them on a 30-unit header.
20
+ headerH: 30,
21
+ rowH: 20,
22
+ };
23
+
24
+ export function erGridLayout(er) {
25
+ const raw = er.layout;
26
+ // Grid cells without a layout block mean the default grid, not absolute
27
+ // coordinates that were never supplied.
28
+ if (!raw) {
29
+ const gridPlaced = (er.entities || []).some((entity) => Number.isFinite(entity?.row) && Number.isFinite(entity?.col));
30
+ return gridPlaced ? { ...DEFAULT_ER_GRID } : null;
31
+ }
32
+ if (raw.mode !== 'grid') return null;
33
+ return { ...DEFAULT_ER_GRID, ...raw };
34
+ }
35
+
36
+ // An entity may omit `width` and inherit the grid's default. Both the layout and
37
+ // the placement validator must resolve that the same way: reading `entity.width`
38
+ // directly turned the documented default into NaN and reported a placement
39
+ // failure for a box the schema accepts.
40
+ export function resolvedEntityWidth(entity, grid) {
41
+ return Number.isFinite(entity?.width) ? entity.width : (grid?.entityW ?? DEFAULT_ER_GRID.entityW);
42
+ }
43
+
44
+ // One header band plus one band per declared column. An entity with no columns
45
+ // still needs a readable body, so the minimum is a single row.
46
+ export function entityHeight(entity, grid) {
47
+ const rows = Math.max(1, Array.isArray(entity.attributes) ? entity.attributes.length : 0);
48
+ return grid.headerH + rows * grid.rowH + 6;
49
+ }
50
+
51
+ // Every raw column index owns the widest box placed in it and every raw row
52
+ // index owns the tallest, so boxes in one band share a baseline without anyone
53
+ // measuring by hand. Indices with no box still contribute their gap, which is
54
+ // how an author asks for a routing channel between two bands. A box on a half
55
+ // column (a class grid's `col: 1.5`) is centred between two columns: it sizes
56
+ // its row but neither column.
57
+ const gridColumn = (col) => Number.isInteger(col * 2) && col >= 0;
58
+ // `columnGaps` raises the gap after a column to a measured floor (see the
59
+ // renderer's relationship-label demand); the authored or default gapX stays
60
+ // the minimum everywhere, so an absent map reproduces the plain grid.
61
+ export function bandedLayout(entities, grid, { columnGaps = null } = {}) {
62
+ const widths = new Map();
63
+ const heights = new Map();
64
+ let maxCol = -1;
65
+ for (const entity of entities) {
66
+ if (!Number.isInteger(entity.row) || !gridColumn(entity.col)) continue;
67
+ maxCol = Math.max(maxCol, Math.ceil(entity.col));
68
+ if (Number.isInteger(entity.col)) widths.set(entity.col, Math.max(widths.get(entity.col) || 0, resolvedEntityWidth(entity, grid)));
69
+ heights.set(entity.row, Math.max(heights.get(entity.row) || 0, entity.height));
70
+ }
71
+
72
+ const columnX = new Map();
73
+ const rowY = new Map();
74
+ let x = grid.origin[0];
75
+ for (let col = 0; col <= maxCol; col += 1) {
76
+ columnX.set(col, x);
77
+ x += (widths.get(col) || 0) + Math.max(grid.gapX, columnGaps?.get(col) || 0);
78
+ }
79
+ // A half-column box may be wider than its empty neighbouring bands. Keep
80
+ // every grid box inside the left margin without changing integer-only grids.
81
+ let leftInset = 0;
82
+ for (const entity of entities) {
83
+ if (!Number.isInteger(entity.row) || !gridColumn(entity.col) || Number.isInteger(entity.col)) continue;
84
+ const centre = (col) => columnX.get(col) + (widths.get(col) || 0) / 2;
85
+ const midpoint = (centre(Math.floor(entity.col)) + centre(Math.ceil(entity.col))) / 2;
86
+ leftInset = Math.max(leftInset, grid.origin[0] - midpoint + resolvedEntityWidth(entity, grid) / 2);
87
+ }
88
+ if (leftInset > 0) {
89
+ for (const [col, column] of columnX) columnX.set(col, column + leftInset);
90
+ x += leftInset;
91
+ }
92
+ let y = grid.origin[1];
93
+ const maxRow = Math.max(-1, ...heights.keys());
94
+ for (let row = 0; row <= maxRow; row += 1) {
95
+ rowY.set(row, y);
96
+ y += (heights.get(row) || 0) + grid.gapY;
97
+ }
98
+
99
+ return { columnX, rowY, widths, heights, totalWidth: x, totalHeight: y };
100
+ }
101
+
102
+ export function resolveEntityPos(entity, grid, bands) {
103
+ if (Array.isArray(entity.pos) && entity.pos.length === 2) return entity.pos;
104
+ if (!grid || !bands) return [NaN, NaN];
105
+ if (!Number.isInteger(entity.row) || !gridColumn(entity.col)) return [NaN, NaN];
106
+ const width = resolvedEntityWidth(entity, grid);
107
+ const rowY = bands.rowY.get(entity.row);
108
+ if (!Number.isInteger(entity.col)) {
109
+ const centre = (col) => bands.columnX.get(col) + (bands.widths.get(col) || 0) / 2;
110
+ const [left, right] = [Math.floor(entity.col), Math.ceil(entity.col)];
111
+ if (![bands.columnX.get(left), bands.columnX.get(right), rowY].every(Number.isFinite)) return [NaN, NaN];
112
+ return [(centre(left) + centre(right)) / 2 - width / 2, rowY];
113
+ }
114
+ const columnX = bands.columnX.get(entity.col);
115
+ if (!Number.isFinite(columnX) || !Number.isFinite(rowY)) return [NaN, NaN];
116
+ // Centre each box in its column so the gaps on both sides stay equal; the
117
+ // router reads those gaps as corridors.
118
+ const columnWidth = bands.widths.get(entity.col) || width;
119
+ return [columnX + (columnWidth - width) / 2, rowY];
120
+ }
121
+
122
+ export function validateErGridPlacement(er, grid, bands, problems) {
123
+ const seen = new Map();
124
+ for (const entity of er.entities ?? []) {
125
+ const hasPos = Array.isArray(entity.pos) && entity.pos.length === 2;
126
+ if (!hasPos && !grid) {
127
+ problems.push(`Entity "${entity.id}" needs pos [x,y] or a grid layout with row/col.`);
128
+ continue;
129
+ }
130
+ if (hasPos) continue;
131
+ if (!Number.isInteger(entity.row) || !Number.isInteger(entity.col)) {
132
+ problems.push(`Entity "${entity.id}" needs pos [x,y] or grid row/col when layout.mode is "grid".`);
133
+ continue;
134
+ }
135
+ if (entity.row < 0 || entity.col < 0) {
136
+ problems.push(`Entity "${entity.id}" row/col must be non-negative integers.`);
137
+ continue;
138
+ }
139
+ const key = `${entity.row},${entity.col}`;
140
+ if (seen.has(key)) {
141
+ problems.push(`Entities "${seen.get(key)}" and "${entity.id}" share grid cell row ${entity.row} col ${entity.col}.`);
142
+ } else {
143
+ seen.set(key, entity.id);
144
+ }
145
+ const [x, y] = resolveEntityPos(entity, grid, bands);
146
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
147
+ problems.push(`Entity "${entity.id}" could not be placed from row ${entity.row} col ${entity.col}.`);
148
+ }
149
+ }
150
+ }