@webpieces/nx-webpieces-rules 0.4.919 → 0.4.921

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 (49) hide show
  1. package/package.json +7 -7
  2. package/src/executors/generate/executor.d.ts +7 -0
  3. package/src/executors/generate/executor.js +13 -5
  4. package/src/executors/generate/executor.js.map +1 -1
  5. package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -20
  6. package/src/executors/validate-architecture-unchanged/executor.js +2 -45
  7. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  8. package/src/executors/visualize/executor.d.ts +8 -1
  9. package/src/executors/visualize/executor.js +11 -3
  10. package/src/executors/visualize/executor.js.map +1 -1
  11. package/src/executors/visualize-runtime/executor.js +2 -1
  12. package/src/executors/visualize-runtime/executor.js.map +1 -1
  13. package/src/executors/visualize-runtime-goal/executor.js +2 -1
  14. package/src/executors/visualize-runtime-goal/executor.js.map +1 -1
  15. package/src/lib/current-architecture.d.ts +26 -0
  16. package/src/lib/current-architecture.js +56 -0
  17. package/src/lib/current-architecture.js.map +1 -0
  18. package/src/lib/graph-color-modes.d.ts +29 -11
  19. package/src/lib/graph-color-modes.js +40 -14
  20. package/src/lib/graph-color-modes.js.map +1 -1
  21. package/src/lib/graph-edge-diff.d.ts +115 -0
  22. package/src/lib/graph-edge-diff.js +263 -0
  23. package/src/lib/graph-edge-diff.js.map +1 -0
  24. package/src/lib/graph-impact-reasons.d.ts +160 -0
  25. package/src/lib/graph-impact-reasons.js +412 -0
  26. package/src/lib/graph-impact-reasons.js.map +1 -0
  27. package/src/lib/graph-impact.d.ts +100 -48
  28. package/src/lib/graph-impact.js +176 -105
  29. package/src/lib/graph-impact.js.map +1 -1
  30. package/src/lib/graph-legend.d.ts +9 -2
  31. package/src/lib/graph-legend.js +31 -4
  32. package/src/lib/graph-legend.js.map +1 -1
  33. package/src/lib/graph-mode-state.client.d.ts +96 -2
  34. package/src/lib/graph-mode-state.client.js +343 -3
  35. package/src/lib/graph-mode-state.client.js.map +1 -1
  36. package/src/lib/graph-navigation.js +4 -2
  37. package/src/lib/graph-navigation.js.map +1 -1
  38. package/src/lib/graph-page-shell.d.ts +5 -3
  39. package/src/lib/graph-page-shell.js +7 -4
  40. package/src/lib/graph-page-shell.js.map +1 -1
  41. package/src/lib/graph-visualizer.client.d.ts +4 -0
  42. package/src/lib/graph-visualizer.client.js +36 -9
  43. package/src/lib/graph-visualizer.client.js.map +1 -1
  44. package/src/lib/runtime-html-page.js +2 -2
  45. package/src/lib/runtime-html-page.js.map +1 -1
  46. package/src/lib/runtime-viewer-adapter.js +1 -1
  47. package/src/lib/runtime-viewer-adapter.js.map +1 -1
  48. package/src/native-actions.js +1 -1
  49. package/src/native-actions.js.map +1 -1
@@ -118,17 +118,66 @@ class WpModeState {
118
118
  }
119
119
  return true;
120
120
  }
121
- /** Which impact shade a box gets, from the ACTIVE scan. */
121
+ /** Which impact shade a box gets, from the ACTIVE scan: WHY nx included it (#1264). */
122
122
  status(id) {
123
123
  const impact = this.impact;
124
124
  if (impact?.touched.includes(id))
125
125
  return 'touched';
126
- if (impact?.affected.includes(id))
127
- return 'affected';
126
+ if (impact?.dependents.includes(id))
127
+ return 'dependent';
128
+ if (impact?.implicit.includes(id))
129
+ return 'implicit';
130
+ if (impact?.rootFile.includes(id))
131
+ return 'rootFile';
132
+ if (impact?.unexplained.includes(id))
133
+ return 'unexplained';
128
134
  if (impact?.buildInputs.includes(id))
129
135
  return 'buildInput';
130
136
  return 'untouched';
131
137
  }
138
+ /** Every affected node id of the active scan, whatever its reason. */
139
+ affected() {
140
+ const impact = this.impact;
141
+ if (impact === null)
142
+ return [];
143
+ return [...impact.touched, ...impact.dependents, ...impact.implicit, ...impact.rootFile, ...impact.unexplained];
144
+ }
145
+ /** The edges gained and lost since the fork point that THIS page draws: build deps or runtime calls. */
146
+ edgeChanges() {
147
+ const edges = window.__WP_IMPACT__?.edges;
148
+ if (edges === undefined)
149
+ return [];
150
+ return this.viewer === 'runtime' ? edges.runtime : edges.dependency;
151
+ }
152
+ /** '' when this page's edge changes were computed (even if none), else why the page shows none. */
153
+ edgeReason() {
154
+ const edges = window.__WP_IMPACT__?.edges;
155
+ if (edges === undefined)
156
+ return 'No edge changes here. Run pnpm arch:visualize to compute them.';
157
+ return this.viewer === 'runtime' ? edges.runtimeReason : edges.dependencyReason;
158
+ }
159
+ /** Every box at either end of a changed edge: the Filter's "Edge changes only". */
160
+ edgeEnds() {
161
+ const ends = new Set();
162
+ for (const change of this.edgeChanges()) {
163
+ ends.add(change.from);
164
+ ends.add(change.to);
165
+ }
166
+ return ends;
167
+ }
168
+ /** "Edges since abc1234: 1 gained · 1 lost." — or why there are none to show. */
169
+ edgeNote() {
170
+ const reason = this.edgeReason();
171
+ if (reason !== '')
172
+ return `${reason}.`;
173
+ const changes = this.edgeChanges();
174
+ const gained = changes.filter((change) => change.change === 'gained').length;
175
+ return `Edges since ${window.__WP_IMPACT__?.edges.base ?? ''}: ${gained} gained · ${changes.length - gained} lost.`;
176
+ }
177
+ /** Why `id` is in the active scan's build; null when it is not affected. */
178
+ reason(id) {
179
+ return this.impact?.reasons.find((reason) => reason.project === id) ?? null;
180
+ }
132
181
  initial() {
133
182
  const hashed = location.hash.slice(1);
134
183
  if (this.available(hashed))
@@ -151,4 +200,295 @@ class WpModeState {
151
200
  }
152
201
  }
153
202
  }
203
+ /**
204
+ * What the "why" panel says about one affected project (#1264): never a reason the graph cannot show.
205
+ * Pure text, so a spec reads it without a browser; GraphPage puts it in a panel.
206
+ */
207
+ class WpImpactWhy {
208
+ static TITLES = {
209
+ touched: 'Changed',
210
+ dependent: 'Dependent',
211
+ implicit: 'Implicitly touched',
212
+ rootFile: 'Root-file touched',
213
+ unexplained: 'Unexplained',
214
+ };
215
+ /**
216
+ * `8 changed · 3 dependents · 22 implicitly touched (runtime-wiring-check inputs) · 47 dependencies`
217
+ * — the same split ImpactCounts (graph-impact.ts) prints on the console.
218
+ */
219
+ counts(impact) {
220
+ const parts = [`${impact.touched.length} changed`, `${impact.dependents.length} dependents`];
221
+ if (impact.implicit.length > 0) {
222
+ const targets = [
223
+ ...new Set(impact.reasons
224
+ .filter((reason) => reason.kind === 'implicit' && reason.target !== '')
225
+ .map((reason) => reason.target)),
226
+ ].sort();
227
+ parts.push(`${impact.implicit.length} implicitly touched${targets.length === 0 ? '' : ` (${targets.join(', ')} inputs)`}`);
228
+ }
229
+ if (impact.rootFile.length > 0)
230
+ parts.push(`${impact.rootFile.length} root-file touched`);
231
+ if (impact.unexplained.length > 0)
232
+ parts.push(`${impact.unexplained.length} unexplained`);
233
+ parts.push(`${impact.buildInputs.length} dependencies`);
234
+ return parts.join(' · ');
235
+ }
236
+ title(reason) {
237
+ return `${WpImpactWhy.TITLES[reason.kind] ?? reason.kind}: ${reason.project}`;
238
+ }
239
+ lines(reason) {
240
+ if (reason.kind === 'touched') {
241
+ const owns = reason.files.length === 1 ? 'this changed file' : `these ${reason.files.length} changed files`;
242
+ return [`It owns ${owns}:`, ...reason.files];
243
+ }
244
+ if (reason.kind === 'dependent')
245
+ return [
246
+ 'It uses a changed or touched project through these dependency edges, so its tests and build re-run:',
247
+ reason.path.join(' -> '),
248
+ ];
249
+ if (reason.kind === 'implicit')
250
+ return [
251
+ reason.target === '' ? reason.detail : `${reason.target} input ${reason.input} matched ${reason.files.join(', ')}`,
252
+ 'nx marks every project whose target input glob matches a changed file. It uses no changed project.',
253
+ ];
254
+ if (reason.kind === 'rootFile')
255
+ return [
256
+ reason.detail,
257
+ ...(reason.path.length > 0 ? [reason.path.join(' -> ')] : []),
258
+ 'nx marked it for a workspace root file; it uses no changed project.',
259
+ ];
260
+ return [reason.detail];
261
+ }
262
+ }
263
+ /**
264
+ * The "why is this in the build?" panel (#1264): opened from an affected box's menu in Impact mode.
265
+ * Text nodes only, so no project name is parsed as HTML.
266
+ */
267
+ class ImpactWhyPanel {
268
+ panel = document.createElement('div');
269
+ text = new WpImpactWhy();
270
+ anchor = null;
271
+ constructor() {
272
+ this.panel.className = 'wp-impact-why';
273
+ this.panel.hidden = true;
274
+ this.panel.setAttribute('role', 'dialog');
275
+ this.panel.setAttribute('aria-label', 'Why this project is in the build');
276
+ document.body.append(this.panel);
277
+ document.addEventListener('keydown', (ev) => {
278
+ if (ev.key !== 'Escape' || this.panel.hidden)
279
+ return;
280
+ ev.stopPropagation();
281
+ this.close();
282
+ }, true);
283
+ document.addEventListener('click', (ev) => {
284
+ const target = ev.target;
285
+ if (target instanceof Node && !this.panel.contains(target) && !this.anchor?.contains(target))
286
+ this.close();
287
+ });
288
+ }
289
+ open(node, reason) {
290
+ this.show(node, this.text.title(reason), this.text.lines(reason));
291
+ }
292
+ /** Any heading and lines, anchored under `node` (a box, or a changed edge). */
293
+ show(node, title, lines) {
294
+ this.anchor = node;
295
+ const heading = document.createElement('strong');
296
+ heading.textContent = title;
297
+ const list = document.createElement('ul');
298
+ for (const line of lines) {
299
+ const item = document.createElement('li');
300
+ item.textContent = line;
301
+ list.append(item);
302
+ }
303
+ const close = document.createElement('button');
304
+ close.type = 'button';
305
+ close.textContent = 'Close';
306
+ close.addEventListener('click', () => this.close());
307
+ this.panel.replaceChildren(heading, list, close);
308
+ this.panel.hidden = false;
309
+ const box = node.getBoundingClientRect();
310
+ this.panel.style.left = `${Math.max(12, Math.min(box.left, innerWidth - this.panel.offsetWidth - 12))}px`;
311
+ this.panel.style.top = `${Math.max(12, Math.min(box.bottom + 6, innerHeight - this.panel.offsetHeight - 12))}px`;
312
+ close.focus({ preventScroll: true });
313
+ }
314
+ close() {
315
+ if (this.panel.hidden)
316
+ return;
317
+ this.panel.hidden = true;
318
+ const anchor = this.anchor;
319
+ this.anchor = null;
320
+ if (anchor?.isConnected)
321
+ anchor.focus({ preventScroll: true });
322
+ }
323
+ }
324
+ /** A changed edge the saved page does not draw: added to the Impact overlay. */
325
+ class WpOverlayEdge {
326
+ id;
327
+ from;
328
+ to;
329
+ dot;
330
+ productDot = null;
331
+ constructor(id, from, to, dot) {
332
+ this.id = id;
333
+ this.from = from;
334
+ this.to = to;
335
+ this.dot = dot;
336
+ }
337
+ }
338
+ /** The page's own model with the overlay's edges: same boxes, levels and frame. */
339
+ class WpOverlayModel {
340
+ edges;
341
+ viewer;
342
+ nodes;
343
+ bands;
344
+ fullDot;
345
+ header;
346
+ footer;
347
+ comparison;
348
+ constructor(base, edges) {
349
+ this.edges = edges;
350
+ this.viewer = base.viewer;
351
+ this.nodes = base.nodes;
352
+ this.bands = base.bands;
353
+ this.fullDot = base.fullDot;
354
+ this.header = base.header;
355
+ this.footer = base.footer;
356
+ this.comparison = base.comparison ?? null;
357
+ }
358
+ }
359
+ /**
360
+ * The edges this branch gained or lost since its fork point, drawn over the page in Impact mode
361
+ * (#1264). A changed edge the page already draws is restyled; one it does not (the page is the saved
362
+ * snapshot, the diff is against the LIVE graph) is added: a gained edge solid red and bold, a lost one
363
+ * a red dashed ghost. Every changed edge is clickable, and a box whose only change is an edge gets a
364
+ * red badge.
365
+ */
366
+ class WpEdgeOverlay {
367
+ modes;
368
+ static RED = '#d32f2f';
369
+ static PREFIX = 'wp-edge-change-';
370
+ /** pair key → that pair's changes; null until first asked. */
371
+ pairs = null;
372
+ /** Rendered edge id → the changes it stands for. */
373
+ byEdgeId = new Map();
374
+ overlay = null;
375
+ constructor(modes) {
376
+ this.modes = modes;
377
+ }
378
+ key(from, to) {
379
+ return `${from}\u0000${to}`;
380
+ }
381
+ grouped() {
382
+ if (this.pairs !== null)
383
+ return this.pairs;
384
+ this.pairs = new Map();
385
+ for (const change of this.modes.edgeChanges()) {
386
+ const key = this.key(change.from, change.to);
387
+ this.pairs.set(key, [...(this.pairs.get(key) ?? []), change]);
388
+ }
389
+ return this.pairs;
390
+ }
391
+ any() {
392
+ return this.modes.edgeChanges().length > 0;
393
+ }
394
+ /** `base` plus a ghost statement for every changed edge it does not draw (both ends must be boxes). */
395
+ model(base) {
396
+ if (this.overlay !== null)
397
+ return this.overlay;
398
+ const ids = new Set(base.nodes.map((node) => node.id));
399
+ const drawn = new Set(base.edges.map((edge) => this.key(edge.from, edge.to)));
400
+ const extra = [];
401
+ for (const key of this.grouped().keys()) {
402
+ const changes = this.grouped().get(key);
403
+ const from = changes[0].from;
404
+ const to = changes[0].to;
405
+ if (drawn.has(key) || !ids.has(from) || !ids.has(to))
406
+ continue;
407
+ const id = `${WpEdgeOverlay.PREFIX}${extra.length}`;
408
+ const dot = ` "${this.esc(from)}" -> "${this.esc(to)}" [id="${id}", constraint=false${this.attrs(changes)}];\n`;
409
+ this.byEdgeId.set(id, changes);
410
+ extra.push(new WpOverlayEdge(id, from, to, dot));
411
+ }
412
+ this.overlay = new WpOverlayModel(base, [...base.edges, ...extra]);
413
+ return this.overlay;
414
+ }
415
+ /** A drawn edge's statement restyled when it changed; null when it did not. */
416
+ edgeDot(edge) {
417
+ if (edge.id.startsWith(WpEdgeOverlay.PREFIX))
418
+ return edge.dot;
419
+ const changes = this.grouped().get(this.key(edge.from, edge.to));
420
+ if (changes === undefined)
421
+ return null;
422
+ this.byEdgeId.set(edge.id, changes);
423
+ return `${edge.dot.slice(0, -2)} [${this.attrs(changes).slice(2)}];\n`;
424
+ }
425
+ /** Gained (any API new) is solid, bold red; otherwise a lost edge is a red dashed ghost. */
426
+ attrs(changes) {
427
+ const gained = changes.some((change) => change.change === 'gained');
428
+ const tooltip = this.esc(changes.map((change) => change.text).join('; '));
429
+ return `, color="${WpEdgeOverlay.RED}", fontcolor="${WpEdgeOverlay.RED}", penwidth=${gained ? '3' : '2'}, style="${gained ? 'bold' : 'dashed'}", tooltip="${tooltip}"`;
430
+ }
431
+ esc(text) {
432
+ return text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
433
+ }
434
+ /** The lines for one box's edge changes ("new on this branch: …", "… no longer calls …"). */
435
+ nodeLines(id) {
436
+ return this.modes
437
+ .edgeChanges()
438
+ .filter((change) => change.from === id || change.to === id)
439
+ .map((change) => change.text);
440
+ }
441
+ /** Every changed edge clickable; a red badge on each box whose ONLY change is an edge. */
442
+ decorate(svg, panel) {
443
+ for (const id of this.byEdgeId.keys()) {
444
+ const edge = svg.querySelector(`g.edge[id="${id}"]`);
445
+ const changes = this.byEdgeId.get(id);
446
+ if (edge === null)
447
+ continue;
448
+ edge.classList.add('wp-edge-change');
449
+ edge.setAttribute('tabindex', '0');
450
+ edge.setAttribute('role', 'button');
451
+ const title = changes.some((change) => change.change === 'gained') ? 'Gained edge' : 'Lost edge';
452
+ const show = (ev) => {
453
+ ev.stopPropagation();
454
+ panel.show(edge, title, changes.map((change) => change.text));
455
+ };
456
+ edge.addEventListener('click', show);
457
+ edge.addEventListener('keydown', (ev) => {
458
+ if (ev.key === 'Enter' || ev.key === ' ')
459
+ show(ev);
460
+ });
461
+ }
462
+ const affected = new Set(this.modes.affected());
463
+ const ends = this.modes.edgeEnds();
464
+ svg.querySelectorAll('g.node').forEach((node) => {
465
+ const id = node.querySelector('title')?.textContent ?? '';
466
+ if (!ends.has(id) || affected.has(id))
467
+ return;
468
+ node.append(this.badge(node));
469
+ });
470
+ }
471
+ badge(node) {
472
+ const ns = 'http://www.w3.org/2000/svg';
473
+ const box = node.getBBox();
474
+ const group = document.createElementNS(ns, 'g');
475
+ group.setAttribute('class', 'wp-edge-badge');
476
+ const circle = document.createElementNS(ns, 'circle');
477
+ circle.setAttribute('cx', String(box.x + box.width - 2));
478
+ circle.setAttribute('cy', String(box.y + 2));
479
+ circle.setAttribute('r', '8');
480
+ circle.setAttribute('fill', WpEdgeOverlay.RED);
481
+ const text = document.createElementNS(ns, 'text');
482
+ text.setAttribute('x', String(box.x + box.width - 2));
483
+ text.setAttribute('y', String(box.y + 6));
484
+ text.setAttribute('text-anchor', 'middle');
485
+ text.setAttribute('font-size', '12');
486
+ text.setAttribute('fill', '#ffffff');
487
+ text.textContent = '±';
488
+ const hint = document.createElementNS(ns, 'title');
489
+ hint.textContent = 'Only its edges changed on this branch';
490
+ group.append(circle, text, hint);
491
+ return group;
492
+ }
493
+ }
154
494
  //# sourceMappingURL=graph-mode-state.client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"graph-mode-state.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-mode-state.client.ts"],"names":[],"mappings":"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","sourcesContent":["class WpModeState {\n /** `goal` (Goal vs Current, #1239) exists on runtime pages only; the shell renders it nowhere else. */\n static readonly MODES = ['runtime', 'architecture', 'impact', 'product', 'goal'];\n static readonly NAMES = ['Runtime', 'Architecture', 'Impact', 'Product', 'Goal vs Current'];\n static readonly SUBTITLES = [\n 'where the code can run',\n 'servers · clients · APIs',\n 'what changed',\n 'which products it belongs to',\n 'Current → Goal proposal',\n ];\n readonly scans: ImpactJson[] = window.__WP_IMPACT__?.scans ?? [];\n kind = '';\n mode = 'runtime';\n\n /**\n * @param hasProducts some box belongs to a product, so the Product mode has something to show\n * @param goalUnavailable '' when Goal vs Current can be shown, else why not (#1239)\n */\n constructor(\n private readonly hasProducts: boolean,\n private readonly viewer: string,\n private readonly goalUnavailable: string,\n ) {\n this.kind = this.initialKind();\n this.mode = this.initial();\n }\n\n private storageKey(kind: string): string {\n return `wp-${this.viewer}-graph-${kind}`;\n }\n\n /** '' when Product can be shown, else why not. */\n productReason(): string {\n return this.hasProducts\n ? ''\n : 'No products here: tag each project product:<name> in its project.json, then regenerate.';\n }\n\n /** '' when Goal vs Current can be shown, else why not (a missing or unusable input, never \"no differences\"). */\n goalReason(): string {\n return this.goalUnavailable;\n }\n\n /** The active scan; null when the page has no Impact data. */\n get impact(): ImpactJson | null {\n return this.scans.find((scan: ImpactJson): boolean => scan.kind === this.kind) ?? null;\n }\n\n /** '' when Impact can be shown, else the one line the drawer and the Filter popover say instead. */\n impactReason(): string {\n const impact = this.impact;\n if (\n impact === null &&\n this.viewer === 'runtime' &&\n document.body.dataset['wpProjectFacts'] === 'false'\n )\n return 'No saved project facts for Impact. Run pnpm nx run architecture:generate, then pnpm arch:visualize-runtime.';\n if (impact === null)\n return `No impact data here. Run pnpm arch:visualize${this.viewer === 'runtime' ? '-runtime' : ''} to compute it — no full regenerate needed.`;\n return impact.available ? '' : `Impact unavailable (${impact.label}): ${impact.reason}.`;\n }\n\n /** Switch the active scan; false (and nothing changes) for a missing or unavailable one. */\n setKind(kind: string): boolean {\n if (!this.kindAvailable(kind)) return false;\n this.kind = kind;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store must not break the page\n try {\n localStorage.setItem(this.storageKey('impact-kind'), kind);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n return true;\n }\n\n kindAvailable(kind: string): boolean {\n return this.scans.some((scan: ImpactJson): boolean => scan.kind === kind && scan.available);\n }\n\n private initialKind(): string {\n const stored = this.read(this.storageKey('impact-kind'));\n if (stored !== null && this.kindAvailable(stored)) return stored;\n const preferred = window.__WP_IMPACT__?.defaultKind ?? '';\n if (this.scans.some((scan: ImpactJson): boolean => scan.kind === preferred))\n return preferred;\n return this.scans[0]?.kind ?? '';\n }\n\n available(mode: string): boolean {\n if (!WpModeState.MODES.includes(mode)) return false;\n if (mode === 'impact') return this.impactReason() === '';\n if (mode === 'product') return this.productReason() === '';\n if (mode === 'goal') return this.goalReason() === '';\n return true;\n }\n\n name(mode: string): string {\n return WpModeState.NAMES[WpModeState.MODES.indexOf(mode)] ?? mode;\n }\n\n subtitle(mode: string): string {\n return WpModeState.SUBTITLES[WpModeState.MODES.indexOf(mode)] ?? '';\n }\n\n /** False (and nothing changes) for an unknown or unavailable mode. */\n set(mode: string): boolean {\n if (!this.available(mode)) return false;\n this.mode = mode;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store must not break the page\n try {\n localStorage.setItem(this.storageKey('mode'), mode);\n if (location.hash !== `#${mode}`) history.replaceState(null, '', `#${mode}`);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n return true;\n }\n\n /** Which impact shade a box gets, from the ACTIVE scan. */\n status(id: string): 'touched' | 'affected' | 'buildInput' | 'untouched' {\n const impact = this.impact;\n if (impact?.touched.includes(id)) return 'touched';\n if (impact?.affected.includes(id)) return 'affected';\n if (impact?.buildInputs.includes(id)) return 'buildInput';\n return 'untouched';\n }\n\n private initial(): string {\n const hashed = location.hash.slice(1);\n if (this.available(hashed)) return hashed;\n const stored = this.read(this.storageKey('mode'));\n if (stored !== null && this.available(stored)) return stored;\n return 'runtime';\n }\n\n private read(key: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store reads as \"nothing stored\"\n try {\n return localStorage.getItem(key);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n return null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"graph-mode-state.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-mode-state.client.ts"],"names":[],"mappings":"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","sourcesContent":["class WpModeState {\n /** `goal` (Goal vs Current, #1239) exists on runtime pages only; the shell renders it nowhere else. */\n static readonly MODES = ['runtime', 'architecture', 'impact', 'product', 'goal'];\n static readonly NAMES = ['Runtime', 'Architecture', 'Impact', 'Product', 'Goal vs Current'];\n static readonly SUBTITLES = [\n 'where the code can run',\n 'servers · clients · APIs',\n 'what changed',\n 'which products it belongs to',\n 'Current → Goal proposal',\n ];\n readonly scans: ImpactJson[] = window.__WP_IMPACT__?.scans ?? [];\n kind = '';\n mode = 'runtime';\n\n /**\n * @param hasProducts some box belongs to a product, so the Product mode has something to show\n * @param goalUnavailable '' when Goal vs Current can be shown, else why not (#1239)\n */\n constructor(\n private readonly hasProducts: boolean,\n private readonly viewer: string,\n private readonly goalUnavailable: string,\n ) {\n this.kind = this.initialKind();\n this.mode = this.initial();\n }\n\n private storageKey(kind: string): string {\n return `wp-${this.viewer}-graph-${kind}`;\n }\n\n /** '' when Product can be shown, else why not. */\n productReason(): string {\n return this.hasProducts\n ? ''\n : 'No products here: tag each project product:<name> in its project.json, then regenerate.';\n }\n\n /** '' when Goal vs Current can be shown, else why not (a missing or unusable input, never \"no differences\"). */\n goalReason(): string {\n return this.goalUnavailable;\n }\n\n /** The active scan; null when the page has no Impact data. */\n get impact(): ImpactJson | null {\n return this.scans.find((scan: ImpactJson): boolean => scan.kind === this.kind) ?? null;\n }\n\n /** '' when Impact can be shown, else the one line the drawer and the Filter popover say instead. */\n impactReason(): string {\n const impact = this.impact;\n if (\n impact === null &&\n this.viewer === 'runtime' &&\n document.body.dataset['wpProjectFacts'] === 'false'\n )\n return 'No saved project facts for Impact. Run pnpm nx run architecture:generate, then pnpm arch:visualize-runtime.';\n if (impact === null)\n return `No impact data here. Run pnpm arch:visualize${this.viewer === 'runtime' ? '-runtime' : ''} to compute it — no full regenerate needed.`;\n return impact.available ? '' : `Impact unavailable (${impact.label}): ${impact.reason}.`;\n }\n\n /** Switch the active scan; false (and nothing changes) for a missing or unavailable one. */\n setKind(kind: string): boolean {\n if (!this.kindAvailable(kind)) return false;\n this.kind = kind;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store must not break the page\n try {\n localStorage.setItem(this.storageKey('impact-kind'), kind);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n return true;\n }\n\n kindAvailable(kind: string): boolean {\n return this.scans.some((scan: ImpactJson): boolean => scan.kind === kind && scan.available);\n }\n\n private initialKind(): string {\n const stored = this.read(this.storageKey('impact-kind'));\n if (stored !== null && this.kindAvailable(stored)) return stored;\n const preferred = window.__WP_IMPACT__?.defaultKind ?? '';\n if (this.scans.some((scan: ImpactJson): boolean => scan.kind === preferred))\n return preferred;\n return this.scans[0]?.kind ?? '';\n }\n\n available(mode: string): boolean {\n if (!WpModeState.MODES.includes(mode)) return false;\n if (mode === 'impact') return this.impactReason() === '';\n if (mode === 'product') return this.productReason() === '';\n if (mode === 'goal') return this.goalReason() === '';\n return true;\n }\n\n name(mode: string): string {\n return WpModeState.NAMES[WpModeState.MODES.indexOf(mode)] ?? mode;\n }\n\n subtitle(mode: string): string {\n return WpModeState.SUBTITLES[WpModeState.MODES.indexOf(mode)] ?? '';\n }\n\n /** False (and nothing changes) for an unknown or unavailable mode. */\n set(mode: string): boolean {\n if (!this.available(mode)) return false;\n this.mode = mode;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store must not break the page\n try {\n localStorage.setItem(this.storageKey('mode'), mode);\n if (location.hash !== `#${mode}`) history.replaceState(null, '', `#${mode}`);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n return true;\n }\n\n /** Which impact shade a box gets, from the ACTIVE scan: WHY nx included it (#1264). */\n status(id: string): 'touched' | 'dependent' | 'implicit' | 'rootFile' | 'unexplained' | 'buildInput' | 'untouched' {\n const impact = this.impact;\n if (impact?.touched.includes(id)) return 'touched';\n if (impact?.dependents.includes(id)) return 'dependent';\n if (impact?.implicit.includes(id)) return 'implicit';\n if (impact?.rootFile.includes(id)) return 'rootFile';\n if (impact?.unexplained.includes(id)) return 'unexplained';\n if (impact?.buildInputs.includes(id)) return 'buildInput';\n return 'untouched';\n }\n\n /** Every affected node id of the active scan, whatever its reason. */\n affected(): string[] {\n const impact = this.impact;\n if (impact === null) return [];\n return [...impact.touched, ...impact.dependents, ...impact.implicit, ...impact.rootFile, ...impact.unexplained];\n }\n\n /** The edges gained and lost since the fork point that THIS page draws: build deps or runtime calls. */\n edgeChanges(): EdgeChangeJson[] {\n const edges = window.__WP_IMPACT__?.edges;\n if (edges === undefined) return [];\n return this.viewer === 'runtime' ? edges.runtime : edges.dependency;\n }\n\n /** '' when this page's edge changes were computed (even if none), else why the page shows none. */\n edgeReason(): string {\n const edges = window.__WP_IMPACT__?.edges;\n if (edges === undefined) return 'No edge changes here. Run pnpm arch:visualize to compute them.';\n return this.viewer === 'runtime' ? edges.runtimeReason : edges.dependencyReason;\n }\n\n /** Every box at either end of a changed edge: the Filter's \"Edge changes only\". */\n edgeEnds(): Set<string> {\n const ends = new Set<string>();\n for (const change of this.edgeChanges()) {\n ends.add(change.from);\n ends.add(change.to);\n }\n return ends;\n }\n\n /** \"Edges since abc1234: 1 gained · 1 lost.\" — or why there are none to show. */\n edgeNote(): string {\n const reason = this.edgeReason();\n if (reason !== '') return `${reason}.`;\n const changes = this.edgeChanges();\n const gained = changes.filter((change: EdgeChangeJson): boolean => change.change === 'gained').length;\n return `Edges since ${window.__WP_IMPACT__?.edges.base ?? ''}: ${gained} gained · ${changes.length - gained} lost.`;\n }\n\n /** Why `id` is in the active scan's build; null when it is not affected. */\n reason(id: string): AffectedReasonJson | null {\n return this.impact?.reasons.find((reason: AffectedReasonJson): boolean => reason.project === id) ?? null;\n }\n\n private initial(): string {\n const hashed = location.hash.slice(1);\n if (this.available(hashed)) return hashed;\n const stored = this.read(this.storageKey('mode'));\n if (stored !== null && this.available(stored)) return stored;\n return 'runtime';\n }\n\n private read(key: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- storage is a per-viewer convenience; a blocked store reads as \"nothing stored\"\n try {\n return localStorage.getItem(key);\n // webpieces-disable no-any-unknown -- browsers may throw any value from blocked storage\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n return null;\n }\n }\n}\n\n/**\n * What the \"why\" panel says about one affected project (#1264): never a reason the graph cannot show.\n * Pure text, so a spec reads it without a browser; GraphPage puts it in a panel.\n */\nclass WpImpactWhy {\n static readonly TITLES: Record<string, string> = {\n touched: 'Changed',\n dependent: 'Dependent',\n implicit: 'Implicitly touched',\n rootFile: 'Root-file touched',\n unexplained: 'Unexplained',\n };\n\n /**\n * `8 changed · 3 dependents · 22 implicitly touched (runtime-wiring-check inputs) · 47 dependencies`\n * — the same split ImpactCounts (graph-impact.ts) prints on the console.\n */\n counts(impact: ImpactJson): string {\n const parts = [`${impact.touched.length} changed`, `${impact.dependents.length} dependents`];\n if (impact.implicit.length > 0) {\n const targets = [\n ...new Set(\n impact.reasons\n .filter((reason: AffectedReasonJson): boolean => reason.kind === 'implicit' && reason.target !== '')\n .map((reason: AffectedReasonJson): string => reason.target),\n ),\n ].sort();\n parts.push(`${impact.implicit.length} implicitly touched${targets.length === 0 ? '' : ` (${targets.join(', ')} inputs)`}`);\n }\n if (impact.rootFile.length > 0) parts.push(`${impact.rootFile.length} root-file touched`);\n if (impact.unexplained.length > 0) parts.push(`${impact.unexplained.length} unexplained`);\n parts.push(`${impact.buildInputs.length} dependencies`);\n return parts.join(' · ');\n }\n\n title(reason: AffectedReasonJson): string {\n return `${WpImpactWhy.TITLES[reason.kind] ?? reason.kind}: ${reason.project}`;\n }\n\n lines(reason: AffectedReasonJson): string[] {\n if (reason.kind === 'touched') {\n const owns = reason.files.length === 1 ? 'this changed file' : `these ${reason.files.length} changed files`;\n return [`It owns ${owns}:`, ...reason.files];\n }\n if (reason.kind === 'dependent')\n return [\n 'It uses a changed or touched project through these dependency edges, so its tests and build re-run:',\n reason.path.join(' -> '),\n ];\n if (reason.kind === 'implicit')\n return [\n reason.target === '' ? reason.detail : `${reason.target} input ${reason.input} matched ${reason.files.join(', ')}`,\n 'nx marks every project whose target input glob matches a changed file. It uses no changed project.',\n ];\n if (reason.kind === 'rootFile')\n return [\n reason.detail,\n ...(reason.path.length > 0 ? [reason.path.join(' -> ')] : []),\n 'nx marked it for a workspace root file; it uses no changed project.',\n ];\n return [reason.detail];\n }\n}\n\n/**\n * The \"why is this in the build?\" panel (#1264): opened from an affected box's menu in Impact mode.\n * Text nodes only, so no project name is parsed as HTML.\n */\nclass ImpactWhyPanel {\n private readonly panel = document.createElement('div');\n private readonly text = new WpImpactWhy();\n private anchor: SVGElement | null = null;\n\n constructor() {\n this.panel.className = 'wp-impact-why';\n this.panel.hidden = true;\n this.panel.setAttribute('role', 'dialog');\n this.panel.setAttribute('aria-label', 'Why this project is in the build');\n document.body.append(this.panel);\n document.addEventListener('keydown', (ev: KeyboardEvent) => {\n if (ev.key !== 'Escape' || this.panel.hidden) return;\n ev.stopPropagation();\n this.close();\n }, true);\n document.addEventListener('click', (ev: MouseEvent) => {\n const target = ev.target;\n if (target instanceof Node && !this.panel.contains(target) && !this.anchor?.contains(target)) this.close();\n });\n }\n\n open(node: SVGGElement, reason: AffectedReasonJson): void {\n this.show(node, this.text.title(reason), this.text.lines(reason));\n }\n\n /** Any heading and lines, anchored under `node` (a box, or a changed edge). */\n show(node: SVGElement, title: string, lines: string[]): void {\n this.anchor = node;\n const heading = document.createElement('strong');\n heading.textContent = title;\n const list = document.createElement('ul');\n for (const line of lines) {\n const item = document.createElement('li');\n item.textContent = line;\n list.append(item);\n }\n const close = document.createElement('button');\n close.type = 'button';\n close.textContent = 'Close';\n close.addEventListener('click', () => this.close());\n this.panel.replaceChildren(heading, list, close);\n this.panel.hidden = false;\n const box = node.getBoundingClientRect();\n this.panel.style.left = `${Math.max(12, Math.min(box.left, innerWidth - this.panel.offsetWidth - 12))}px`;\n this.panel.style.top = `${Math.max(12, Math.min(box.bottom + 6, innerHeight - this.panel.offsetHeight - 12))}px`;\n close.focus({ preventScroll: true });\n }\n\n close(): void {\n if (this.panel.hidden) return;\n this.panel.hidden = true;\n const anchor = this.anchor;\n this.anchor = null;\n if (anchor?.isConnected) anchor.focus({ preventScroll: true });\n }\n}\n\n/** A changed edge the saved page does not draw: added to the Impact overlay. */\nclass WpOverlayEdge implements RenderEdgeJson {\n readonly productDot: string | null = null;\n\n constructor(\n readonly id: string,\n readonly from: string,\n readonly to: string,\n readonly dot: string,\n ) {}\n}\n\n/** The page's own model with the overlay's edges: same boxes, levels and frame. */\nclass WpOverlayModel implements RenderModelJson {\n readonly viewer: 'architecture' | 'runtime';\n readonly nodes: RenderNodeJson[];\n readonly bands: RenderBandJson[];\n readonly fullDot: string;\n readonly header: string;\n readonly footer: string;\n readonly comparison: RuntimeComparisonJson | null;\n\n constructor(\n base: RenderModelJson,\n readonly edges: RenderEdgeJson[],\n ) {\n this.viewer = base.viewer;\n this.nodes = base.nodes;\n this.bands = base.bands;\n this.fullDot = base.fullDot;\n this.header = base.header;\n this.footer = base.footer;\n this.comparison = base.comparison ?? null;\n }\n}\n\n/**\n * The edges this branch gained or lost since its fork point, drawn over the page in Impact mode\n * (#1264). A changed edge the page already draws is restyled; one it does not (the page is the saved\n * snapshot, the diff is against the LIVE graph) is added: a gained edge solid red and bold, a lost one\n * a red dashed ghost. Every changed edge is clickable, and a box whose only change is an edge gets a\n * red badge.\n */\nclass WpEdgeOverlay {\n static readonly RED = '#d32f2f';\n static readonly PREFIX = 'wp-edge-change-';\n /** pair key → that pair's changes; null until first asked. */\n private pairs: Map<string, EdgeChangeJson[]> | null = null;\n /** Rendered edge id → the changes it stands for. */\n private readonly byEdgeId = new Map<string, EdgeChangeJson[]>();\n private overlay: RenderModelJson | null = null;\n\n constructor(private readonly modes: WpModeState) {}\n\n private key(from: string, to: string): string {\n return `${from}\\u0000${to}`;\n }\n\n private grouped(): Map<string, EdgeChangeJson[]> {\n if (this.pairs !== null) return this.pairs;\n this.pairs = new Map<string, EdgeChangeJson[]>();\n for (const change of this.modes.edgeChanges()) {\n const key = this.key(change.from, change.to);\n this.pairs.set(key, [...(this.pairs.get(key) ?? []), change]);\n }\n return this.pairs;\n }\n\n any(): boolean {\n return this.modes.edgeChanges().length > 0;\n }\n\n /** `base` plus a ghost statement for every changed edge it does not draw (both ends must be boxes). */\n model(base: RenderModelJson): RenderModelJson {\n if (this.overlay !== null) return this.overlay;\n const ids = new Set(base.nodes.map((node: RenderNodeJson): string => node.id));\n const drawn = new Set(base.edges.map((edge: RenderEdgeJson): string => this.key(edge.from, edge.to)));\n const extra: RenderEdgeJson[] = [];\n for (const key of this.grouped().keys()) {\n const changes = this.grouped().get(key) as EdgeChangeJson[];\n const from = changes[0].from;\n const to = changes[0].to;\n if (drawn.has(key) || !ids.has(from) || !ids.has(to)) continue;\n const id = `${WpEdgeOverlay.PREFIX}${extra.length}`;\n const dot = ` \"${this.esc(from)}\" -> \"${this.esc(to)}\" [id=\"${id}\", constraint=false${this.attrs(changes)}];\\n`;\n this.byEdgeId.set(id, changes);\n extra.push(new WpOverlayEdge(id, from, to, dot));\n }\n this.overlay = new WpOverlayModel(base, [...base.edges, ...extra]);\n return this.overlay;\n }\n\n /** A drawn edge's statement restyled when it changed; null when it did not. */\n edgeDot(edge: RenderEdgeJson): string | null {\n if (edge.id.startsWith(WpEdgeOverlay.PREFIX)) return edge.dot;\n const changes = this.grouped().get(this.key(edge.from, edge.to));\n if (changes === undefined) return null;\n this.byEdgeId.set(edge.id, changes);\n return `${edge.dot.slice(0, -2)} [${this.attrs(changes).slice(2)}];\\n`;\n }\n\n /** Gained (any API new) is solid, bold red; otherwise a lost edge is a red dashed ghost. */\n private attrs(changes: EdgeChangeJson[]): string {\n const gained = changes.some((change: EdgeChangeJson): boolean => change.change === 'gained');\n const tooltip = this.esc(changes.map((change: EdgeChangeJson): string => change.text).join('; '));\n return `, color=\"${WpEdgeOverlay.RED}\", fontcolor=\"${WpEdgeOverlay.RED}\", penwidth=${gained ? '3' : '2'}, style=\"${gained ? 'bold' : 'dashed'}\", tooltip=\"${tooltip}\"`;\n }\n\n private esc(text: string): string {\n return text.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n }\n\n /** The lines for one box's edge changes (\"new on this branch: …\", \"… no longer calls …\"). */\n nodeLines(id: string): string[] {\n return this.modes\n .edgeChanges()\n .filter((change: EdgeChangeJson): boolean => change.from === id || change.to === id)\n .map((change: EdgeChangeJson): string => change.text);\n }\n\n /** Every changed edge clickable; a red badge on each box whose ONLY change is an edge. */\n decorate(svg: SVGSVGElement, panel: ImpactWhyPanel): void {\n for (const id of this.byEdgeId.keys()) {\n const edge = svg.querySelector<SVGGElement>(`g.edge[id=\"${id}\"]`);\n const changes = this.byEdgeId.get(id) as EdgeChangeJson[];\n if (edge === null) continue;\n edge.classList.add('wp-edge-change');\n edge.setAttribute('tabindex', '0');\n edge.setAttribute('role', 'button');\n const title = changes.some((change: EdgeChangeJson): boolean => change.change === 'gained') ? 'Gained edge' : 'Lost edge';\n const show = (ev: Event): void => {\n ev.stopPropagation();\n panel.show(edge, title, changes.map((change: EdgeChangeJson): string => change.text));\n };\n edge.addEventListener('click', show);\n edge.addEventListener('keydown', (ev: KeyboardEvent) => {\n if (ev.key === 'Enter' || ev.key === ' ') show(ev);\n });\n }\n const affected = new Set(this.modes.affected());\n const ends = this.modes.edgeEnds();\n svg.querySelectorAll<SVGGElement>('g.node').forEach((node: SVGGElement) => {\n const id = node.querySelector('title')?.textContent ?? '';\n if (!ends.has(id) || affected.has(id)) return;\n node.append(this.badge(node));\n });\n }\n\n private badge(node: SVGGElement): SVGGElement {\n const ns = 'http://www.w3.org/2000/svg';\n const box = node.getBBox();\n const group = document.createElementNS(ns, 'g');\n group.setAttribute('class', 'wp-edge-badge');\n const circle = document.createElementNS(ns, 'circle');\n circle.setAttribute('cx', String(box.x + box.width - 2));\n circle.setAttribute('cy', String(box.y + 2));\n circle.setAttribute('r', '8');\n circle.setAttribute('fill', WpEdgeOverlay.RED);\n const text = document.createElementNS(ns, 'text');\n text.setAttribute('x', String(box.x + box.width - 2));\n text.setAttribute('y', String(box.y + 6));\n text.setAttribute('text-anchor', 'middle');\n text.setAttribute('font-size', '12');\n text.setAttribute('fill', '#ffffff');\n text.textContent = '±';\n const hint = document.createElementNS(ns, 'title');\n hint.textContent = 'Only its edges changed on this branch';\n group.append(circle, text, hint);\n return group;\n }\n}\n"]}
@@ -26,9 +26,11 @@ class GraphNavigation {
26
26
  .wp-navigation.wp-zoom button { min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid #d9dce5; background: #fff; color: #1b1e27; font-size: 15px; }
27
27
  .wp-navigation.wp-zoom button:hover { border-color: #3559d6; }
28
28
  .wp-navigation.wp-zoom output { min-width: 42px; padding: 2px 6px; border-radius: 6px; background: #ffffffd9; color: #5d6475; font-size: 12px; text-align: center; }
29
- .wp-graph-details { position: fixed; z-index: 1100; box-sizing: border-box; max-width: min(520px, calc(100vw - 24px)); max-height: min(480px, calc(100vh - 24px)); overflow: auto; background: white; padding: 12px; border: 1px solid #aaa; border-radius: 6px; box-shadow: 0 4px 16px #0003; font: 14px/1.5 Arial, sans-serif; overflow-wrap: anywhere; }
30
- .wp-graph-details li { margin-bottom: 8px; }
29
+ .wp-graph-details, .wp-impact-why { position: fixed; z-index: 1100; box-sizing: border-box; max-width: min(520px, calc(100vw - 24px)); max-height: min(480px, calc(100vh - 24px)); overflow: auto; background: white; padding: 12px; border: 1px solid #aaa; border-radius: 6px; box-shadow: 0 4px 16px #0003; font: 14px/1.5 Arial, sans-serif; overflow-wrap: anywhere; }
30
+ .wp-graph-details li, .wp-impact-why li { margin-bottom: 8px; }
31
31
  .wp-api-detail { cursor: pointer; text-decoration: underline; }
32
+ #graph g.edge.wp-edge-change { cursor: pointer; }
33
+ #graph g.edge.wp-edge-change:focus { outline: 2px dashed #d32f2f; }
32
34
  .wp-api-detail:focus { outline: 2px solid #1565c0; }
33
35
  `;
34
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graph-navigation.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-navigation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where the zoom controls sit. The runtime page keeps its toolbar ABOVE the graph; the architecture\n * page gives the graph the whole viewport and floats a compact `− 1:1 ⤢ +` cluster bottom-right.\n */\nexport enum NavigationLayout {\n TOOLBAR = 'toolbar',\n FLOATING = 'floating',\n}\n\n/** Shared local-file navigation. Reset restores readable scale; Fit is an explicit overview. */\nexport class GraphNavigation {\n constructor(private readonly layout: NavigationLayout) {}\n\n styles(): string {\n return `\n #graph { overflow: auto; max-height: 75vh; text-align: left; touch-action: pan-x pan-y; }\n #graph svg { max-width: none; max-height: none; display: block; }\n .wp-navigation { display: flex; gap: 8px; align-items: center; margin: 10px 0; }\n .wp-navigation button { padding: 6px 12px; cursor: pointer; }\n .wp-navigation.wp-zoom { position: absolute; right: 14px; bottom: 14px; margin: 0; gap: 6px; z-index: 20; }\n .wp-navigation.wp-zoom button { min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid #d9dce5; background: #fff; color: #1b1e27; font-size: 15px; }\n .wp-navigation.wp-zoom button:hover { border-color: #3559d6; }\n .wp-navigation.wp-zoom output { min-width: 42px; padding: 2px 6px; border-radius: 6px; background: #ffffffd9; color: #5d6475; font-size: 12px; text-align: center; }\n .wp-graph-details { position: fixed; z-index: 1100; box-sizing: border-box; max-width: min(520px, calc(100vw - 24px)); max-height: min(480px, calc(100vh - 24px)); overflow: auto; background: white; padding: 12px; border: 1px solid #aaa; border-radius: 6px; box-shadow: 0 4px 16px #0003; font: 14px/1.5 Arial, sans-serif; overflow-wrap: anywhere; }\n .wp-graph-details li { margin-bottom: 8px; }\n .wp-api-detail { cursor: pointer; text-decoration: underline; }\n .wp-api-detail:focus { outline: 2px solid #1565c0; }\n `;\n }\n\n script(): string {\n const floating = this.layout === NavigationLayout.FLOATING;\n return `new (${GraphNavigationController.toString()})(${JSON.stringify(floating)});`;\n }\n}\n\n/** Serialized browser controller; dependencies are native DOM APIs only. */\nclass GraphNavigationController {\n private readonly host = document.getElementById('graph');\n private readonly toolbar = document.createElement('nav');\n private readonly status = document.createElement('output');\n private svg: SVGSVGElement | null = null;\n private scale = 1;\n\n constructor(floating: boolean) {\n if (this.host === null) return;\n this.toolbar.className = floating ? 'wp-navigation wp-zoom' : 'wp-navigation';\n this.toolbar.setAttribute('aria-label', 'Graph navigation');\n if (floating) this.floatingButtons();\n else this.toolbarButtons();\n new MutationObserver(() => this.attach()).observe(this.host, { childList: true });\n this.attach();\n window.addEventListener('resize', () => this.changed());\n this.host.addEventListener('scroll', () => this.changed());\n }\n\n private toolbarButtons(): void {\n this.host?.before(this.toolbar);\n this.button('Zoom in', 'Zoom in', () => this.zoom(1.25));\n this.button('Zoom out', 'Zoom out', () => this.zoom(0.8));\n this.button('Readable reset', 'Readable reset', () => this.reset());\n this.button('Fit overview', 'Fit overview', () => this.fit());\n this.toolbar.append(this.status);\n }\n\n /** Placed AFTER the graph in the DOM, so it never sits ahead of the boxes in the tab order. */\n private floatingButtons(): void {\n this.host?.after(this.toolbar);\n this.toolbar.append(this.status);\n this.button('−', 'Zoom out', () => this.zoom(0.8));\n this.button('1:1', 'Readable reset', () => this.reset());\n this.button('⤢', 'Fit overview', () => this.fit());\n this.button('+', 'Zoom in', () => this.zoom(1.25));\n }\n\n private button(text: string, label: string, action: () => void): void {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = text;\n button.title = label;\n button.setAttribute('aria-label', label);\n button.addEventListener('click', action);\n this.toolbar.append(button);\n }\n\n private changed(): void {\n document.dispatchEvent(new Event('wp-graph-transform'));\n }\n\n private resize(): void {\n if (this.svg === null) return;\n const box = this.svg.viewBox.baseVal;\n this.svg.style.width = `${box.width * this.scale}px`;\n this.svg.style.height = `${box.height * this.scale}px`;\n this.status.textContent = `${Math.round(this.scale * 100)}%`;\n this.changed();\n }\n\n private zoom(factor: number): void {\n if (this.host === null) return;\n const before = this.scale;\n this.scale = Math.max(0.1, Math.min(4, this.scale * factor));\n const centerX = this.host.scrollLeft + this.host.clientWidth / 2;\n const centerY = this.host.scrollTop + this.host.clientHeight / 2;\n this.resize();\n this.host.scrollLeft = (centerX * this.scale) / before - this.host.clientWidth / 2;\n this.host.scrollTop = (centerY * this.scale) / before - this.host.clientHeight / 2;\n }\n\n private reset(): void {\n this.scale = 1;\n this.resize();\n this.scrollOrigin();\n }\n\n private scrollOrigin(): void {\n if (this.host === null) return;\n this.host.scrollLeft = 0;\n this.host.scrollTop = 0;\n }\n\n private fit(): void {\n if (this.svg === null || this.host === null) return;\n this.scale = Math.min(\n 1,\n this.host.clientWidth / this.svg.viewBox.baseVal.width,\n this.host.clientHeight / this.svg.viewBox.baseVal.height,\n );\n this.resize();\n this.scrollOrigin();\n }\n\n private attach(): void {\n const next = this.host?.querySelector('svg');\n if (next === null || next === undefined || next === this.svg) return;\n this.svg = next;\n this.reset();\n }\n}\n"]}
1
+ {"version":3,"file":"graph-navigation.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-navigation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Where the zoom controls sit. The runtime page keeps its toolbar ABOVE the graph; the architecture\n * page gives the graph the whole viewport and floats a compact `− 1:1 ⤢ +` cluster bottom-right.\n */\nexport enum NavigationLayout {\n TOOLBAR = 'toolbar',\n FLOATING = 'floating',\n}\n\n/** Shared local-file navigation. Reset restores readable scale; Fit is an explicit overview. */\nexport class GraphNavigation {\n constructor(private readonly layout: NavigationLayout) {}\n\n styles(): string {\n return `\n #graph { overflow: auto; max-height: 75vh; text-align: left; touch-action: pan-x pan-y; }\n #graph svg { max-width: none; max-height: none; display: block; }\n .wp-navigation { display: flex; gap: 8px; align-items: center; margin: 10px 0; }\n .wp-navigation button { padding: 6px 12px; cursor: pointer; }\n .wp-navigation.wp-zoom { position: absolute; right: 14px; bottom: 14px; margin: 0; gap: 6px; z-index: 20; }\n .wp-navigation.wp-zoom button { min-width: 34px; height: 34px; padding: 0 8px; border-radius: 8px; border: 1px solid #d9dce5; background: #fff; color: #1b1e27; font-size: 15px; }\n .wp-navigation.wp-zoom button:hover { border-color: #3559d6; }\n .wp-navigation.wp-zoom output { min-width: 42px; padding: 2px 6px; border-radius: 6px; background: #ffffffd9; color: #5d6475; font-size: 12px; text-align: center; }\n .wp-graph-details, .wp-impact-why { position: fixed; z-index: 1100; box-sizing: border-box; max-width: min(520px, calc(100vw - 24px)); max-height: min(480px, calc(100vh - 24px)); overflow: auto; background: white; padding: 12px; border: 1px solid #aaa; border-radius: 6px; box-shadow: 0 4px 16px #0003; font: 14px/1.5 Arial, sans-serif; overflow-wrap: anywhere; }\n .wp-graph-details li, .wp-impact-why li { margin-bottom: 8px; }\n .wp-api-detail { cursor: pointer; text-decoration: underline; }\n #graph g.edge.wp-edge-change { cursor: pointer; }\n #graph g.edge.wp-edge-change:focus { outline: 2px dashed #d32f2f; }\n .wp-api-detail:focus { outline: 2px solid #1565c0; }\n `;\n }\n\n script(): string {\n const floating = this.layout === NavigationLayout.FLOATING;\n return `new (${GraphNavigationController.toString()})(${JSON.stringify(floating)});`;\n }\n}\n\n/** Serialized browser controller; dependencies are native DOM APIs only. */\nclass GraphNavigationController {\n private readonly host = document.getElementById('graph');\n private readonly toolbar = document.createElement('nav');\n private readonly status = document.createElement('output');\n private svg: SVGSVGElement | null = null;\n private scale = 1;\n\n constructor(floating: boolean) {\n if (this.host === null) return;\n this.toolbar.className = floating ? 'wp-navigation wp-zoom' : 'wp-navigation';\n this.toolbar.setAttribute('aria-label', 'Graph navigation');\n if (floating) this.floatingButtons();\n else this.toolbarButtons();\n new MutationObserver(() => this.attach()).observe(this.host, { childList: true });\n this.attach();\n window.addEventListener('resize', () => this.changed());\n this.host.addEventListener('scroll', () => this.changed());\n }\n\n private toolbarButtons(): void {\n this.host?.before(this.toolbar);\n this.button('Zoom in', 'Zoom in', () => this.zoom(1.25));\n this.button('Zoom out', 'Zoom out', () => this.zoom(0.8));\n this.button('Readable reset', 'Readable reset', () => this.reset());\n this.button('Fit overview', 'Fit overview', () => this.fit());\n this.toolbar.append(this.status);\n }\n\n /** Placed AFTER the graph in the DOM, so it never sits ahead of the boxes in the tab order. */\n private floatingButtons(): void {\n this.host?.after(this.toolbar);\n this.toolbar.append(this.status);\n this.button('−', 'Zoom out', () => this.zoom(0.8));\n this.button('1:1', 'Readable reset', () => this.reset());\n this.button('⤢', 'Fit overview', () => this.fit());\n this.button('+', 'Zoom in', () => this.zoom(1.25));\n }\n\n private button(text: string, label: string, action: () => void): void {\n const button = document.createElement('button');\n button.type = 'button';\n button.textContent = text;\n button.title = label;\n button.setAttribute('aria-label', label);\n button.addEventListener('click', action);\n this.toolbar.append(button);\n }\n\n private changed(): void {\n document.dispatchEvent(new Event('wp-graph-transform'));\n }\n\n private resize(): void {\n if (this.svg === null) return;\n const box = this.svg.viewBox.baseVal;\n this.svg.style.width = `${box.width * this.scale}px`;\n this.svg.style.height = `${box.height * this.scale}px`;\n this.status.textContent = `${Math.round(this.scale * 100)}%`;\n this.changed();\n }\n\n private zoom(factor: number): void {\n if (this.host === null) return;\n const before = this.scale;\n this.scale = Math.max(0.1, Math.min(4, this.scale * factor));\n const centerX = this.host.scrollLeft + this.host.clientWidth / 2;\n const centerY = this.host.scrollTop + this.host.clientHeight / 2;\n this.resize();\n this.host.scrollLeft = (centerX * this.scale) / before - this.host.clientWidth / 2;\n this.host.scrollTop = (centerY * this.scale) / before - this.host.clientHeight / 2;\n }\n\n private reset(): void {\n this.scale = 1;\n this.resize();\n this.scrollOrigin();\n }\n\n private scrollOrigin(): void {\n if (this.host === null) return;\n this.host.scrollLeft = 0;\n this.host.scrollTop = 0;\n }\n\n private fit(): void {\n if (this.svg === null || this.host === null) return;\n this.scale = Math.min(\n 1,\n this.host.clientWidth / this.svg.viewBox.baseVal.width,\n this.host.clientHeight / this.svg.viewBox.baseVal.height,\n );\n this.resize();\n this.scrollOrigin();\n }\n\n private attach(): void {\n const next = this.host?.querySelector('svg');\n if (next === null || next === undefined || next === this.svg) return;\n this.svg = next;\n this.reset();\n }\n}\n"]}
@@ -13,9 +13,9 @@
13
13
  import { ProductColor } from './graph-products';
14
14
  /**
15
15
  * The Filter popover's "Changes on this branch" choices. The page computes each set from the Impact
16
- * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their
16
+ * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = every project nx affected, whatever its reason (their
17
17
  * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +
18
- * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than
18
+ * every affected project + every build input — the dependencies of the DEPENDENTS too, which is why it is more than
19
19
  * the union of the two rows above it.
20
20
  *
21
21
  * "Dependents" are what USE a project and "dependencies" what it uses (#1179). "Dependee" would mean
@@ -26,7 +26,9 @@ export declare enum ChangeScope {
26
26
  CHANGED = "changed",
27
27
  DEPENDENTS = "dependents",
28
28
  DEPENDENCIES = "dependencies",
29
- BUILD = "build"
29
+ BUILD = "build",
30
+ /** #1264: the boxes at either end of an edge gained or lost since the fork point. */
31
+ EDGES = "edges"
30
32
  }
31
33
  /**
32
34
  * One change scope: its name and the grey second line under it, like the Color-by menu. These are the
@@ -16,9 +16,9 @@ exports.GraphPageShell = exports.ShellParts = exports.SCOPE_OPTIONS = exports.Sc
16
16
  const graph_color_modes_1 = require("./graph-color-modes");
17
17
  /**
18
18
  * The Filter popover's "Changes on this branch" choices. The page computes each set from the Impact
19
- * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their
19
+ * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = every project nx affected, whatever its reason (their
20
20
  * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +
21
- * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than
21
+ * every affected project + every build input — the dependencies of the DEPENDENTS too, which is why it is more than
22
22
  * the union of the two rows above it.
23
23
  *
24
24
  * "Dependents" are what USE a project and "dependencies" what it uses (#1179). "Dependee" would mean
@@ -31,6 +31,8 @@ var ChangeScope;
31
31
  ChangeScope["DEPENDENTS"] = "dependents";
32
32
  ChangeScope["DEPENDENCIES"] = "dependencies";
33
33
  ChangeScope["BUILD"] = "build";
34
+ /** #1264: the boxes at either end of an edge gained or lost since the fork point. */
35
+ ChangeScope["EDGES"] = "edges";
34
36
  })(ChangeScope || (exports.ChangeScope = ChangeScope = {}));
35
37
  /**
36
38
  * One change scope: its name and the grey second line under it, like the Color-by menu. These are the
@@ -51,9 +53,10 @@ exports.ScopeOption = ScopeOption;
51
53
  exports.SCOPE_OPTIONS = [
52
54
  new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),
53
55
  new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),
54
- new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),
56
+ new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything else nx affected: dependents, implicitly and root-file touched (their tests re-run)'),
55
57
  new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),
56
58
  new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),
59
+ new ScopeOption(ChangeScope.EDGES, 'Edge changes only', 'projects that gained or lost a dependency or call since the fork point'),
57
60
  ];
58
61
  /** Everything the shell frames, already rendered by its owner. */
59
62
  class ShellParts {
@@ -242,7 +245,7 @@ class GraphPageShell {
242
245
  <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row).</p>
243
246
  <p><strong>Filter</strong> narrows the graph by what changed on this branch or in the last commit, by runtime, by role and by product; the groups combine. A product keeps its servers, clients and apps plus everything they depend on, so <strong>Product</strong> + <strong>Changed + dependents + dependencies</strong> is exactly what this branch builds that can affect that product. Every box keeps its own L-row, and a level left empty shows as a thin labeled band.</p>
244
247
  <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>
245
- <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber) and its dependents (amber outline); with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>
248
+ <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber), its dependents (amber outline), and what nx marked for another reason: an input glob (violet hatch) or a root file (teal). Click an affected box for why; with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>
246
249
  <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> <kbd>4</kbd> switch mode · <kbd>Esc</kbd> unlock</p>
247
250
  </div>`;
248
251
  }