@transitrix/diagrams 1.0.0 → 1.0.1

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 (267) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +47 -47
  3. package/dist/__tests__/edge-path.test.js +8 -0
  4. package/dist/activity-card/__tests__/example.test.js +20 -1
  5. package/dist/activity-card/__tests__/layout.test.js +102 -11
  6. package/dist/activity-card/__tests__/resolver.test.js +139 -6
  7. package/dist/activity-card/index.d.ts +1 -1
  8. package/dist/activity-card/layout.d.ts +1 -1
  9. package/dist/activity-card/layout.js +220 -103
  10. package/dist/activity-card/resolver.js +107 -26
  11. package/dist/activity-card/types.d.ts +150 -27
  12. package/dist/activity-card/validate.js +3 -0
  13. package/dist/actor/__tests__/validate.test.js +63 -31
  14. package/dist/actor/index.d.ts +1 -1
  15. package/dist/actor/index.js +1 -1
  16. package/dist/actor/types.d.ts +7 -3
  17. package/dist/actor/types.js +17 -3
  18. package/dist/actor/validate.js +55 -26
  19. package/dist/assertion/types.d.ts +3 -1
  20. package/dist/assertion/types.js +1 -1
  21. package/dist/change/__tests__/validate.test.js +40 -34
  22. package/dist/change/index.d.ts +1 -0
  23. package/dist/change/types.d.ts +1 -1
  24. package/dist/change/types.js +2 -2
  25. package/dist/change/validate.d.ts +6 -1
  26. package/dist/change/validate.js +68 -41
  27. package/dist/compliance/__tests__/classify.test.js +34 -0
  28. package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
  29. package/dist/compliance/__tests__/html.test.js +2 -2
  30. package/dist/compliance/__tests__/impact.test.js +311 -6
  31. package/dist/compliance/classify.d.ts +6 -0
  32. package/dist/compliance/classify.js +9 -1
  33. package/dist/compliance/coverage-metric.d.ts +31 -11
  34. package/dist/compliance/coverage-metric.js +149 -39
  35. package/dist/compliance/html.js +77 -74
  36. package/dist/compliance/impact.d.ts +77 -16
  37. package/dist/compliance/impact.js +193 -25
  38. package/dist/compliance/index.d.ts +2 -2
  39. package/dist/compliance/index.js +1 -1
  40. package/dist/compliance/markdown.js +1 -0
  41. package/dist/compliance/types.d.ts +25 -6
  42. package/dist/edge-path.d.ts +8 -4
  43. package/dist/edge-path.js +11 -6
  44. package/dist/factor/__tests__/validate.test.js +74 -45
  45. package/dist/factor/index.d.ts +3 -2
  46. package/dist/factor/index.js +1 -1
  47. package/dist/factor/types.d.ts +4 -7
  48. package/dist/factor/types.js +10 -5
  49. package/dist/factor/validate.d.ts +6 -1
  50. package/dist/factor/validate.js +68 -50
  51. package/dist/fgca/__tests__/resolver.test.js +185 -0
  52. package/dist/fgca/index.d.ts +2 -0
  53. package/dist/fgca/index.js +1 -0
  54. package/dist/fgca/resolver.d.ts +49 -0
  55. package/dist/fgca/resolver.js +124 -0
  56. package/dist/goals/index.d.ts +1 -1
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.js +5 -0
  59. package/dist/process-blueprint/__tests__/layout.test.js +37 -0
  60. package/dist/process-blueprint/layout.js +5 -1
  61. package/dist/process-blueprint/types.d.ts +22 -1
  62. package/dist/stakeholder/__tests__/validate.test.js +49 -47
  63. package/dist/stakeholder/index.d.ts +1 -0
  64. package/dist/stakeholder/types.d.ts +3 -4
  65. package/dist/stakeholder/types.js +2 -3
  66. package/dist/stakeholder/validate.d.ts +6 -1
  67. package/dist/stakeholder/validate.js +71 -50
  68. package/dist/target-state/__tests__/validate.test.js +43 -27
  69. package/dist/target-state/index.d.ts +2 -0
  70. package/dist/target-state/index.js +1 -0
  71. package/dist/target-state/types.d.ts +6 -1
  72. package/dist/target-state/types.js +8 -7
  73. package/dist/target-state/validate.d.ts +6 -1
  74. package/dist/target-state/validate.js +69 -45
  75. package/dist/theme/themes.js +69 -69
  76. package/dist/webview/__tests__/entry.test.js +11 -11
  77. package/dist/webview/render-activities.d.ts +3 -1
  78. package/dist/webview/render-activities.js +2 -2
  79. package/dist/webview/render-activity-card.js +104 -77
  80. package/dist/webview/render-fgca.d.ts +4 -0
  81. package/dist/webview/render-fgca.js +2 -5
  82. package/dist/webview/render-goals.d.ts +1 -0
  83. package/dist/webview/render-goals.js +15 -15
  84. package/dist/webview/render-process-blueprint.js +10 -10
  85. package/package.json +40 -40
  86. package/src/__tests__/edge-path.test.ts +58 -46
  87. package/src/__tests__/scope.test.ts +27 -27
  88. package/src/__tests__/yaml-normalize.test.ts +158 -158
  89. package/src/activities/__tests__/cpm.test.ts +164 -164
  90. package/src/activities/__tests__/gantt.test.ts +245 -245
  91. package/src/activities/__tests__/layout.test.ts +112 -112
  92. package/src/activities/__tests__/validate.test.ts +409 -409
  93. package/src/activities/cpm.ts +95 -95
  94. package/src/activities/gantt.ts +276 -276
  95. package/src/activities/index.ts +28 -28
  96. package/src/activities/layout.ts +112 -112
  97. package/src/activities/types.ts +148 -148
  98. package/src/activities/validate.ts +357 -357
  99. package/src/activity-card/__tests__/example.test.ts +85 -66
  100. package/src/activity-card/__tests__/layout.test.ts +169 -63
  101. package/src/activity-card/__tests__/resolver.test.ts +307 -161
  102. package/src/activity-card/__tests__/validate.test.ts +131 -131
  103. package/src/activity-card/index.ts +38 -32
  104. package/src/activity-card/layout.ts +324 -194
  105. package/src/activity-card/resolver.ts +415 -337
  106. package/src/activity-card/types.ts +376 -237
  107. package/src/activity-card/validate.ts +162 -158
  108. package/src/actor/__tests__/validate.test.ts +70 -31
  109. package/src/actor/index.ts +1 -1
  110. package/src/actor/types.ts +21 -7
  111. package/src/actor/validate.ts +59 -27
  112. package/src/applications/__tests__/validate.test.ts +256 -256
  113. package/src/applications/index.ts +11 -11
  114. package/src/applications/types.ts +43 -43
  115. package/src/applications/validate.ts +131 -131
  116. package/src/assertion/__tests__/example.test.ts +37 -37
  117. package/src/assertion/__tests__/validate.test.ts +142 -142
  118. package/src/assertion/index.ts +12 -12
  119. package/src/assertion/types.ts +60 -58
  120. package/src/assertion/validate.ts +145 -145
  121. package/src/blocks/__tests__/layout.test.ts +169 -169
  122. package/src/blocks/__tests__/validate.test.ts +260 -260
  123. package/src/blocks/index.ts +17 -17
  124. package/src/blocks/layout.ts +186 -186
  125. package/src/blocks/types.ts +62 -62
  126. package/src/blocks/validate.ts +206 -206
  127. package/src/capability-map/__tests__/validate.test.ts +238 -238
  128. package/src/capability-map/index.ts +8 -8
  129. package/src/capability-map/types.ts +29 -29
  130. package/src/capability-map/validate.ts +127 -127
  131. package/src/change/__tests__/validate.test.ts +43 -34
  132. package/src/change/index.ts +1 -0
  133. package/src/change/types.ts +5 -3
  134. package/src/change/validate.ts +75 -43
  135. package/src/compliance/__tests__/classify.test.ts +64 -27
  136. package/src/compliance/__tests__/confidence.test.ts +98 -98
  137. package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
  138. package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
  139. package/src/compliance/__tests__/example.test.ts +57 -57
  140. package/src/compliance/__tests__/gap-report.test.ts +128 -128
  141. package/src/compliance/__tests__/html.test.ts +141 -141
  142. package/src/compliance/__tests__/impact.test.ts +840 -481
  143. package/src/compliance/__tests__/markdown.test.ts +94 -94
  144. package/src/compliance/__tests__/views.test.ts +64 -64
  145. package/src/compliance/classify.ts +100 -84
  146. package/src/compliance/confidence.ts +69 -69
  147. package/src/compliance/coverage-metric.ts +404 -263
  148. package/src/compliance/gap-report.ts +94 -94
  149. package/src/compliance/html.ts +298 -295
  150. package/src/compliance/impact.ts +861 -677
  151. package/src/compliance/index.ts +57 -56
  152. package/src/compliance/markdown.ts +136 -135
  153. package/src/compliance/reverse-index.ts +33 -33
  154. package/src/compliance/types.ts +136 -120
  155. package/src/compliance/views.ts +46 -46
  156. package/src/compliance-matrix/__tests__/build.test.ts +200 -200
  157. package/src/compliance-matrix/__tests__/example.test.ts +57 -57
  158. package/src/compliance-matrix/index.ts +11 -11
  159. package/src/compliance-matrix/types.ts +102 -102
  160. package/src/edge-path.ts +50 -44
  161. package/src/factor/__tests__/validate.test.ts +80 -45
  162. package/src/factor/index.ts +3 -7
  163. package/src/factor/types.ts +14 -18
  164. package/src/factor/validate.ts +77 -51
  165. package/src/fgca/__tests__/chain-table.test.ts +172 -172
  166. package/src/fgca/__tests__/layout.test.ts +179 -179
  167. package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
  168. package/src/fgca/__tests__/preview-layout.test.ts +124 -124
  169. package/src/fgca/__tests__/resolver.test.ts +209 -0
  170. package/src/fgca/__tests__/validate.test.ts +142 -142
  171. package/src/fgca/chain-table.ts +225 -225
  172. package/src/fgca/index.ts +47 -45
  173. package/src/fgca/layout.ts +204 -204
  174. package/src/fgca/nodeTypes.ts +11 -11
  175. package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
  176. package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
  177. package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
  178. package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
  179. package/src/fgca/parse-canonical.ts +400 -400
  180. package/src/fgca/preview-layout.ts +217 -217
  181. package/src/fgca/resolver.ts +157 -0
  182. package/src/fgca/types.ts +50 -50
  183. package/src/fgca/validate.ts +126 -126
  184. package/src/geometry.ts +9 -9
  185. package/src/goals/__tests__/layout.test.ts +175 -175
  186. package/src/goals/__tests__/mutations.test.ts +111 -111
  187. package/src/goals/__tests__/parse-canonical.test.ts +145 -145
  188. package/src/goals/__tests__/validate.test.ts +202 -202
  189. package/src/goals/index.ts +20 -21
  190. package/src/goals/layout.ts +172 -172
  191. package/src/goals/mutations.ts +123 -123
  192. package/src/goals/parse-canonical.ts +286 -286
  193. package/src/goals/types.ts +85 -85
  194. package/src/goals/validate.ts +136 -136
  195. package/src/index.ts +28 -23
  196. package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
  197. package/src/process-blueprint/__tests__/layout.test.ts +240 -195
  198. package/src/process-blueprint/__tests__/validate.test.ts +310 -310
  199. package/src/process-blueprint/index.ts +31 -31
  200. package/src/process-blueprint/layout.ts +535 -531
  201. package/src/process-blueprint/types.ts +263 -242
  202. package/src/process-blueprint/validate.ts +181 -181
  203. package/src/process-map/__tests__/validate.test.ts +223 -223
  204. package/src/process-map/index.ts +10 -10
  205. package/src/process-map/types.ts +36 -36
  206. package/src/process-map/validate.ts +135 -135
  207. package/src/products/__tests__/validate.test.ts +239 -239
  208. package/src/products/index.ts +9 -9
  209. package/src/products/types.ts +31 -31
  210. package/src/products/validate.ts +112 -112
  211. package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
  212. package/src/repo-validate/index.ts +2 -2
  213. package/src/repo-validate/types.ts +50 -50
  214. package/src/repo-validate/validate-repo.ts +196 -196
  215. package/src/requirement/__tests__/example.test.ts +24 -24
  216. package/src/requirement/__tests__/validate.test.ts +96 -96
  217. package/src/requirement/index.ts +4 -4
  218. package/src/requirement/types.ts +35 -35
  219. package/src/requirement/validate.ts +92 -92
  220. package/src/scenarios/__tests__/validate.test.ts +209 -209
  221. package/src/scenarios/index.ts +15 -15
  222. package/src/scenarios/types.ts +37 -37
  223. package/src/scenarios/validate.ts +122 -122
  224. package/src/schema-version.ts +16 -16
  225. package/src/scope.ts +46 -46
  226. package/src/stakeholder/__tests__/validate.test.ts +51 -47
  227. package/src/stakeholder/index.ts +1 -0
  228. package/src/stakeholder/types.ts +7 -7
  229. package/src/stakeholder/validate.ts +79 -52
  230. package/src/target-state/__tests__/validate.test.ts +48 -27
  231. package/src/target-state/index.ts +2 -0
  232. package/src/target-state/types.ts +11 -7
  233. package/src/target-state/validate.ts +76 -46
  234. package/src/theme/index.ts +4 -4
  235. package/src/theme/themes.ts +237 -237
  236. package/src/theme/tokens.ts +133 -133
  237. package/src/typed-id.ts +56 -56
  238. package/src/validation-types.ts +36 -36
  239. package/src/webview/__tests__/entry-notations.test.ts +77 -77
  240. package/src/webview/__tests__/entry.test.ts +64 -64
  241. package/src/webview/__tests__/render-goals.test.ts +72 -72
  242. package/src/webview/entry.ts +292 -292
  243. package/src/webview/render-activities.ts +5 -3
  244. package/src/webview/render-activity-card.ts +266 -234
  245. package/src/webview/render-fgca.ts +6 -5
  246. package/src/webview/render-goals.ts +111 -110
  247. package/src/webview/render-process-blueprint.ts +207 -207
  248. package/src/webview/styles.css +322 -323
  249. package/src/yaml-normalize.ts +56 -56
  250. package/dist/element-envelope.d.ts +0 -15
  251. package/dist/element-envelope.js +0 -55
  252. package/dist/issues/__tests__/layout.test.js +0 -62
  253. package/dist/issues/__tests__/validate.test.d.ts +0 -1
  254. package/dist/issues/__tests__/validate.test.js +0 -82
  255. package/dist/issues/index.d.ts +0 -4
  256. package/dist/issues/index.js +0 -2
  257. package/dist/issues/layout.d.ts +0 -10
  258. package/dist/issues/layout.js +0 -82
  259. package/dist/issues/types.d.ts +0 -61
  260. package/dist/issues/types.js +0 -6
  261. package/dist/issues/validate.d.ts +0 -3
  262. package/dist/issues/validate.js +0 -143
  263. package/dist/webview/transitrix-render.css +0 -323
  264. package/dist/webview/transitrix-render.js +0 -6452
  265. package/dist/webview/transitrix-render.js.map +0 -7
  266. package/src/element-envelope.ts +0 -76
  267. /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
@@ -1,110 +1,111 @@
1
- /**
2
- * Browser-safe SVG renderer for the canonical Goals notation.
3
- *
4
- * Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
5
- * validated GoalTree into renderable SVG so JCEF can drop it into the preview
6
- * panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
7
- * in VS Code-specific concerns (themes, title block, save dialogs); this
8
- * module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
9
- * no `node:fs`, no `node:path`.
10
- *
11
- * Step 4 will follow the same shape for the remaining ten notations.
12
- */
13
- import { layoutGoalTree } from '../goals/layout.js';
14
- import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
15
- import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
16
- import { generateSvgEmbedCss } from '../theme/index.js';
17
-
18
- const NODE_W = 250;
19
- const NODE_H = 60;
20
- const RANK_SEP = 100;
21
- const NODE_SEP = 24;
22
- const PAD = 24;
23
- const LABEL_MAX = 36;
24
- const LABEL_TRIM = 34;
25
-
26
- function escXml(s: string): string {
27
- return s
28
- .replace(/&/g, '&')
29
- .replace(/</g, '&lt;')
30
- .replace(/>/g, '&gt;')
31
- .replace(/"/g, '&quot;');
32
- }
33
-
34
- export interface RenderGoalsOptions {
35
- treeName?: string;
36
- curvature?: number;
37
- }
38
-
39
- export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
40
- const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE } = options;
41
-
42
- const layout: GoalTreeLayout = layoutGoalTree(tree, {
43
- nodeWidth: NODE_W,
44
- nodeHeight: NODE_H,
45
- rankSep: RANK_SEP,
46
- nodeSep: NODE_SEP,
47
- });
48
-
49
- if (layout.nodes.length === 0) {
50
- return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
51
- }
52
-
53
- const w = layout.bounds.width + PAD * 2;
54
- const h = layout.bounds.height + PAD * 2;
55
- const ox = -layout.bounds.x + PAD;
56
- const oy = -layout.bounds.y + PAD;
57
-
58
- const nodeMap = new Map(layout.nodes.map((n) => [n.id, n]));
59
-
60
- function edgePath(e: LaidOutEdge): string {
61
- const s = nodeMap.get(e.source);
62
- const t = nodeMap.get(e.target);
63
- if (!s || !t) return '';
64
- const sx = s.x + ox + s.width;
65
- const sy = s.y + oy + s.height / 2;
66
- const tx = t.x + ox;
67
- const ty = t.y + oy + t.height / 2;
68
- return horizontalCubicEdgePath(sx, sy, tx, ty, curvature);
69
- }
70
-
71
- const edgeSvg = layout.edges
72
- .map((e) => `<path d="${edgePath(e)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
73
- .join('\n');
74
-
75
- const nodeSvg = layout.nodes
76
- .map((n) => {
77
- const x = n.x + ox;
78
- const y = n.y + oy;
79
- const level = n.data.level % 8;
80
- const labelText = n.data.name ?? String(n.id);
81
- const label = labelText.length > LABEL_MAX ? labelText.slice(0, LABEL_TRIM) + '…' : labelText;
82
- return `<g>
83
- <rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="8"/>
84
- <text class="text-primary" x="${x + n.width / 2}" y="${y + n.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(label)}</text>
85
- </g>`;
86
- })
87
- .join('\n');
88
-
89
- const titleSvg = treeName
90
- ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
91
- : '';
92
-
93
- // Embed the shared theme CSS inside the SVG so the rendered output is
94
- // self-contained — the JCEF host page only needs to drop the SVG into the
95
- // DOM and styling resolves without any cooperation from the host stylesheet.
96
- // Matches what the VS Code path produces via `prepareSvgForExport`.
97
- const embedCss = generateSvgEmbedCss('transitrix');
98
-
99
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
100
- <style>${embedCss}</style>
101
- <defs>
102
- <marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
103
- <path d="M0,0 L0,6 L8,3 z" class="arrow-fill"/>
104
- </marker>
105
- </defs>
106
- ${titleSvg}
107
- ${nodeSvg}
108
- ${edgeSvg}
109
- </svg>`;
110
- }
1
+ /**
2
+ * Browser-safe SVG renderer for the canonical Goals notation.
3
+ *
4
+ * Step 3 of the IntelliJ epic (ADR 0001): the webview bundle must turn a
5
+ * validated GoalTree into renderable SVG so JCEF can drop it into the preview
6
+ * panel. The VS Code path lives in `extension/src/goals-preview.ts` and pulls
7
+ * in VS Code-specific concerns (themes, title block, save dialogs); this
8
+ * module is the host-neutral subset — pure layout → SVG with no VS Code APIs,
9
+ * no `node:fs`, no `node:path`.
10
+ *
11
+ * Step 4 will follow the same shape for the remaining ten notations.
12
+ */
13
+ import { layoutGoalTree } from '../goals/layout.js';
14
+ import type { GoalTree, GoalTreeLayout, LaidOutEdge } from '../goals/types.js';
15
+ import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
16
+ import { generateSvgEmbedCss } from '../theme/index.js';
17
+
18
+ const NODE_W = 250;
19
+ const NODE_H = 60;
20
+ const RANK_SEP = 100;
21
+ const NODE_SEP = 24;
22
+ const PAD = 24;
23
+ const LABEL_MAX = 36;
24
+ const LABEL_TRIM = 34;
25
+
26
+ function escXml(s: string): string {
27
+ return s
28
+ .replace(/&/g, '&amp;')
29
+ .replace(/</g, '&lt;')
30
+ .replace(/>/g, '&gt;')
31
+ .replace(/"/g, '&quot;');
32
+ }
33
+
34
+ export interface RenderGoalsOptions {
35
+ treeName?: string;
36
+ curvature?: number;
37
+ entryCurvature?: number;
38
+ }
39
+
40
+ export function renderGoalsSvg(tree: GoalTree, options: RenderGoalsOptions = {}): string {
41
+ const { treeName = '', curvature = DEFAULT_EDGE_CURVATURE, entryCurvature } = options;
42
+
43
+ const layout: GoalTreeLayout = layoutGoalTree(tree, {
44
+ nodeWidth: NODE_W,
45
+ nodeHeight: NODE_H,
46
+ rankSep: RANK_SEP,
47
+ nodeSep: NODE_SEP,
48
+ });
49
+
50
+ if (layout.nodes.length === 0) {
51
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
52
+ }
53
+
54
+ const w = layout.bounds.width + PAD * 2;
55
+ const h = layout.bounds.height + PAD * 2;
56
+ const ox = -layout.bounds.x + PAD;
57
+ const oy = -layout.bounds.y + PAD;
58
+
59
+ const nodeMap = new Map(layout.nodes.map((n) => [n.id, n]));
60
+
61
+ function edgePath(e: LaidOutEdge): string {
62
+ const s = nodeMap.get(e.source);
63
+ const t = nodeMap.get(e.target);
64
+ if (!s || !t) return '';
65
+ const sx = s.x + ox + s.width;
66
+ const sy = s.y + oy + s.height / 2;
67
+ const tx = t.x + ox;
68
+ const ty = t.y + oy + t.height / 2;
69
+ return horizontalCubicEdgePath(sx, sy, tx, ty, curvature, entryCurvature);
70
+ }
71
+
72
+ const edgeSvg = layout.edges
73
+ .map((e) => `<path d="${edgePath(e)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
74
+ .join('\n');
75
+
76
+ const nodeSvg = layout.nodes
77
+ .map((n) => {
78
+ const x = n.x + ox;
79
+ const y = n.y + oy;
80
+ const level = n.data.level % 8;
81
+ const labelText = n.data.name ?? String(n.id);
82
+ const label = labelText.length > LABEL_MAX ? labelText.slice(0, LABEL_TRIM) + '…' : labelText;
83
+ return `<g>
84
+ <rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="8"/>
85
+ <text class="text-primary" x="${x + n.width / 2}" y="${y + n.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(label)}</text>
86
+ </g>`;
87
+ })
88
+ .join('\n');
89
+
90
+ const titleSvg = treeName
91
+ ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(`Goal tree — ${treeName}`)}</text>`
92
+ : '';
93
+
94
+ // Embed the shared theme CSS inside the SVG so the rendered output is
95
+ // self-contained — the JCEF host page only needs to drop the SVG into the
96
+ // DOM and styling resolves without any cooperation from the host stylesheet.
97
+ // Matches what the VS Code path produces via `prepareSvgForExport`.
98
+ const embedCss = generateSvgEmbedCss('transitrix');
99
+
100
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
101
+ <style>${embedCss}</style>
102
+ <defs>
103
+ <marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
104
+ <path d="M0,0 L0,6 L8,3 z" class="arrow-fill"/>
105
+ </marker>
106
+ </defs>
107
+ ${titleSvg}
108
+ ${nodeSvg}
109
+ ${edgeSvg}
110
+ </svg>`;
111
+ }
@@ -1,207 +1,207 @@
1
- /**
2
- * Host-neutral SVG renderer for the Process Blueprint notation.
3
- *
4
- * Step 4 of the IntelliJ epic (ADR 0001): ported from the pure SVG core of
5
- * `extension/src/process-blueprint-preview.ts` (`layoutToSvg`). The VS Code
6
- * title block (`svg-title-block.ts`) is intentionally dropped — JCEF has no
7
- * VS Code APIs — and replaced with `render-goals.ts`' simple optional
8
- * `<text class="text-header">` heading. No `vscode`, no `node:*`, no host APIs;
9
- * the shared theme CSS is embedded inside the `<svg>` so the output is
10
- * self-contained.
11
- */
12
- import { layoutProcessBlueprint } from '../process-blueprint/layout.js';
13
- import type {
14
- ComplianceChip,
15
- ProcessBlueprintFile,
16
- ProcessBlueprintLayout,
17
- } from '../process-blueprint/types.js';
18
- import { generateSvgEmbedCss } from '../theme/index.js';
19
- import { escXml } from './render-util.js';
20
-
21
- const PAD = 24;
22
-
23
- function truncate(text: string, maxChars: number): string {
24
- if (text.length <= maxChars) return text;
25
- return text.slice(0, Math.max(0, maxChars - 1)) + '…';
26
- }
27
-
28
- /**
29
- * Render a left-anchored, vertically centred multi-line text cell. `lines` are
30
- * pre-wrapped by the layout; the block is centred within the cell height.
31
- */
32
- function textCellSvg(
33
- lines: string[],
34
- cls: string,
35
- x: number,
36
- cellTop: number,
37
- cellHeight: number,
38
- lineHeight: number,
39
- ): string {
40
- const ls = lines.length > 0 ? lines : [''];
41
- const first = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * lineHeight;
42
- const tspans = ls
43
- .map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
44
- .join('');
45
- return `<text class="${cls}" dominant-baseline="central">${tspans}</text>`;
46
- }
47
-
48
- /**
49
- * Render a single compliance law chip.
50
- *
51
- * Three orthogonal decorations are expressed as SVG overrides:
52
- * - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
53
- * - `gap` — warning-level fill (`class="…compliance-gap"`)
54
- * - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
55
- * badge circle in the top-right corner
56
- */
57
- function complianceChipSvg(chip: ComplianceChip, ox: number, oy: number): string {
58
- const { x, y, width, height, lawId, decorations } = chip;
59
- const ax = x + ox;
60
- const ay = y + oy;
61
- const rx = 6;
62
- const hasNew = decorations.includes('new');
63
- const hasGap = decorations.includes('gap');
64
- const hasDeadline = decorations.includes('deadline');
65
-
66
- let rectClass = 'diagram-node level-5 compliance-chip';
67
- if (hasDeadline) rectClass += ' compliance-deadline';
68
- else if (hasGap) rectClass += ' compliance-gap';
69
-
70
- const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
71
- const parts: string[] = [];
72
- parts.push(
73
- `<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="${rx}"${strokeDash}/>`,
74
- );
75
- parts.push(
76
- `<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(lawId, Math.floor(width / 8)))}</text>`,
77
- );
78
- if (hasDeadline) {
79
- const br = 5;
80
- const bx = ax + width - br - 3;
81
- const by = ay + br + 3;
82
- parts.push(`<circle class="compliance-badge" cx="${bx}" cy="${by}" r="${br}"/>`);
83
- parts.push(
84
- `<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle" dominant-baseline="central">!</text>`,
85
- );
86
- }
87
- return parts.join('\n');
88
- }
89
-
90
- export interface RenderProcessBlueprintOptions {
91
- title?: string;
92
- }
93
-
94
- export function renderProcessBlueprintSvg(
95
- doc: ProcessBlueprintFile,
96
- options: RenderProcessBlueprintOptions = {},
97
- ): string {
98
- const { title = '' } = options;
99
-
100
- const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc);
101
-
102
- if (layout.bounds.width === 0 || layout.bounds.height === 0) {
103
- return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
104
- }
105
-
106
- const titleH = title ? PAD : 0;
107
- const w = layout.bounds.width + PAD * 2;
108
- const h = layout.bounds.height + PAD * 2 + titleH;
109
- const ox = PAD;
110
- const oy = PAD + titleH;
111
-
112
- const parts: string[] = [];
113
-
114
- parts.push(
115
- `<rect class="diagram-node level-0" x="${ox}" y="${oy}" width="${layout.bounds.width}" height="${layout.bounds.height}" rx="6"/>`,
116
- );
117
-
118
- for (const s of layout.stageHeaders) {
119
- parts.push(
120
- `<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}"/>`,
121
- );
122
- parts.push(
123
- `<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(s.name, 28))}</text>`,
124
- );
125
- }
126
-
127
- for (const l of layout.legend) {
128
- parts.push(
129
- `<rect class="diagram-node level-2" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}"/>`,
130
- );
131
- parts.push(
132
- `<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}" dominant-baseline="central">${escXml(l.label)}</text>`,
133
- );
134
- }
135
-
136
- const textX = layout.cellTextPadX;
137
- for (const c of layout.goalCells) {
138
- parts.push(
139
- `<rect class="diagram-node level-3" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
140
- );
141
- parts.push(
142
- textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
143
- );
144
- }
145
- for (const c of layout.resultCells) {
146
- parts.push(
147
- `<rect class="diagram-node level-4" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
148
- );
149
- parts.push(
150
- textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
151
- );
152
- }
153
-
154
- for (let r = 0; r < layout.aspectRows.length; r++) {
155
- const row = layout.aspectRows[r];
156
- const level = 5 + (r % 3);
157
- parts.push(
158
- `<rect class="diagram-node level-${level}" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.15"/>`,
159
- );
160
- for (let i = 1; i < layout.stageHeaders.length; i++) {
161
- const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
162
- parts.push(
163
- `<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
164
- );
165
- }
166
- for (const p of row.pills) {
167
- parts.push(
168
- `<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
169
- );
170
- const label = p.id ? `${p.name} · ${p.id}` : p.name;
171
- parts.push(
172
- `<text class="text-pill" x="${p.x + ox + p.width / 2}" y="${p.y + oy + p.height / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncate(label, Math.floor(p.width / 8)))}</text>`,
173
- );
174
- }
175
- }
176
-
177
- // Compliance row (optional).
178
- if (layout.complianceRow) {
179
- const row = layout.complianceRow;
180
- parts.push(
181
- `<rect class="diagram-node level-5" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.10"/>`,
182
- );
183
- for (let i = 1; i < layout.stageHeaders.length; i++) {
184
- const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
185
- parts.push(
186
- `<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
187
- );
188
- }
189
- for (const chip of row.chips) {
190
- parts.push(complianceChipSvg(chip, ox, oy));
191
- }
192
- }
193
-
194
- const titleSvg = title
195
- ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
196
- : '';
197
-
198
- // Embed the shared theme CSS inside the SVG so the rendered output is
199
- // self-contained for the JCEF host — matches render-goals.ts.
200
- const embedCss = generateSvgEmbedCss('transitrix');
201
-
202
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
203
- <style>${embedCss}</style>
204
- ${titleSvg}
205
- ${parts.join('\n')}
206
- </svg>`;
207
- }
1
+ /**
2
+ * Host-neutral SVG renderer for the Process Blueprint notation.
3
+ *
4
+ * Step 4 of the IntelliJ epic (ADR 0001): ported from the pure SVG core of
5
+ * `extension/src/process-blueprint-preview.ts` (`layoutToSvg`). The VS Code
6
+ * title block (`svg-title-block.ts`) is intentionally dropped — JCEF has no
7
+ * VS Code APIs — and replaced with `render-goals.ts`' simple optional
8
+ * `<text class="text-header">` heading. No `vscode`, no `node:*`, no host APIs;
9
+ * the shared theme CSS is embedded inside the `<svg>` so the output is
10
+ * self-contained.
11
+ */
12
+ import { layoutProcessBlueprint } from '../process-blueprint/layout.js';
13
+ import type {
14
+ ComplianceChip,
15
+ ProcessBlueprintFile,
16
+ ProcessBlueprintLayout,
17
+ } from '../process-blueprint/types.js';
18
+ import { generateSvgEmbedCss } from '../theme/index.js';
19
+ import { escXml } from './render-util.js';
20
+
21
+ const PAD = 24;
22
+
23
+ function truncate(text: string, maxChars: number): string {
24
+ if (text.length <= maxChars) return text;
25
+ return text.slice(0, Math.max(0, maxChars - 1)) + '…';
26
+ }
27
+
28
+ /**
29
+ * Render a left-anchored, vertically centred multi-line text cell. `lines` are
30
+ * pre-wrapped by the layout; the block is centred within the cell height.
31
+ */
32
+ function textCellSvg(
33
+ lines: string[],
34
+ cls: string,
35
+ x: number,
36
+ cellTop: number,
37
+ cellHeight: number,
38
+ lineHeight: number,
39
+ ): string {
40
+ const ls = lines.length > 0 ? lines : [''];
41
+ const first = cellTop + cellHeight / 2 - ((ls.length - 1) / 2) * lineHeight;
42
+ const tspans = ls
43
+ .map((ln, i) => `<tspan x="${x}" y="${first + i * lineHeight}">${escXml(ln)}</tspan>`)
44
+ .join('');
45
+ return `<text class="${cls}" style="dominant-baseline:central">${tspans}</text>`;
46
+ }
47
+
48
+ /**
49
+ * Render a single compliance law chip.
50
+ *
51
+ * Three orthogonal decorations are expressed as SVG overrides:
52
+ * - `new` — dashed stroke border (`stroke-dasharray="4 2"`)
53
+ * - `gap` — warning-level fill (`class="…compliance-gap"`)
54
+ * - `deadline` — urgent fill override (`class="…compliance-deadline"`) + a small
55
+ * badge circle in the top-right corner
56
+ */
57
+ function complianceChipSvg(chip: ComplianceChip, ox: number, oy: number): string {
58
+ const { x, y, width, height, lawId, decorations } = chip;
59
+ const ax = x + ox;
60
+ const ay = y + oy;
61
+ const rx = 6;
62
+ const hasNew = decorations.includes('new');
63
+ const hasGap = decorations.includes('gap');
64
+ const hasDeadline = decorations.includes('deadline');
65
+
66
+ let rectClass = 'diagram-node level-5 compliance-chip';
67
+ if (hasDeadline) rectClass += ' compliance-deadline';
68
+ else if (hasGap) rectClass += ' compliance-gap';
69
+
70
+ const strokeDash = hasNew ? ' stroke-dasharray="4 2"' : '';
71
+ const parts: string[] = [];
72
+ parts.push(
73
+ `<rect class="${rectClass}" x="${ax}" y="${ay}" width="${width}" height="${height}" rx="${rx}"${strokeDash}/>`,
74
+ );
75
+ parts.push(
76
+ `<text class="text-pill" x="${ax + width / 2}" y="${ay + height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(lawId, Math.floor(width / 8)))}</text>`,
77
+ );
78
+ if (hasDeadline) {
79
+ const br = 5;
80
+ const bx = ax + width - br - 3;
81
+ const by = ay + br + 3;
82
+ parts.push(`<circle class="compliance-badge" cx="${bx}" cy="${by}" r="${br}"/>`);
83
+ parts.push(
84
+ `<text class="compliance-badge-text" x="${bx}" y="${by}" text-anchor="middle" style="dominant-baseline:central">!</text>`,
85
+ );
86
+ }
87
+ return parts.join('\n');
88
+ }
89
+
90
+ export interface RenderProcessBlueprintOptions {
91
+ title?: string;
92
+ }
93
+
94
+ export function renderProcessBlueprintSvg(
95
+ doc: ProcessBlueprintFile,
96
+ options: RenderProcessBlueprintOptions = {},
97
+ ): string {
98
+ const { title = '' } = options;
99
+
100
+ const layout: ProcessBlueprintLayout = layoutProcessBlueprint(doc);
101
+
102
+ if (layout.bounds.width === 0 || layout.bounds.height === 0) {
103
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0" viewBox="0 0 0 0"></svg>`;
104
+ }
105
+
106
+ const titleH = title ? PAD : 0;
107
+ const w = layout.bounds.width + PAD * 2;
108
+ const h = layout.bounds.height + PAD * 2 + titleH;
109
+ const ox = PAD;
110
+ const oy = PAD + titleH;
111
+
112
+ const parts: string[] = [];
113
+
114
+ parts.push(
115
+ `<rect class="diagram-node level-0" x="${ox}" y="${oy}" width="${layout.bounds.width}" height="${layout.bounds.height}" rx="6"/>`,
116
+ );
117
+
118
+ for (const s of layout.stageHeaders) {
119
+ parts.push(
120
+ `<rect class="diagram-node level-1" x="${s.x + ox}" y="${s.y + oy}" width="${s.width}" height="${s.height}"/>`,
121
+ );
122
+ parts.push(
123
+ `<text class="text-header" x="${s.x + ox + s.width / 2}" y="${s.y + oy + s.height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(s.name, 28))}</text>`,
124
+ );
125
+ }
126
+
127
+ for (const l of layout.legend) {
128
+ parts.push(
129
+ `<rect class="diagram-node level-2" x="${ox}" y="${l.y + oy}" width="${layout.legendColumnWidth}" height="${l.height}"/>`,
130
+ );
131
+ parts.push(
132
+ `<text class="text-primary" x="${ox + 12}" y="${l.y + oy + l.height / 2}" style="dominant-baseline:central">${escXml(l.label)}</text>`,
133
+ );
134
+ }
135
+
136
+ const textX = layout.cellTextPadX;
137
+ for (const c of layout.goalCells) {
138
+ parts.push(
139
+ `<rect class="diagram-node level-3" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
140
+ );
141
+ parts.push(
142
+ textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
143
+ );
144
+ }
145
+ for (const c of layout.resultCells) {
146
+ parts.push(
147
+ `<rect class="diagram-node level-4" x="${c.x + ox}" y="${c.y + oy}" width="${c.width}" height="${c.height}"/>`,
148
+ );
149
+ parts.push(
150
+ textCellSvg(c.lines, 'text-secondary', c.x + ox + textX, c.y + oy, c.height, layout.textLineHeight),
151
+ );
152
+ }
153
+
154
+ for (let r = 0; r < layout.aspectRows.length; r++) {
155
+ const row = layout.aspectRows[r];
156
+ const level = 5 + (r % 3);
157
+ parts.push(
158
+ `<rect class="diagram-node level-${level}" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.15"/>`,
159
+ );
160
+ for (let i = 1; i < layout.stageHeaders.length; i++) {
161
+ const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
162
+ parts.push(
163
+ `<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
164
+ );
165
+ }
166
+ for (const p of row.pills) {
167
+ parts.push(
168
+ `<rect class="diagram-node level-${level}" x="${p.x + ox}" y="${p.y + oy}" width="${p.width}" height="${p.height}" rx="6"/>`,
169
+ );
170
+ const label = p.id ? `${p.name} · ${p.id}` : p.name;
171
+ parts.push(
172
+ `<text class="text-pill" x="${p.x + ox + p.width / 2}" y="${p.y + oy + p.height / 2}" text-anchor="middle" style="dominant-baseline:central">${escXml(truncate(label, Math.floor(p.width / 8)))}</text>`,
173
+ );
174
+ }
175
+ }
176
+
177
+ // Compliance row (optional).
178
+ if (layout.complianceRow) {
179
+ const row = layout.complianceRow;
180
+ parts.push(
181
+ `<rect class="diagram-node level-5" x="${layout.legendColumnWidth + ox}" y="${row.y + oy}" width="${layout.bounds.width - layout.legendColumnWidth}" height="${row.height}" opacity="0.10"/>`,
182
+ );
183
+ for (let i = 1; i < layout.stageHeaders.length; i++) {
184
+ const x = layout.legendColumnWidth + i * layout.stageColumnWidth + ox;
185
+ parts.push(
186
+ `<line class="diagram-edge" x1="${x}" y1="${row.y + oy}" x2="${x}" y2="${row.y + row.height + oy}" opacity="0.3"/>`,
187
+ );
188
+ }
189
+ for (const chip of row.chips) {
190
+ parts.push(complianceChipSvg(chip, ox, oy));
191
+ }
192
+ }
193
+
194
+ const titleSvg = title
195
+ ? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
196
+ : '';
197
+
198
+ // Embed the shared theme CSS inside the SVG so the rendered output is
199
+ // self-contained for the JCEF host — matches render-goals.ts.
200
+ const embedCss = generateSvgEmbedCss('transitrix');
201
+
202
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
203
+ <style>${embedCss}</style>
204
+ ${titleSvg}
205
+ ${parts.join('\n')}
206
+ </svg>`;
207
+ }