@webpieces/nx-webpieces-rules 0.4.853 → 0.4.855

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 (110) hide show
  1. package/README.md +14 -0
  2. package/executors.json +5 -0
  3. package/package.json +7 -7
  4. package/src/executors/generate/executor.js +31 -55
  5. package/src/executors/generate/executor.js.map +1 -1
  6. package/src/executors/runtime-wiring-check/executor.d.ts +10 -0
  7. package/src/executors/runtime-wiring-check/executor.js +42 -0
  8. package/src/executors/runtime-wiring-check/executor.js.map +1 -0
  9. package/src/executors/runtime-wiring-check/schema.json +14 -0
  10. package/src/executors/validate-api-lib-tag/executor.js +5 -3
  11. package/src/executors/validate-api-lib-tag/executor.js.map +1 -1
  12. package/src/executors/validate-api-relations/executor.js +11 -8
  13. package/src/executors/validate-api-relations/executor.js.map +1 -1
  14. package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -1
  15. package/src/executors/validate-architecture-unchanged/executor.js +22 -24
  16. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  17. package/src/index.d.ts +1 -0
  18. package/src/index.js +5 -1
  19. package/src/index.js.map +1 -1
  20. package/src/lib/api-usage/api-contract-approval.d.ts +7 -0
  21. package/src/lib/api-usage/api-contract-approval.js +50 -0
  22. package/src/lib/api-usage/api-contract-approval.js.map +1 -0
  23. package/src/lib/api-usage/api-contract-evidence.d.ts +6 -0
  24. package/src/lib/api-usage/api-contract-evidence.js +41 -0
  25. package/src/lib/api-usage/api-contract-evidence.js.map +1 -0
  26. package/src/lib/api-usage/api-relations-validator.d.ts +13 -2
  27. package/src/lib/api-usage/api-relations-validator.js +23 -0
  28. package/src/lib/api-usage/api-relations-validator.js.map +1 -1
  29. package/src/lib/api-usage/api-relations.d.ts +3 -0
  30. package/src/lib/api-usage/api-relations.js.map +1 -1
  31. package/src/lib/api-usage/api-scanner.d.ts +40 -0
  32. package/src/lib/api-usage/api-scanner.js +7 -1
  33. package/src/lib/api-usage/api-scanner.js.map +1 -1
  34. package/src/lib/graph-comparator.js +11 -1
  35. package/src/lib/graph-comparator.js.map +1 -1
  36. package/src/lib/graph-filter.client.d.ts +40 -0
  37. package/src/lib/graph-filter.client.js +190 -0
  38. package/src/lib/graph-filter.client.js.map +1 -0
  39. package/src/lib/graph-level-bands.d.ts +0 -18
  40. package/src/lib/graph-level-bands.js +4 -3
  41. package/src/lib/graph-level-bands.js.map +1 -1
  42. package/src/lib/graph-loader.js +12 -6
  43. package/src/lib/graph-loader.js.map +1 -1
  44. package/src/lib/graph-navigation.d.ts +5 -0
  45. package/src/lib/graph-navigation.js +102 -0
  46. package/src/lib/graph-navigation.js.map +1 -0
  47. package/src/lib/graph-node-menu.d.ts +1 -0
  48. package/src/lib/graph-node-menu.js +29 -0
  49. package/src/lib/graph-node-menu.js.map +1 -1
  50. package/src/lib/graph-render-model.d.ts +32 -0
  51. package/src/lib/graph-render-model.js +76 -0
  52. package/src/lib/graph-render-model.js.map +1 -0
  53. package/src/lib/graph-sorter.d.ts +2 -0
  54. package/src/lib/graph-sorter.js.map +1 -1
  55. package/src/lib/graph-visualizer.client.d.ts +14 -73
  56. package/src/lib/graph-visualizer.client.js +71 -243
  57. package/src/lib/graph-visualizer.client.js.map +1 -1
  58. package/src/lib/graph-visualizer.d.ts +8 -15
  59. package/src/lib/graph-visualizer.js +48 -26
  60. package/src/lib/graph-visualizer.js.map +1 -1
  61. package/src/lib/runtime-details.d.ts +13 -0
  62. package/src/lib/runtime-details.js +191 -0
  63. package/src/lib/runtime-details.js.map +1 -0
  64. package/src/lib/runtime-graph-model.d.ts +4 -1
  65. package/src/lib/runtime-graph-model.js.map +1 -1
  66. package/src/lib/runtime-graph.js +10 -4
  67. package/src/lib/runtime-graph.js.map +1 -1
  68. package/src/lib/runtime-html-page.d.ts +7 -3
  69. package/src/lib/runtime-html-page.js +19 -6
  70. package/src/lib/runtime-html-page.js.map +1 -1
  71. package/src/lib/runtime-visualizer.client.d.ts +8 -19
  72. package/src/lib/runtime-visualizer.client.js +28 -41
  73. package/src/lib/runtime-visualizer.client.js.map +1 -1
  74. package/src/lib/runtime-visualizer.d.ts +3 -45
  75. package/src/lib/runtime-visualizer.js +77 -79
  76. package/src/lib/runtime-visualizer.js.map +1 -1
  77. package/src/lib/runtime-viz-theme.js +4 -3
  78. package/src/lib/runtime-viz-theme.js.map +1 -1
  79. package/src/lib/runtime-wiring/DeclarationCompileAssertions.d.ts +4 -0
  80. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js +27 -0
  81. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js.map +1 -0
  82. package/src/lib/runtime-wiring/approved-graph.d.ts +8 -0
  83. package/src/lib/runtime-wiring/approved-graph.js +115 -0
  84. package/src/lib/runtime-wiring/approved-graph.js.map +1 -0
  85. package/src/lib/runtime-wiring/assembler.d.ts +24 -0
  86. package/src/lib/runtime-wiring/assembler.js +116 -0
  87. package/src/lib/runtime-wiring/assembler.js.map +1 -0
  88. package/src/lib/runtime-wiring/codec.d.ts +14 -0
  89. package/src/lib/runtime-wiring/codec.js +162 -0
  90. package/src/lib/runtime-wiring/codec.js.map +1 -0
  91. package/src/lib/runtime-wiring/declaration.d.ts +68 -0
  92. package/src/lib/runtime-wiring/declaration.js +88 -0
  93. package/src/lib/runtime-wiring/declaration.js.map +1 -0
  94. package/src/lib/runtime-wiring/source-extractor.d.ts +28 -0
  95. package/src/lib/runtime-wiring/source-extractor.js +253 -0
  96. package/src/lib/runtime-wiring/source-extractor.js.map +1 -0
  97. package/src/lib/runtime-wiring/source-values.d.ts +21 -0
  98. package/src/lib/runtime-wiring/source-values.js +204 -0
  99. package/src/lib/runtime-wiring/source-values.js.map +1 -0
  100. package/src/lib/runtime-wiring/verification.d.ts +9 -0
  101. package/src/lib/runtime-wiring/verification.js +56 -0
  102. package/src/lib/runtime-wiring/verification.js.map +1 -0
  103. package/src/plugin.d.ts +0 -21
  104. package/src/plugin.js +12 -7
  105. package/src/plugin.js.map +1 -1
  106. package/src/runtime-wiring-targets.d.ts +7 -0
  107. package/src/runtime-wiring-targets.js +58 -0
  108. package/src/runtime-wiring-targets.js.map +1 -0
  109. package/src/validation-targets.js +10 -2
  110. package/src/validation-targets.js.map +1 -1
@@ -1,125 +1,28 @@
1
1
  "use strict";
2
- /*
3
- * Browser-side script for architecture/dependencies.html.
4
- *
5
- * graph-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT
6
- * placeholder (see viz-client-globals.d.ts) with the JSON-encoded Graphviz DOT, and inlines the result into a <script> tag. So it
7
- * runs in a browser, never in node — but it is ordinary TypeScript, compiled in place by tsc exactly
8
- * like the package's bin entry points, and linted like every other file here.
9
- *
10
- * IT USED TO BE A COMMITTED .js ASSET, exempted from `no-js-files`, and its own header said why: so the
11
- * dim/highlight/lock logic "can define ordinary browser functions without tripping the TypeScript lint
12
- * rules". That is a file existing to dodge the rules — the same anti-pattern as the bin shims this repo
13
- * deleted, and the same cure applies. The logic is a CLASS now, which is what `no-function-outside-class`
14
- * was asking for all along; nothing needed a disable.
15
- *
16
- * Behaviour: after Viz renders the SVG, hovering a box dims the rest and lights its full
17
- * ancestor+descendant chain; the #wp-lock dropdown PINS one box's chain (the dim survives mouse-leave)
18
- * and filters the responsibilities cards below the graph to just that chain. "All" clears the lock.
19
- *
20
- * CLICKING a box opens the shared floating node menu (graph-node-menu.ts, the same menu every
21
- * project's design.html uses) — "View Design" only for a project that HAS a design.html, then
22
- * Lock/Unlock. Menu and dropdown are one lock: both go through setLock(), so each reflects the other.
23
- */
24
- // The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both
25
- // scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token
26
- // in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.
27
- /**
28
- * The class graph-visualizer.ts stamps on the invisible rank anchors, spacer bands and ordering
29
- * edges that pin each level to its own row. They are layout scaffolding, not architecture, so the
30
- * indexes below skip them: an anchor must never be walkable as a dependency, or hovering one box
31
- * would light boxes it has nothing to do with.
32
- *
33
- * Graphviz drops `style=invis` elements from its SVG entirely (verified against the emitted SVG),
34
- * so today nothing carrying this class reaches the DOM at all. The skip is the guarantee that the
35
- * scaffolding stays inert if that ever stops being true — the DOT is the only place that decides
36
- * what is scaffolding, and it says so with this class.
37
- */
38
- const LAYOUT_CLASS = 'wp-layout';
39
- /** One traversal direction: the edges to light, and the nodes to walk on to. */
40
- class Direction {
41
- nodes;
42
- edges;
43
- constructor(nodes, edges) {
44
- this.nodes = nodes;
45
- this.edges = edges;
46
- }
47
- }
48
- /**
49
- * Indexes the rendered SVG and drives the dim/highlight/lock interaction.
50
- *
51
- * The adjacency is DIRECTED and kept as two maps per axis: `in*` is what points AT a node (ancestors,
52
- * walked upward) and `out*` is what it points to (dependencies, walked downward). Highlighting walks
53
- * both, so a hovered box lights its entire chain in both directions rather than just its neighbours.
54
- */
2
+ /** Architecture interactions bind to each new SVG; logical Lock/Filter state belongs to the page. */
55
3
  class GraphHighlighter {
56
4
  svg;
5
+ chain;
6
+ model;
7
+ page;
57
8
  nodeByName = new Map();
58
- inEdges = new Map();
59
- outEdges = new Map();
60
- inNodes = new Map();
61
- outNodes = new Map();
62
- /**
63
- * The box the dropdown pinned, or null. Hover still works on top of a lock — leaving a box
64
- * restores the LOCKED view instead of clearing, so the pinned subgraph stays visible while you
65
- * scroll down to its responsibility cards.
66
- */
67
- locked = null;
68
- constructor(svg) {
9
+ constructor(svg, chain, model, page) {
69
10
  this.svg = svg;
11
+ this.chain = chain;
12
+ this.model = model;
13
+ this.page = page;
70
14
  }
71
- /** Index the SVG, then wire hover, the lock dropdown, and the per-node floating menu. */
72
15
  wire() {
73
- this.indexNodes();
74
- this.indexEdges();
75
- this.wireHover();
76
- this.wireLock();
77
- this.wireMenu();
78
- }
79
- indexNodes() {
80
16
  this.svg.querySelectorAll('g.node').forEach((g) => {
81
- if (g.classList.contains(LAYOUT_CLASS))
82
- return;
83
- const title = g.querySelector('title');
84
- if (title !== null && title.textContent !== null) {
85
- this.nodeByName.set(title.textContent.trim(), g);
86
- }
87
- });
88
- }
89
- indexEdges() {
90
- this.svg.querySelectorAll('g.edge').forEach((edge) => {
91
- if (edge.classList.contains(LAYOUT_CLASS))
17
+ if (g.classList.contains('wp-layout'))
92
18
  return;
93
- const title = edge.querySelector('title');
94
- const text = title === null ? null : title.textContent;
95
- if (text === null)
96
- return;
97
- const idx = text.indexOf('->');
98
- if (idx < 0)
99
- return;
100
- const from = text.slice(0, idx).trim();
101
- const to = text.slice(idx + 2).trim();
102
- this.ensureEdges(this.outEdges, from).add(edge);
103
- this.ensureEdges(this.inEdges, to).add(edge);
104
- this.ensureNodes(this.outNodes, from).add(to);
105
- this.ensureNodes(this.inNodes, to).add(from);
19
+ const name = g.querySelector('title')?.textContent;
20
+ if (name !== null && name !== undefined)
21
+ this.nodeByName.set(name, g);
106
22
  });
107
- }
108
- ensureEdges(map, key) {
109
- const existing = map.get(key);
110
- if (existing !== undefined)
111
- return existing;
112
- const created = new Set();
113
- map.set(key, created);
114
- return created;
115
- }
116
- ensureNodes(map, key) {
117
- const existing = map.get(key);
118
- if (existing !== undefined)
119
- return existing;
120
- const created = new Set();
121
- map.set(key, created);
122
- return created;
23
+ this.wireHover();
24
+ this.wireMenu();
25
+ this.relight();
123
26
  }
124
27
  clear() {
125
28
  this.svg.classList.remove('wp-dim');
@@ -127,161 +30,86 @@ class GraphHighlighter {
127
30
  el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl');
128
31
  });
129
32
  }
130
- /**
131
- * Dim everything, then transitively light ancestors and descendants of `name`: edges reached get
132
- * `wp-hl`, boxes get `wp-neighbor`. The `visited` set is what makes a cyclic graph terminate.
133
- */
134
- highlight(name, focusEl) {
33
+ highlight(name) {
135
34
  this.clear();
35
+ const focus = this.nodeByName.get(name);
36
+ if (focus === undefined)
37
+ return;
38
+ const lit = this.chain.nodes(name);
136
39
  this.svg.classList.add('wp-dim');
137
- focusEl.classList.add('wp-focus');
138
- const directions = [
139
- new Direction(this.inNodes, this.inEdges),
140
- new Direction(this.outNodes, this.outEdges),
141
- ];
142
- for (const dir of directions)
143
- this.walk(name, dir);
144
- }
145
- walk(start, dir) {
146
- const visited = new Set();
147
- const stack = [start];
148
- while (stack.length > 0) {
149
- const cur = stack.pop();
150
- const edges = dir.edges.get(cur);
151
- if (edges !== undefined) {
152
- edges.forEach((e) => { e.classList.add('wp-hl'); });
40
+ focus.classList.add('wp-focus');
41
+ for (const id of lit)
42
+ this.nodeByName.get(id)?.classList.add('wp-neighbor');
43
+ for (const edge of this.model.edges) {
44
+ if (lit.has(edge.from) && lit.has(edge.to)) {
45
+ this.svg.querySelector(`[id="${edge.id}"]`)?.classList.add('wp-hl');
153
46
  }
154
- const next = dir.nodes.get(cur);
155
- if (next === undefined)
156
- continue;
157
- next.forEach((name) => {
158
- if (visited.has(name))
159
- return;
160
- visited.add(name);
161
- stack.push(name);
162
- const g = this.nodeByName.get(name);
163
- if (g !== undefined)
164
- g.classList.add('wp-neighbor');
165
- });
166
47
  }
167
48
  }
168
- /** Leaving a box restores the locked view rather than clearing it outright. */
169
49
  relight() {
170
- if (this.locked === null) {
50
+ const locked = this.page.lockSelection();
51
+ if (locked === null)
171
52
  this.clear();
172
- return;
173
- }
174
- const g = this.nodeByName.get(this.locked);
175
- if (g !== undefined)
176
- this.highlight(this.locked, g);
53
+ else
54
+ this.highlight(locked);
177
55
  }
178
56
  wireHover() {
179
57
  this.nodeByName.forEach((g, name) => {
180
- g.addEventListener('mouseenter', () => { this.highlight(name, g); });
181
- g.addEventListener('mouseleave', () => { this.relight(); });
58
+ g.addEventListener('mouseenter', () => this.highlight(name));
59
+ g.addEventListener('mouseleave', () => this.relight());
182
60
  });
183
61
  }
184
- wireLock() {
185
- const lockSelect = this.lockSelect();
186
- if (lockSelect === null)
187
- return;
188
- lockSelect.addEventListener('change', () => {
189
- this.applyLock(lockSelect.value === '' ? null : lockSelect.value);
62
+ wireMenu() {
63
+ const designs = new Map(__DESIGN_LINKS__.map((link) => [link.nodeId, link.href]));
64
+ WpNodeMenu.wire(this.svg, (name) => {
65
+ const items = [];
66
+ const href = designs.get(name);
67
+ if (href !== undefined)
68
+ items.push(new WpNodeMenuItem('View Design', () => window.open(href, '_blank')));
69
+ const locked = this.page.lockSelection() === name;
70
+ items.push(new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', () => this.page.setLock(locked ? null : name)));
71
+ items.push(this.page.filterItem(name));
72
+ return items;
190
73
  });
191
74
  }
192
- lockSelect() {
193
- return document.getElementById('wp-lock');
75
+ }
76
+ class GraphPage extends WpFilterPage {
77
+ highlighter = null;
78
+ captureBinding(_svg) {
79
+ const previous = this.highlighter;
80
+ return () => { this.highlighter = previous; this.filterCards(); };
81
+ }
82
+ wireControls() {
83
+ const select = document.getElementById('wp-lock');
84
+ select?.addEventListener('change', () => this.setLock(select.value === '' ? null : select.value));
85
+ }
86
+ wireSvg(svg) {
87
+ this.highlighter = new GraphHighlighter(svg, this.chain, this.model, this);
88
+ this.highlighter.wire();
89
+ this.filterCards();
90
+ svg.querySelectorAll('g.node').forEach((node) => {
91
+ if (node.querySelector('title')?.textContent === this.anchor)
92
+ node.classList.add('wp-filter-anchor');
93
+ });
194
94
  }
195
- /** True when `name` is the box the page is currently locked on — what the menu labels itself by. */
196
- isLocked(name) {
197
- return this.locked === name;
95
+ lockSelection() {
96
+ return this.locked;
198
97
  }
199
- /**
200
- * THE one lock entry point, so the dropdown and the node menu can never disagree: it sets the
201
- * dropdown's selection, re-highlights, and re-filters the cards whichever of the two asked.
202
- * `null` is "All (no lock)".
203
- */
204
98
  setLock(name) {
205
- const lockSelect = this.lockSelect();
206
- if (lockSelect !== null)
207
- lockSelect.value = name === null ? '' : name;
208
- this.applyLock(name);
209
- }
210
- applyLock(name) {
211
99
  this.locked = name;
212
- if (name === null) {
213
- this.clear();
214
- }
215
- else {
216
- const g = this.nodeByName.get(name);
217
- if (g !== undefined)
218
- this.highlight(name, g);
219
- }
100
+ const select = document.getElementById('wp-lock');
101
+ if (select !== null)
102
+ select.value = name ?? '';
103
+ this.highlighter?.relight();
220
104
  this.filterCards();
221
105
  }
222
- /**
223
- * Every box opens the floating menu (the shared one both generated graphs use). "View Design" is
224
- * only built when that project actually HAS a committed design.html; the bottom item is always
225
- * Lock or Unlock, whichever applies to this box right now.
226
- */
227
- wireMenu() {
228
- const designs = new Map();
229
- for (const link of __DESIGN_LINKS__)
230
- designs.set(link.nodeId, link.href);
231
- WpNodeMenu.wire(this.svg, (name) => {
232
- const items = [];
233
- const href = designs.get(name);
234
- if (href !== undefined) {
235
- items.push(new WpNodeMenuItem('View Design', () => { window.open(href, '_blank'); }));
236
- }
237
- const locked = this.isLocked(name);
238
- items.push(new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', () => {
239
- this.setLock(locked ? null : name);
240
- }));
241
- return items;
242
- });
243
- }
244
- /**
245
- * Filter the responsibility cards to the locked box's chain by READING BACK the
246
- * `.wp-focus`/`.wp-neighbor` classes highlight() just set — so there is no second graph walk and
247
- * the cards can never disagree with the picture.
248
- */
249
106
  filterCards() {
250
- const lit = new Set();
251
- if (this.locked !== null) {
252
- this.svg.querySelectorAll('.wp-focus, .wp-neighbor').forEach((el) => {
253
- const title = el.querySelector('title');
254
- if (title !== null && title.textContent !== null)
255
- lit.add(title.textContent.trim());
256
- });
257
- }
107
+ const lit = this.locked === null ? this.retained : this.chain.nodes(this.locked);
258
108
  document.querySelectorAll('.wp-resp-card').forEach((card) => {
259
109
  const name = card.getAttribute('data-node');
260
- const visible = this.locked === null || (name !== null && lit.has(name));
261
- card.classList.toggle('wp-hidden', !visible);
262
- });
263
- }
264
- }
265
- /** Renders the DOT and hands the SVG to the highlighter. Reports a failure into the page, not just the console. */
266
- class GraphPage {
267
- render() {
268
- Viz.instance()
269
- .then((viz) => {
270
- const element = viz.renderSVGElement(__DOT__);
271
- const host = document.getElementById('graph');
272
- if (host === null)
273
- return;
274
- host.appendChild(element);
275
- new GraphHighlighter(element).wire();
276
- })
277
- // webpieces-disable no-any-unknown -- a promise rejection reason is untyped BY THE LANGUAGE (any value can be thrown), and this browser script cannot import the repo's toError helper; it is stringified, never dereferenced
278
- .catch((err) => {
279
- console.error(err);
280
- const host = document.getElementById('graph');
281
- if (host !== null)
282
- host.innerHTML = '<pre>' + String(err) + '</pre>';
110
+ card.classList.toggle('wp-hidden', name === null || !this.retained.has(name) || !lit.has(name));
283
111
  });
284
112
  }
285
113
  }
286
- new GraphPage().render();
114
+ new GraphPage(__RENDER_MODEL__).render();
287
115
  //# sourceMappingURL=graph-visualizer.client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for architecture/dependencies.html.\n *\n * graph-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded Graphviz DOT, and inlines the result into a <script> tag. So it\n * runs in a browser, never in node — but it is ordinary TypeScript, compiled in place by tsc exactly\n * like the package's bin entry points, and linted like every other file here.\n *\n * IT USED TO BE A COMMITTED .js ASSET, exempted from `no-js-files`, and its own header said why: so the\n * dim/highlight/lock logic \"can define ordinary browser functions without tripping the TypeScript lint\n * rules\". That is a file existing to dodge the rules — the same anti-pattern as the bin shims this repo\n * deleted, and the same cure applies. The logic is a CLASS now, which is what `no-function-outside-class`\n * was asking for all along; nothing needed a disable.\n *\n * Behaviour: after Viz renders the SVG, hovering a box dims the rest and lights its full\n * ancestor+descendant chain; the #wp-lock dropdown PINS one box's chain (the dim survives mouse-leave)\n * and filters the responsibilities cards below the graph to just that chain. \"All\" clears the lock.\n *\n * CLICKING a box opens the shared floating node menu (graph-node-menu.ts, the same menu every\n * project's design.html uses) — \"View Design\" only for a project that HAS a design.html, then\n * Lock/Unlock. Menu and dropdown are one lock: both go through setLock(), so each reflects the other.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/**\n * The class graph-visualizer.ts stamps on the invisible rank anchors, spacer bands and ordering\n * edges that pin each level to its own row. They are layout scaffolding, not architecture, so the\n * indexes below skip them: an anchor must never be walkable as a dependency, or hovering one box\n * would light boxes it has nothing to do with.\n *\n * Graphviz drops `style=invis` elements from its SVG entirely (verified against the emitted SVG),\n * so today nothing carrying this class reaches the DOM at all. The skip is the guarantee that the\n * scaffolding stays inert if that ever stops being true — the DOT is the only place that decides\n * what is scaffolding, and it says so with this class.\n */\nconst LAYOUT_CLASS = 'wp-layout';\n\n/** One traversal direction: the edges to light, and the nodes to walk on to. */\nclass Direction {\n constructor(\n readonly nodes: Map<string, Set<string>>,\n readonly edges: Map<string, Set<Element>>,\n ) {}\n}\n\n/**\n * Indexes the rendered SVG and drives the dim/highlight/lock interaction.\n *\n * The adjacency is DIRECTED and kept as two maps per axis: `in*` is what points AT a node (ancestors,\n * walked upward) and `out*` is what it points to (dependencies, walked downward). Highlighting walks\n * both, so a hovered box lights its entire chain in both directions rather than just its neighbours.\n */\nclass GraphHighlighter {\n private readonly nodeByName = new Map<string, SVGGElement>();\n private readonly inEdges = new Map<string, Set<Element>>();\n private readonly outEdges = new Map<string, Set<Element>>();\n private readonly inNodes = new Map<string, Set<string>>();\n private readonly outNodes = new Map<string, Set<string>>();\n\n /**\n * The box the dropdown pinned, or null. Hover still works on top of a lock — leaving a box\n * restores the LOCKED view instead of clearing, so the pinned subgraph stays visible while you\n * scroll down to its responsibility cards.\n */\n private locked: string | null = null;\n\n constructor(private readonly svg: SVGSVGElement) {}\n\n /** Index the SVG, then wire hover, the lock dropdown, and the per-node floating menu. */\n wire(): void {\n this.indexNodes();\n this.indexEdges();\n this.wireHover();\n this.wireLock();\n this.wireMenu();\n }\n\n private indexNodes(): void {\n this.svg.querySelectorAll('g.node').forEach((g: Element): void => {\n if (g.classList.contains(LAYOUT_CLASS)) return;\n const title = g.querySelector('title');\n if (title !== null && title.textContent !== null) {\n this.nodeByName.set(title.textContent.trim(), g as SVGGElement);\n }\n });\n }\n\n private indexEdges(): void {\n this.svg.querySelectorAll('g.edge').forEach((edge: Element): void => {\n if (edge.classList.contains(LAYOUT_CLASS)) return;\n const title = edge.querySelector('title');\n const text = title === null ? null : title.textContent;\n if (text === null) return;\n const idx = text.indexOf('->');\n if (idx < 0) return;\n const from = text.slice(0, idx).trim();\n const to = text.slice(idx + 2).trim();\n this.ensureEdges(this.outEdges, from).add(edge);\n this.ensureEdges(this.inEdges, to).add(edge);\n this.ensureNodes(this.outNodes, from).add(to);\n this.ensureNodes(this.inNodes, to).add(from);\n });\n }\n\n private ensureEdges(map: Map<string, Set<Element>>, key: string): Set<Element> {\n const existing = map.get(key);\n if (existing !== undefined) return existing;\n const created = new Set<Element>();\n map.set(key, created);\n return created;\n }\n\n private ensureNodes(map: Map<string, Set<string>>, key: string): Set<string> {\n const existing = map.get(key);\n if (existing !== undefined) return existing;\n const created = new Set<string>();\n map.set(key, created);\n return created;\n }\n\n private clear(): void {\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl').forEach((el: Element): void => {\n el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl');\n });\n }\n\n /**\n * Dim everything, then transitively light ancestors and descendants of `name`: edges reached get\n * `wp-hl`, boxes get `wp-neighbor`. The `visited` set is what makes a cyclic graph terminate.\n */\n private highlight(name: string, focusEl: SVGGElement): void {\n this.clear();\n this.svg.classList.add('wp-dim');\n focusEl.classList.add('wp-focus');\n const directions = [\n new Direction(this.inNodes, this.inEdges),\n new Direction(this.outNodes, this.outEdges),\n ];\n for (const dir of directions) this.walk(name, dir);\n }\n\n private walk(start: string, dir: Direction): void {\n const visited = new Set<string>();\n const stack = [start];\n while (stack.length > 0) {\n const cur = stack.pop() as string;\n const edges = dir.edges.get(cur);\n if (edges !== undefined) {\n edges.forEach((e: Element): void => { e.classList.add('wp-hl'); });\n }\n const next = dir.nodes.get(cur);\n if (next === undefined) continue;\n next.forEach((name: string): void => {\n if (visited.has(name)) return;\n visited.add(name);\n stack.push(name);\n const g = this.nodeByName.get(name);\n if (g !== undefined) g.classList.add('wp-neighbor');\n });\n }\n }\n\n /** Leaving a box restores the locked view rather than clearing it outright. */\n private relight(): void {\n if (this.locked === null) {\n this.clear();\n return;\n }\n const g = this.nodeByName.get(this.locked);\n if (g !== undefined) this.highlight(this.locked, g);\n }\n\n private wireHover(): void {\n this.nodeByName.forEach((g: SVGGElement, name: string): void => {\n g.addEventListener('mouseenter', (): void => { this.highlight(name, g); });\n g.addEventListener('mouseleave', (): void => { this.relight(); });\n });\n }\n\n private wireLock(): void {\n const lockSelect = this.lockSelect();\n if (lockSelect === null) return;\n lockSelect.addEventListener('change', (): void => {\n this.applyLock(lockSelect.value === '' ? null : lockSelect.value);\n });\n }\n\n private lockSelect(): HTMLSelectElement | null {\n return document.getElementById('wp-lock') as HTMLSelectElement | null;\n }\n\n /** True when `name` is the box the page is currently locked on — what the menu labels itself by. */\n isLocked(name: string): boolean {\n return this.locked === name;\n }\n\n /**\n * THE one lock entry point, so the dropdown and the node menu can never disagree: it sets the\n * dropdown's selection, re-highlights, and re-filters the cards whichever of the two asked.\n * `null` is \"All (no lock)\".\n */\n setLock(name: string | null): void {\n const lockSelect = this.lockSelect();\n if (lockSelect !== null) lockSelect.value = name === null ? '' : name;\n this.applyLock(name);\n }\n\n private applyLock(name: string | null): void {\n this.locked = name;\n if (name === null) {\n this.clear();\n } else {\n const g = this.nodeByName.get(name);\n if (g !== undefined) this.highlight(name, g);\n }\n this.filterCards();\n }\n\n /**\n * Every box opens the floating menu (the shared one both generated graphs use). \"View Design\" is\n * only built when that project actually HAS a committed design.html; the bottom item is always\n * Lock or Unlock, whichever applies to this box right now.\n */\n private wireMenu(): void {\n const designs = new Map<string, string>();\n for (const link of __DESIGN_LINKS__) designs.set(link.nodeId, link.href);\n WpNodeMenu.wire(this.svg, (name: string): WpNodeMenuItem[] => {\n const items: WpNodeMenuItem[] = [];\n const href = designs.get(name);\n if (href !== undefined) {\n items.push(new WpNodeMenuItem('View Design', (): void => { window.open(href, '_blank'); }));\n }\n const locked = this.isLocked(name);\n items.push(new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', (): void => {\n this.setLock(locked ? null : name);\n }));\n return items;\n });\n }\n\n /**\n * Filter the responsibility cards to the locked box's chain by READING BACK the\n * `.wp-focus`/`.wp-neighbor` classes highlight() just set — so there is no second graph walk and\n * the cards can never disagree with the picture.\n */\n private filterCards(): void {\n const lit = new Set<string>();\n if (this.locked !== null) {\n this.svg.querySelectorAll('.wp-focus, .wp-neighbor').forEach((el: Element): void => {\n const title = el.querySelector('title');\n if (title !== null && title.textContent !== null) lit.add(title.textContent.trim());\n });\n }\n document.querySelectorAll('.wp-resp-card').forEach((card: Element): void => {\n const name = card.getAttribute('data-node');\n const visible = this.locked === null || (name !== null && lit.has(name));\n card.classList.toggle('wp-hidden', !visible);\n });\n }\n}\n\n/** Renders the DOT and hands the SVG to the highlighter. Reports a failure into the page, not just the console. */\nclass GraphPage {\n render(): void {\n Viz.instance()\n .then((viz: VizInstance): void => {\n const element = viz.renderSVGElement(__DOT__);\n const host = document.getElementById('graph');\n if (host === null) return;\n host.appendChild(element);\n new GraphHighlighter(element).wire();\n })\n // webpieces-disable no-any-unknown -- a promise rejection reason is untyped BY THE LANGUAGE (any value can be thrown), and this browser script cannot import the repo's toError helper; it is stringified, never dereferenced\n .catch((err: unknown): void => {\n console.error(err);\n const host = document.getElementById('graph');\n if (host !== null) host.innerHTML = '<pre>' + String(err) + '</pre>';\n });\n }\n}\n\nnew GraphPage().render();\n"]}
1
+ {"version":3,"file":"graph-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Architecture interactions bind to each new SVG; logical Lock/Filter state belongs to the page. */\nclass GraphHighlighter {\n private readonly nodeByName = new Map<string, SVGGElement>();\n\n constructor(\n private readonly svg: SVGSVGElement,\n private readonly chain: WpGraphChain,\n private readonly model: RenderModelJson,\n private readonly page: GraphPage,\n ) {}\n\n wire(): void {\n this.svg.querySelectorAll<SVGGElement>('g.node').forEach((g) => {\n if (g.classList.contains('wp-layout')) return;\n const name = g.querySelector('title')?.textContent;\n if (name !== null && name !== undefined) this.nodeByName.set(name, g);\n });\n this.wireHover();\n this.wireMenu();\n this.relight();\n }\n\n private clear(): void {\n this.svg.classList.remove('wp-dim');\n this.svg.querySelectorAll('.wp-focus, .wp-neighbor, .wp-hl').forEach((el) => {\n el.classList.remove('wp-focus', 'wp-neighbor', 'wp-hl');\n });\n }\n\n private highlight(name: string): void {\n this.clear();\n const focus = this.nodeByName.get(name);\n if (focus === undefined) return;\n const lit = this.chain.nodes(name);\n this.svg.classList.add('wp-dim');\n focus.classList.add('wp-focus');\n for (const id of lit) this.nodeByName.get(id)?.classList.add('wp-neighbor');\n for (const edge of this.model.edges) {\n if (lit.has(edge.from) && lit.has(edge.to)) {\n this.svg.querySelector(`[id=\"${edge.id}\"]`)?.classList.add('wp-hl');\n }\n }\n }\n\n relight(): void {\n const locked = this.page.lockSelection();\n if (locked === null) this.clear();\n else this.highlight(locked);\n }\n\n private wireHover(): void {\n this.nodeByName.forEach((g, name) => {\n g.addEventListener('mouseenter', () => this.highlight(name));\n g.addEventListener('mouseleave', () => this.relight());\n });\n }\n\n private wireMenu(): void {\n const designs = new Map(__DESIGN_LINKS__.map((link) => [link.nodeId, link.href]));\n WpNodeMenu.wire(this.svg, (name) => {\n const items: WpNodeMenuItem[] = [];\n const href = designs.get(name);\n if (href !== undefined)\n items.push(new WpNodeMenuItem('View Design', () => window.open(href, '_blank')));\n const locked = this.page.lockSelection() === name;\n items.push(\n new WpNodeMenuItem(locked ? 'Unlock' : 'Lock', () =>\n this.page.setLock(locked ? null : name),\n ),\n );\n items.push(this.page.filterItem(name));\n return items;\n });\n }\n}\n\nclass GraphPage extends WpFilterPage {\n private highlighter: GraphHighlighter | null = null;\n\n protected override captureBinding(_svg: SVGSVGElement | null): () => void {\n const previous = this.highlighter;\n return (): void => { this.highlighter = previous; this.filterCards(); };\n }\n\n protected override wireControls(): void {\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n select?.addEventListener('change', () =>\n this.setLock(select.value === '' ? null : select.value),\n );\n }\n\n protected wireSvg(svg: SVGSVGElement): void {\n this.highlighter = new GraphHighlighter(svg, this.chain, this.model, this);\n this.highlighter.wire();\n this.filterCards();\n svg.querySelectorAll<SVGGElement>('g.node').forEach((node) => {\n if (node.querySelector('title')?.textContent === this.anchor)\n node.classList.add('wp-filter-anchor');\n });\n }\n\n lockSelection(): string | null {\n return this.locked;\n }\n\n setLock(name: string | null): void {\n this.locked = name;\n const select = document.getElementById('wp-lock') as HTMLSelectElement | null;\n if (select !== null) select.value = name ?? '';\n this.highlighter?.relight();\n this.filterCards();\n }\n\n private filterCards(): void {\n const lit = this.locked === null ? this.retained : this.chain.nodes(this.locked);\n document.querySelectorAll('.wp-resp-card').forEach((card) => {\n const name = card.getAttribute('data-node');\n card.classList.toggle(\n 'wp-hidden',\n name === null || !this.retained.has(name) || !lit.has(name),\n );\n });\n }\n}\n\nnew GraphPage(__RENDER_MODEL__).render();\n"]}
@@ -1,15 +1,5 @@
1
- /**
2
- * Graph Visualizer
3
- *
4
- * Generates visual representations of the architecture graph:
5
- * - DOT format (for Graphviz)
6
- * - Interactive HTML (using viz.js)
7
- *
8
- * All behavior lives on the injectable GraphVisualizer class so webpieces DI +
9
- * @DocumentDesign can wire it — module-scope functions are a dead end the DI
10
- * graph can't reach.
11
- */
12
1
  import type { EnhancedGraph } from './graph-sorter';
2
+ import { GraphRenderModel } from './graph-render-model';
13
3
  /**
14
4
  * One node's design page, handed to the browser so the node menu can offer "View Design" for the
15
5
  * boxes that HAVE one and omit the item entirely for the boxes that do not.
@@ -31,8 +21,8 @@ export declare class VisualizationPaths {
31
21
  * client — the inliner is a blind split/join, so a second occurrence (in a comment, say) would be
32
22
  * replaced by the entire DOT too, bloating every generated page.
33
23
  */
34
- export declare const CLIENT_DOT_PLACEHOLDER: string;
35
- /** Same contract as CLIENT_DOT_PLACEHOLDER, for the node → design.html links the menu offers. */
24
+ export declare const CLIENT_MODEL_PLACEHOLDER: string;
25
+ /** Same contract as CLIENT_MODEL_PLACEHOLDER, for the node → design.html links the menu offers. */
36
26
  export declare const CLIENT_DESIGN_LINKS_PLACEHOLDER: string;
37
27
  /**
38
28
  * Read a compiled browser client sitting beside this file.
@@ -44,6 +34,8 @@ export declare const CLIENT_DESIGN_LINKS_PLACEHOLDER: string;
44
34
  export declare function readCompiledClient(name: string): string;
45
35
  export declare class GraphVisualizer {
46
36
  private readonly clientJs;
37
+ private readonly filterJs;
38
+ private readonly filterAssets;
47
39
  private readonly snapshot;
48
40
  private readonly names;
49
41
  private readonly responsibilities;
@@ -57,7 +49,7 @@ export declare class GraphVisualizer {
57
49
  * a source checkout (the source there is .client.ts). A unit test running from source supplies the
58
50
  * text itself rather than requiring the package to have been built first.
59
51
  */
60
- constructor(clientJs?: () => string);
52
+ constructor(clientJs?: () => string, filterJs?: () => string);
61
53
  /**
62
54
  * A project tagged `drawOnGraph:false` is hidden from the rendered graph —
63
55
  * its node, its rank placement, its dropdown option, its responsibilities
@@ -122,6 +114,7 @@ export declare class GraphVisualizer {
122
114
  * Generate Graphviz DOT format from the graph
123
115
  */
124
116
  generateDot(graph: EnhancedGraph, title?: string): string;
117
+ generateRenderModel(graph: EnhancedGraph, title?: string): GraphRenderModel;
125
118
  /**
126
119
  * Refuse to draw a graph that would render something false.
127
120
  *
@@ -144,7 +137,7 @@ export declare class GraphVisualizer {
144
137
  /**
145
138
  * Generate interactive HTML with embedded SVG using viz.js
146
139
  */
147
- generateHTML(dot: string, links: DesignLink[], title?: string, lockControl?: string, responsibilitiesHtml?: string): string;
140
+ generateHTML(model: GraphRenderModel, links: DesignLink[], title?: string, lockControl?: string, responsibilitiesHtml?: string): string;
148
141
  /**
149
142
  * The lock control (a single-select dropdown, rendered below the legend).
150
143
  * Picking a module LOCKS the graph into that box's hover view — its full