@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,413 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs';
4
+ import {
5
+ animateAttr,
6
+ focusEdgeAttrs,
7
+ focusNodeAttrs,
8
+ focusNodeTitle,
9
+ loadDiagram,
10
+ svgAccessibleText,
11
+ svgRootAttrs,
12
+ writeDiagram,
13
+ } from '../shared/cli.mjs';
14
+ import { throwDiagnosticProblems } from '../shared/diagnostics.mjs';
15
+ import { legendFootprint, resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
16
+ import { nodeTextFit } from '../shared/text-fit.mjs';
17
+ import { translateMessage as i18nText } from '../shared/i18n.mjs';
18
+ import { asArray } from '../shared/geometry.mjs';
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+ const layoutJsonMode = process.argv.includes('--layout-json');
22
+ const cliArgs = process.argv.filter((arg) => arg !== '--layout-json');
23
+ const { diagram: wf, template, outPath, sourceEvidence } = loadDiagram({
24
+ rendererDir: __dirname,
25
+ diagramType: 'waterfall',
26
+ defaultExample: 'checkout-request.waterfall.json',
27
+ argv: cliArgs,
28
+ });
29
+ const locale = wf.meta.locale;
30
+ const unit = wf.meta.unit || 'ms';
31
+ // The schema spells microseconds `us` for ASCII input; the canvas uses µs.
32
+ const unitLabel = unit === 'us' ? 'µs' : unit;
33
+ const ADVANCE = nodeTextFit.widthFactor;
34
+
35
+ const layout = {
36
+ margin: 28,
37
+ top: 52,
38
+ axisH: 36,
39
+ rowH: 25,
40
+ barH: 14,
41
+ indent: 18,
42
+ nameFont: 12.5,
43
+ durationFont: 11,
44
+ axisWidth: wf.layout?.width ?? 760,
45
+ columnGap: 28,
46
+ minTickPx: 80,
47
+ };
48
+
49
+ // ---- Timing checks -------------------------------------------------------------
50
+ // Bars are drawn from recorded timing only. A span states `end` or `duration`
51
+ // (not both unless they agree); only `status: "incomplete"` may state neither,
52
+ // and it is drawn as an open bar up to the last recorded instant instead of
53
+ // with an invented end. Parents must exist and must not form a cycle.
54
+ const problems = [];
55
+ const details = [];
56
+ function fail(code, message, subject, evidence, supportedFixes) {
57
+ problems.push(message);
58
+ details.push({ code, severity: 'error', message, subject: { diagramType: 'waterfall', ...subject }, evidence, supportedFixes });
59
+ }
60
+ const raw = asArray(wf.spans);
61
+ const byId = new Map();
62
+ for (const [index, span] of raw.entries()) {
63
+ if (byId.has(span.id)) fail('waterfall/duplicate-id', `Span id "${span.id}" is declared twice.`, { path: `/spans/${index}/id` }, { id: span.id }, ['Give every span a unique id.']);
64
+ byId.set(span.id, span);
65
+ const hasEnd = Number.isFinite(span.end);
66
+ const hasDuration = Number.isFinite(span.duration);
67
+ const timing = (message, fixes) => fail('waterfall/invalid-timing', `Span "${span.id}" ${message}`, { path: `/spans/${index}`, nodeId: span.id },
68
+ { start: span.start, end: span.end ?? null, duration: span.duration ?? null, status: span.status ?? 'ok' }, fixes);
69
+ if (hasDuration && !Number.isFinite(span.start + span.duration)) {
70
+ timing('has a derived end outside the finite numeric range.', ['Rescale the recorded timing and unit to a representable range.']);
71
+ }
72
+ if (hasEnd && span.end < span.start) timing(`ends (${span.end}) before it starts (${span.start}).`, ['Correct start or end from the recorded timing.']);
73
+ if (hasEnd && hasDuration && Math.abs(span.end - span.start - span.duration) > 1e-9) {
74
+ timing(`states end ${span.end} and duration ${span.duration}, which disagree with start ${span.start}.`, ['Keep only one of end or duration.']);
75
+ }
76
+ if (!hasEnd && !hasDuration && span.status !== 'incomplete') {
77
+ timing('has neither end nor duration. A span without a recorded end must be marked status "incomplete".', ['Add the recorded end or duration.', 'Set status to "incomplete" if the trace has no end for it.']);
78
+ }
79
+ }
80
+ for (const [index, span] of raw.entries()) {
81
+ if (span.parent !== undefined && !byId.has(span.parent)) {
82
+ fail('waterfall/missing-parent', `Span "${span.id}" names parent "${span.parent}", which is not declared.`,
83
+ { path: `/spans/${index}/parent`, nodeId: span.id }, { parent: span.parent }, ['Declare the parent span.', 'Remove parent to make this a root span.']);
84
+ }
85
+ }
86
+ {
87
+ const reported = new Set();
88
+ for (const [index, span] of raw.entries()) {
89
+ const chain = [];
90
+ let current = span;
91
+ while (current && current.parent !== undefined && !chain.includes(current.id)) {
92
+ chain.push(current.id);
93
+ current = byId.get(current.parent);
94
+ }
95
+ if (current && chain.includes(current.id)) {
96
+ const cycle = chain.slice(chain.indexOf(current.id));
97
+ const key = [...cycle].sort().join('|');
98
+ if (reported.has(key)) continue;
99
+ reported.add(key);
100
+ fail('waterfall/cycle', `Parent links form a cycle: ${[...cycle, cycle[0]].map((id) => `"${id}"`).join(' -> ')}.`,
101
+ { path: `/spans/${index}/parent`, nodeId: span.id }, { cycle }, ['Point one span in the cycle at a parent outside it.']);
102
+ }
103
+ }
104
+ }
105
+ if (problems.length) throwDiagnosticProblems('Waterfall validation failed', problems, { code: 'waterfall/input', subject: { diagramType: 'waterfall' }, diagnostics: details });
106
+
107
+ // ---- Timing model --------------------------------------------------------------
108
+ const recordedEnd = (span) => (Number.isFinite(span.end) ? span.end : Number.isFinite(span.duration) ? span.start + span.duration : null);
109
+ const t0 = Math.min(...raw.map((span) => span.start));
110
+ const t1 = Math.max(...raw.map((span) => recordedEnd(span) ?? span.start));
111
+ const wall = t1 - t0;
112
+
113
+ const spans = raw.map((span, index) => {
114
+ const end = recordedEnd(span);
115
+ const open = end === null;
116
+ // An incomplete span with no end is drawn to the last recorded instant of
117
+ // the trace and labelled as a lower bound, never as a measured duration.
118
+ return { ...span, index, status: span.status || 'ok', open, end: open ? t1 : end, duration: (open ? t1 : end) - span.start };
119
+ });
120
+ const children = new Map(spans.map((span) => [span.id, []]));
121
+ for (const span of spans) if (span.parent !== undefined) children.get(span.parent).push(span);
122
+ const byStart = (a, b) => a.start - b.start || a.index - b.index;
123
+ const rows = [];
124
+ (function visit(list, depth) {
125
+ for (const span of [...list].sort(byStart)) {
126
+ rows.push({ ...span, depth });
127
+ visit(children.get(span.id), depth + 1);
128
+ }
129
+ }(spans.filter((span) => span.parent === undefined), 0));
130
+
131
+ function number(value) {
132
+ if (value !== 0 && Math.abs(value) < 0.01) {
133
+ return Math.abs(value) < 1e-6 ? Number(value.toPrecision(3)).toString()
134
+ : value.toLocaleString('en-US', { maximumSignificantDigits: 3 });
135
+ }
136
+ const rounded = Math.abs(value) > Number.MAX_VALUE / 100 ? value : Math.round(value * 100) / 100;
137
+ return rounded.toLocaleString('en-US', { maximumFractionDigits: 2 });
138
+ }
139
+ const formatDuration = (value) => `${number(value)} ${unitLabel}`;
140
+ function percent(value) {
141
+ if (!wall) return '100%';
142
+ const share = (value / wall) * 100;
143
+ return `${share >= 10 ? Math.round(share) : Math.round(share * 10) / 10}%`;
144
+ }
145
+
146
+ // ---- Tones ---------------------------------------------------------------------
147
+ // Colour states who did the work: each service gets its own palette family in
148
+ // order of first appearance, and a span without one inherits its parent's.
149
+ // A span no service owns stays neutral. Red is never a service colour: it is
150
+ // reserved for status "error", so a failure is never mistaken for a team.
151
+ const SERVICE_TONES = ['frontend', 'backend', 'database', 'cloud', 'messagebus'];
152
+ const services = [...new Set(rows.map((row) => row.service).filter(Boolean))];
153
+ const serviceOf = new Map();
154
+ for (const row of rows) serviceOf.set(row.id, row.service ?? (row.parent !== undefined ? serviceOf.get(row.parent) : undefined));
155
+ const serviceTone = (service) => (service === undefined ? 'external' : SERVICE_TONES[services.indexOf(service) % SERVICE_TONES.length]);
156
+ const STATUS_TONE = { error: 'security', cancelled: 'external' };
157
+ const toneOf = (row) => STATUS_TONE[row.status] ?? serviceTone(serviceOf.get(row.id));
158
+
159
+ // ---- Geometry ------------------------------------------------------------------
160
+ // Name column: tree indent, then a service dot, then the name.
161
+ const NAME_DOT = 14;
162
+ const nameWidth = (row) => row.depth * layout.indent + NAME_DOT + textUnits(row.name) * layout.nameFont * ADVANCE * 1.04;
163
+ const labelW = Math.ceil(Math.max(140, ...rows.map(nameWidth)));
164
+ const axisX0 = layout.margin + labelW + layout.columnGap;
165
+ const axisX1 = axisX0 + layout.axisWidth;
166
+ const scale = wall > 0 ? layout.axisWidth / wall : 0;
167
+ if (!Number.isFinite(scale)) {
168
+ fail('waterfall/invalid-timing', 'The recorded range cannot form a finite axis scale.',
169
+ { path: '/spans' }, { start: t0, end: t1, wall }, ['Rescale the recorded timing and unit to a representable range.']);
170
+ throwDiagnosticProblems('Waterfall validation failed', problems, { diagnostics: details });
171
+ }
172
+ const xOf = (t) => Math.round((axisX0 + (t - t0) * scale) * 100) / 100;
173
+ const rowsTop = layout.top + layout.axisH;
174
+
175
+ const NICE = [1, 2, 2.5, 5];
176
+ const tickStep = (() => {
177
+ if (!wall) return 1;
178
+ const target = (layout.minTickPx / layout.axisWidth) * wall;
179
+ let magnitude = 10 ** Math.floor(Math.log10(target));
180
+ for (;;) {
181
+ for (const factor of NICE) if (factor * magnitude >= target) return factor * magnitude;
182
+ magnitude *= 10;
183
+ }
184
+ })();
185
+ const ticks = [];
186
+ const firstTick = Math.ceil(t0 / tickStep) * tickStep;
187
+ const tickCount = Math.max(0, Math.floor((t1 - firstTick) / tickStep + 1e-9) + 1);
188
+ for (let index = 0; index < tickCount; index += 1) {
189
+ const t = firstTick + index * tickStep;
190
+ // Large timestamps can round adjacent ticks to the same double. Index-based
191
+ // iteration remains bounded even when adding tickStep cannot advance t.
192
+ if (ticks.at(-1)?.t !== t) ticks.push({ t, x: xOf(t) });
193
+ }
194
+
195
+ // Absolute times need enough precision for the axis and the shortest recorded
196
+ // span, rather than the duration formatter's fixed significant-digit floor.
197
+ const timeResolution = Math.min(tickStep, ...spans.filter(row => row.duration > 0).map(row => row.duration));
198
+ function timeNumber(value) {
199
+ const ordinary = number(value);
200
+ if (Math.abs(value) < 1e21 && Math.abs(Number(ordinary.replaceAll(',', '')) - value) <= timeResolution / 100) return ordinary;
201
+ const digits = value === 0 ? 1 : Math.min(17, Math.max(1, Math.floor(Math.log10(Math.abs(value))) - Math.floor(Math.log10(timeResolution)) + 2));
202
+ const rounded = Number(value.toPrecision(digits));
203
+ return Number.isFinite(rounded) ? rounded.toString() : value.toString();
204
+ }
205
+
206
+ const LABEL_GAP = 7;
207
+ const parentIds = new Set(rows.map((row) => row.parent).filter((id) => id !== undefined));
208
+ const placed = rows.map((row, index) => {
209
+ const y = rowsTop + index * layout.rowH;
210
+ const x0 = xOf(row.start);
211
+ const x1 = xOf(row.end);
212
+ const width = Math.max(2, x1 - x0);
213
+ const label = row.open ? i18nText(locale, 'waterfall.incomplete', { duration: formatDuration(row.duration) }) : formatDuration(row.duration);
214
+ const labelWidth = textUnits(label) * layout.durationFont * ADVANCE + 4;
215
+ // Every duration reads at the same place, just after its bar; the canvas
216
+ // reserves room past the axis for the widest one, so a short operation and
217
+ // a bar that ends at the wall-clock edge are labelled the same way.
218
+ return { ...row, y, x0, x1, width, label, labelWidth, placement: 'after', tone: toneOf(row), parentBar: parentIds.has(row.id) };
219
+ });
220
+
221
+ // ---- Legend & canvas -----------------------------------------------------------
222
+ // The legend names the services (when more than one does work) and any status
223
+ // that changes a bar's paint; a single-service trace with only ok spans needs
224
+ // no key.
225
+ const presentStatuses = new Set(placed.map((row) => row.status));
226
+ // Past five services the palette repeats. Services that share a colour share
227
+ // one entry, so the key never claims a colour belongs to a single service.
228
+ const serviceLegendEntries = [...services.reduce((byTone, service) => {
229
+ const tone = serviceTone(service);
230
+ byTone.set(tone, [...(byTone.get(tone) || []), service]);
231
+ return byTone;
232
+ }, new Map())].map(([tone, owners]) => ({ kind: `service:${owners[0]}`, label: owners.join(' · '), tone, interactive: false }));
233
+ const legendEntries = [
234
+ ...(services.length > 1 ? serviceLegendEntries : []),
235
+ ...resolveLegend(undefined, ['error', 'cancelled', 'incomplete'].map((kind) => ({ kind, label: i18nText(locale, `legend.waterfall.${kind}`), tone: STATUS_TONE[kind] ?? 'external', interactive: false })), presentStatuses),
236
+ ];
237
+ const contentBottom = rowsTop + placed.length * layout.rowH + 8;
238
+ const labelRoom = Math.ceil(Math.max(...placed.map((row) => row.x1 + LABEL_GAP + row.labelWidth)) - axisX1);
239
+ const width = Math.ceil(axisX1 + Math.max(24, labelRoom) + layout.margin);
240
+ const axisLabels = ticks.map(tick => {
241
+ const text = `${timeNumber(tick.t)} ${unitLabel}`;
242
+ const halfWidth = (textUnits(text) * 10.5 * ADVANCE + 4) / 2;
243
+ const x = Math.max(layout.margin + halfWidth, Math.min(tick.x, width - layout.margin - halfWidth));
244
+ return { text, x, left: x - halfWidth, right: x + halfWidth };
245
+ });
246
+ // Keep the endpoints readable; omit intermediate labels that cannot fit. The
247
+ // ticks and the proportional time geometry remain unchanged.
248
+ if (axisLabels.length > 1) {
249
+ const first = axisLabels[0];
250
+ const last = axisLabels.at(-1);
251
+ const overlap = Math.max(0, first.right + 8 - last.left);
252
+ const leftShift = Math.min(overlap, first.left - layout.margin);
253
+ first.x -= leftShift;
254
+ first.left -= leftShift;
255
+ first.right -= leftShift;
256
+ const rightShift = Math.min(overlap - leftShift, width - layout.margin - last.right);
257
+ last.x += rightShift;
258
+ last.left += rightShift;
259
+ last.right += rightShift;
260
+ }
261
+ let previousLabelRight = -Infinity;
262
+ for (const [index, label] of axisLabels.entries()) {
263
+ const last = axisLabels.at(-1);
264
+ if (label.left < previousLabelRight + 8 || (index > 0 && index < axisLabels.length - 1 && label.right + 8 > last.left)) continue;
265
+ label.visible = true;
266
+ previousLabelRight = label.right;
267
+ }
268
+ const footprint = legendFootprint(legendEntries, { width: width - layout.margin * 2, fontSize: 11 });
269
+ const height = Math.ceil(contentBottom + layout.margin + (legendEntries.length ? 34 + footprint.extraHeight : 0));
270
+ const viewBox = [width, height];
271
+
272
+ // ---- Rendering -----------------------------------------------------------------
273
+ function renderHeader() {
274
+ const evidence = i18nText(locale, `waterfall.evidence.${wf.meta.evidence}`);
275
+ const evidenceW = Math.ceil(textUnits(evidence) * 10.5 * ADVANCE + 24);
276
+ const tone = wf.meta.evidence === 'measured' ? 'backend' : 'messagebus';
277
+ const wallText = i18nText(locale, 'waterfall.wall', { duration: formatDuration(wall) });
278
+ const range = `${timeNumber(t0)} → ${timeNumber(t1)} ${unitLabel}`;
279
+ const wallX = layout.margin + evidenceW + 14;
280
+ return ` <g data-waterfall-evidence="${esc(wf.meta.evidence)}">
281
+ <rect x="${layout.margin}" y="14" width="${evidenceW}" height="22" rx="11" class="c-${tone}" stroke-width="1"/>
282
+ <circle cx="${layout.margin + 11}" cy="25" r="3" class="t-${tone}"/>
283
+ <text x="${layout.margin + evidenceW / 2 + 5}" y="29" class="t-${tone}" font-size="10.5" font-weight="700" text-anchor="middle">${esc(evidence)}</text>
284
+ </g>
285
+ <text data-waterfall-total="${wall}" x="${wallX}" y="29.5" class="t-primary wf-num" font-size="13" font-weight="700">${esc(wallText)}<tspan class="t-muted" font-weight="500" dx="10">${esc(range)}</tspan></text>`;
286
+ }
287
+
288
+ function renderAxis() {
289
+ const lineY = rowsTop - 6;
290
+ return [
291
+ ` <text x="${layout.margin}" y="${lineY - 10}" class="t-muted wf-caps" font-size="10" font-weight="700">${esc(i18nText(locale, 'waterfall.operation'))}</text>`,
292
+ ` <line x1="${axisX0}" y1="${lineY}" x2="${axisX1}" y2="${lineY}" class="wf-axis" stroke-width="1"/>`,
293
+ ...ticks.map((tick, index) => ` <line x1="${tick.x}" y1="${lineY}" x2="${tick.x}" y2="${contentBottom}" class="wf-grid${index ? '' : ' wf-grid-origin'}" stroke-width="1"/>
294
+ <line x1="${tick.x}" y1="${lineY - 4}" x2="${tick.x}" y2="${lineY}" class="wf-axis" stroke-width="1"/>
295
+ ${axisLabels[index].visible ? `<text x="${axisLabels[index].x}" y="${lineY - 10}" class="t-muted wf-num" font-size="10.5" text-anchor="middle">${esc(axisLabels[index].text)}</text>` : ''}`),
296
+ ].join('\n');
297
+ }
298
+
299
+ // Tree guides in the name column state the parent/child structure; the bars
300
+ // themselves only ever state time.
301
+ const dotX = (row) => layout.margin + row.depth * layout.indent + 4;
302
+
303
+ function renderGuides() {
304
+ const rowIndex = new Map(placed.map((row, index) => [row.id, index]));
305
+ return placed.filter((row) => row.parent !== undefined).map((row) => {
306
+ const parent = placed[rowIndex.get(row.parent)];
307
+ const parentY = parent.y + layout.rowH / 2 + 6;
308
+ const y = row.y + layout.rowH / 2;
309
+ const x = dotX(parent);
310
+ return ` <path ${focusEdgeAttrs(row.parent, row.id, undefined, row.index)} d="M ${x} ${parentY} V ${y - 4} Q ${x} ${y} ${x + 4} ${y} H ${dotX(row) - 7}" class="wf-guide" stroke-width="1" fill="none"/>`;
311
+ }).join('\n');
312
+ }
313
+
314
+ // A span with children is drawn as a tinted, outlined bracket over its time;
315
+ // a span that does the work itself is a solid bar. Parent and child are never
316
+ // added together, and the paint says which bars are inclusive summaries.
317
+ function renderRow(row, order) {
318
+ const parent = row.parent !== undefined ? placed.find((candidate) => candidate.id === row.parent) : null;
319
+ const timing = `${timeNumber(row.start)}–${row.open ? '…' : timeNumber(row.end)} ${unitLabel} · ${row.label} · ${i18nText(locale, 'waterfall.share', { percent: percent(row.duration), total: formatDuration(wall) })}`;
320
+ const service = serviceOf.get(row.id);
321
+ const passport = { kind: row.tone, sublabel: [timing, row.detail].filter(Boolean).join(' — '), context: [service, parent?.name].filter(Boolean).join(' \u203a ') || undefined };
322
+ const midY = row.y + layout.rowH / 2;
323
+ const barY = midY - layout.barH / 2;
324
+ const nameX = dotX(row) + 10;
325
+ const paint = row.open ? 'wf-open' : row.parentBar ? 'wf-span' : 'wf-solid';
326
+ const openTail = row.open ? `<path d="M ${row.x1 - 8} ${barY + 2} L ${row.x1 - 2} ${midY} L ${row.x1 - 8} ${barY + layout.barH - 2}" class="wf-open-edge t-${row.tone}" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>` : '';
327
+ return ` <g ${focusNodeAttrs(row.id, row.name, passport, locale)} data-waterfall-start="${row.start}" data-waterfall-end="${row.open ? '' : row.end}" data-waterfall-duration="${row.duration}" data-waterfall-depth="${row.depth}" data-waterfall-status="${row.status}"${service ? ` data-waterfall-service="${esc(service)}"` : ''}>
328
+ ${focusNodeTitle(row.name, passport)}
329
+ <rect x="${layout.margin - 8}" y="${row.y + 1}" width="${width - layout.margin * 2 + 16}" height="${layout.rowH - 2}" rx="6" class="c-mask wf-row${order % 2 ? ' wf-row-alt' : ''}"/>
330
+ <rect x="${row.x0}" y="${barY}" width="${row.width}" height="${layout.barH}" rx="${Math.min(4, row.width / 2)}" class="c-${row.tone} ${paint}"${animateAttr(wf.meta, 'node', order)} stroke-width="1.2"/>
331
+ ${openTail}
332
+ <circle cx="${dotX(row)}" cy="${midY}" r="${row.depth ? 3.5 : 4.5}" class="t-${row.tone} wf-dot"/>
333
+ <text data-node-label="" x="${nameX}" y="${midY + 4.3}" class="t-primary" font-size="${layout.nameFont}" font-weight="${row.depth ? (row.parentBar ? 650 : 500) : 750}">${esc(row.name)}</text>
334
+ <text x="${row.x1 + LABEL_GAP}" y="${midY + 3.8}" class="${row.parentBar ? 't-primary' : 't-muted'} wf-num" font-size="${layout.durationFont}" font-weight="${row.parentBar ? 700 : 600}">${esc(row.label)}</text>
335
+ </g>`;
336
+ }
337
+
338
+ function legendSwatch(entry) {
339
+ const paint = entry.kind === 'incomplete' ? 'wf-open' : 'wf-solid';
340
+ return `<rect x="${entry.x}" y="${entry.baseline - 10}" width="14" height="10" rx="2.5" class="c-${entry.tone} ${paint}" stroke-width="1.2"/>`;
341
+ }
342
+
343
+ const SOLID_TONES = ['frontend', 'backend', 'database', 'cloud', 'messagebus', 'security', 'external'];
344
+
345
+ function renderSvg() {
346
+ return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(wf.meta)} data-motion-choreography="replay" data-waterfall-ui="" data-waterfall-unit="${esc(unit)}" data-reader-fit="width-first" data-reader-min-text="7.5">
347
+ ${svgAccessibleText(wf.meta, 'waterfall')}
348
+ ${renderDefinitions()}
349
+ <style>
350
+ svg[data-waterfall-ui] .wf-axis { stroke: var(--text-dim); }
351
+ svg[data-waterfall-ui] .wf-grid { stroke: var(--lane-stroke); stroke-dasharray: 2 4; opacity: .7; }
352
+ svg[data-waterfall-ui] .wf-grid-origin { stroke-dasharray: none; opacity: .9; }
353
+ svg[data-waterfall-ui] .wf-row { fill: transparent; }
354
+ svg[data-waterfall-ui] .wf-row-alt { fill: var(--lane-fill); fill-opacity: 1; opacity: .7; }
355
+ svg[data-waterfall-ui] .wf-guide { stroke: var(--lane-stroke); }
356
+ svg[data-waterfall-ui] .wf-span { fill-opacity: .9; }
357
+ svg[data-waterfall-ui] .wf-open { stroke-dasharray: 3 2.5; fill-opacity: .6; }
358
+ svg[data-waterfall-ui] .wf-open-edge { fill: none !important; }
359
+ svg[data-waterfall-ui] .wf-num { font-variant-numeric: tabular-nums; }
360
+ svg[data-waterfall-ui] .wf-caps { letter-spacing: .06em; }
361
+ svg[data-waterfall-ui] .wf-solid { stroke: none; }
362
+ /* Thin bars stay crisp: the classic preset's card shadow blurs their edges. */
363
+ svg[data-waterfall-ui] [data-node-id] > rect { filter: none !important; }
364
+ ${SOLID_TONES.map((tone) => ` svg[data-waterfall-ui] .wf-solid.c-${tone} { fill: var(--${tone}-stroke); }
365
+ svg[data-waterfall-ui] .wf-open-edge.t-${tone} { stroke: var(--${tone}-stroke); }`).join('\n')}
366
+ </style>
367
+
368
+ <!-- Background Grid -->
369
+ <rect width="100%" height="100%" fill="url(#grid)" />
370
+
371
+ ${renderHeader()}
372
+
373
+ <!-- Time axis (bar position and length come only from recorded timing) -->
374
+ ${renderAxis()}
375
+
376
+ <!-- Parent / child guides -->
377
+ ${renderGuides()}
378
+
379
+ <!-- Spans, depth-first by start time -->
380
+ ${placed.map(renderRow).join('\n')}
381
+
382
+ <!-- Legend -->
383
+ ${legendEntries.length ? renderResolvedLegend({ entries: legendEntries, locale, layout: { x: layout.margin, baselineY: height - 16, width: width - layout.margin * 2, fontSize: 11, minTitleY: contentBottom + 8, diagramType: 'waterfall' }, renderSwatch: legendSwatch }) : ''}
384
+ </svg>`;
385
+ }
386
+
387
+ function buildLayoutReport() {
388
+ return {
389
+ diagram_type: 'waterfall',
390
+ unit,
391
+ viewBox,
392
+ origin: t0,
393
+ wall,
394
+ pxPerUnit: scale,
395
+ axis: { x0: axisX0, x1: axisX1 },
396
+ ticks,
397
+ rows: placed.map((row) => ({ id: row.id, parent: row.parent ?? null, depth: row.depth, start: row.start, end: row.end, open: row.open, x0: row.x0, x1: row.x1, y: row.y, label: row.label, placement: row.placement })),
398
+ };
399
+ }
400
+
401
+ if (layoutJsonMode) {
402
+ console.log(JSON.stringify(buildLayoutReport(), null, 2));
403
+ process.exit(0);
404
+ }
405
+ writeDiagram({
406
+ outPath,
407
+ template,
408
+ diagramType: 'waterfall',
409
+ meta: wf.meta,
410
+ svg: renderSvg(),
411
+ cards: wf.cards,
412
+ sourceEvidence,
413
+ });