@webpieces/nx-webpieces-rules 0.4.912 → 0.4.913
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.
- package/README.md +11 -0
- package/executors.json +5 -0
- package/package.json +7 -7
- package/src/executors/help/executor.js +2 -0
- package/src/executors/help/executor.js.map +1 -1
- package/src/executors/visualize-runtime/executor.d.ts +3 -1
- package/src/executors/visualize-runtime/executor.js +9 -3
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/executors/visualize-runtime-goal/executor.d.ts +27 -0
- package/src/executors/visualize-runtime-goal/executor.js +98 -0
- package/src/executors/visualize-runtime-goal/executor.js.map +1 -0
- package/src/executors/visualize-runtime-goal/schema.json +13 -0
- package/src/lib/graph-color-modes.d.ts +8 -1
- package/src/lib/graph-color-modes.js +11 -1
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-filter.client.d.ts +9 -2
- package/src/lib/graph-filter.client.js +15 -0
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +9 -2
- package/src/lib/graph-mode-state.client.js +17 -4
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +6 -5
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +41 -0
- package/src/lib/graph-render-model.js +45 -1
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +7 -1
- package/src/lib/graph-visualizer.client.js +37 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/runtime-goal-comparison.d.ts +55 -0
- package/src/lib/runtime-goal-comparison.js +209 -0
- package/src/lib/runtime-goal-comparison.js.map +1 -0
- package/src/lib/runtime-goal-details.d.ts +17 -0
- package/src/lib/runtime-goal-details.js +118 -0
- package/src/lib/runtime-goal-details.js.map +1 -0
- package/src/lib/runtime-goal-file.d.ts +72 -0
- package/src/lib/runtime-goal-file.js +201 -0
- package/src/lib/runtime-goal-file.js.map +1 -0
- package/src/lib/runtime-goal-styles.d.ts +10 -0
- package/src/lib/runtime-goal-styles.js +25 -0
- package/src/lib/runtime-goal-styles.js.map +1 -0
- package/src/lib/runtime-goal-validator.d.ts +39 -0
- package/src/lib/runtime-goal-validator.js +236 -0
- package/src/lib/runtime-goal-validator.js.map +1 -0
- package/src/lib/runtime-goal-view.d.ts +22 -0
- package/src/lib/runtime-goal-view.js +64 -0
- package/src/lib/runtime-goal-view.js.map +1 -0
- package/src/lib/runtime-graph-diff.d.ts +107 -0
- package/src/lib/runtime-graph-diff.js +346 -0
- package/src/lib/runtime-graph-diff.js.map +1 -0
- package/src/lib/runtime-html-page.d.ts +2 -0
- package/src/lib/runtime-html-page.js +28 -3
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.d.ts +2 -0
- package/src/lib/runtime-layout.js +4 -0
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-union-graph.d.ts +20 -0
- package/src/lib/runtime-union-graph.js +94 -0
- package/src/lib/runtime-union-graph.js.map +1 -0
- package/src/lib/runtime-visualizer.client.js +7 -3
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +8 -3
- package/src/lib/runtime-visualizer.js +18 -10
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-options.d.ts +28 -1
- package/src/lib/runtime-viz-options.js +36 -2
- package/src/lib/runtime-viz-options.js.map +1 -1
- package/src/plugin.js +2 -2
- package/src/plugin.js.map +1 -1
- package/src/runtime-targets.d.ts +6 -0
- package/src/runtime-targets.js +17 -0
- package/src/runtime-targets.js.map +1 -1
|
@@ -127,6 +127,21 @@ class WpFilterPage {
|
|
|
127
127
|
this.chain = new WpGraphChain(model);
|
|
128
128
|
this.retained = new Set(model.nodes.map((node) => node.id));
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —
|
|
132
|
+
* so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of
|
|
133
|
+
* the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is
|
|
134
|
+
* dropped rather than filtering everything away; a Lock survives and relights when its box exists.
|
|
135
|
+
*/
|
|
136
|
+
useModel(model) {
|
|
137
|
+
if (model === this.model)
|
|
138
|
+
return;
|
|
139
|
+
this.model = model;
|
|
140
|
+
this.chain = new WpGraphChain(model);
|
|
141
|
+
this.retained = new Set(model.nodes.map((node) => node.id));
|
|
142
|
+
if (this.anchor !== null && !model.nodes.some((node) => node.id === this.anchor))
|
|
143
|
+
this.anchor = null;
|
|
144
|
+
}
|
|
130
145
|
render() {
|
|
131
146
|
document
|
|
132
147
|
.getElementById('wp-filter-off')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */\nabstract class WpFilterPage {\n protected readonly chain: WpGraphChain;\n protected locked: string | null = null;\n protected anchor: string | null = null;\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected readonly model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.filter(null));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.filter(null);\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Hide unconnected\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /**\n * Draw again with the current filter — after a color-mode switch. A control outside the graph\n * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n redraw(): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.filter(this.anchor);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n filterItem(name: string): WpNodeMenuItem {\n return new WpNodeMenuItem(\n this.anchor === null ? 'Filter Unconnected' : 'Turn off Filter',\n () => this.filter(this.anchor === null ? name : null),\n );\n }\n\n filter(anchor: string | null): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(\n anchor === null\n ? new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id))\n : this.chain.nodes(anchor),\n );\n const previousAnchor = this.anchor;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n anchor === null && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.anchor = anchor;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.anchor = previousAnchor;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.anchor === null;\n if (label !== null) label.textContent = this.anchor;\n if (this.anchor !== null) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Turn off Filter or reload the page.`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */\nabstract class WpFilterPage {\n protected chain: WpGraphChain;\n protected locked: string | null = null;\n protected anchor: string | null = null;\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n /**\n * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —\n * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of\n * the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is\n * dropped rather than filtering everything away; a Lock survives and relights when its box exists.\n */\n protected useModel(model: RenderModelJson): void {\n if (model === this.model) return;\n this.model = model;\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node: RenderNodeJson): string => node.id));\n if (this.anchor !== null && !model.nodes.some((node: RenderNodeJson): boolean => node.id === this.anchor)) this.anchor = null;\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.filter(null));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.filter(null);\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Hide unconnected\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /**\n * Draw again with the current filter — after a color-mode switch. A control outside the graph\n * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n redraw(): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.filter(this.anchor);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n filterItem(name: string): WpNodeMenuItem {\n return new WpNodeMenuItem(\n this.anchor === null ? 'Filter Unconnected' : 'Turn off Filter',\n () => this.filter(this.anchor === null ? name : null),\n );\n }\n\n filter(anchor: string | null): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(\n anchor === null\n ? new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id))\n : this.chain.nodes(anchor),\n );\n const previousAnchor = this.anchor;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n anchor === null && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.anchor = anchor;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.anchor = previousAnchor;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.anchor === null;\n if (label !== null) label.textContent = this.anchor;\n if (this.anchor !== null) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Turn off Filter or reload the page.`;\n }\n}\n"]}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
declare class WpModeState {
|
|
2
2
|
private readonly hasProducts;
|
|
3
3
|
private readonly viewer;
|
|
4
|
+
private readonly goalUnavailable;
|
|
5
|
+
/** `goal` (Goal vs Current, #1239) exists on runtime pages only; the shell renders it nowhere else. */
|
|
4
6
|
static readonly MODES: string[];
|
|
5
7
|
static readonly NAMES: string[];
|
|
6
8
|
static readonly SUBTITLES: string[];
|
|
7
9
|
readonly scans: ImpactJson[];
|
|
8
10
|
kind: string;
|
|
9
11
|
mode: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param hasProducts some box belongs to a product, so the Product mode has something to show
|
|
14
|
+
* @param goalUnavailable '' when Goal vs Current can be shown, else why not (#1239)
|
|
15
|
+
*/
|
|
16
|
+
constructor(hasProducts: boolean, viewer: string, goalUnavailable: string);
|
|
12
17
|
private storageKey;
|
|
13
18
|
/** '' when Product can be shown, else why not. */
|
|
14
19
|
productReason(): string;
|
|
20
|
+
/** '' when Goal vs Current can be shown, else why not (a missing or unusable input, never "no differences"). */
|
|
21
|
+
goalReason(): string;
|
|
15
22
|
/** The active scan; null when the page has no Impact data. */
|
|
16
23
|
get impact(): ImpactJson | null;
|
|
17
24
|
/** '' when Impact can be shown, else the one line the drawer and the Filter popover say instead. */
|
|
@@ -2,21 +2,28 @@
|
|
|
2
2
|
class WpModeState {
|
|
3
3
|
hasProducts;
|
|
4
4
|
viewer;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
goalUnavailable;
|
|
6
|
+
/** `goal` (Goal vs Current, #1239) exists on runtime pages only; the shell renders it nowhere else. */
|
|
7
|
+
static MODES = ['runtime', 'architecture', 'impact', 'product', 'goal'];
|
|
8
|
+
static NAMES = ['Runtime', 'Architecture', 'Impact', 'Product', 'Goal vs Current'];
|
|
7
9
|
static SUBTITLES = [
|
|
8
10
|
'where the code can run',
|
|
9
11
|
'servers · clients · APIs',
|
|
10
12
|
'what changed',
|
|
11
13
|
'which products it belongs to',
|
|
14
|
+
'Current → Goal proposal',
|
|
12
15
|
];
|
|
13
16
|
scans = window.__WP_IMPACT__?.scans ?? [];
|
|
14
17
|
kind = '';
|
|
15
18
|
mode = 'runtime';
|
|
16
|
-
/**
|
|
17
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param hasProducts some box belongs to a product, so the Product mode has something to show
|
|
21
|
+
* @param goalUnavailable '' when Goal vs Current can be shown, else why not (#1239)
|
|
22
|
+
*/
|
|
23
|
+
constructor(hasProducts, viewer, goalUnavailable) {
|
|
18
24
|
this.hasProducts = hasProducts;
|
|
19
25
|
this.viewer = viewer;
|
|
26
|
+
this.goalUnavailable = goalUnavailable;
|
|
20
27
|
this.kind = this.initialKind();
|
|
21
28
|
this.mode = this.initial();
|
|
22
29
|
}
|
|
@@ -29,6 +36,10 @@ class WpModeState {
|
|
|
29
36
|
? ''
|
|
30
37
|
: 'No products here: tag each project product:<name> in its project.json, then regenerate.';
|
|
31
38
|
}
|
|
39
|
+
/** '' when Goal vs Current can be shown, else why not (a missing or unusable input, never "no differences"). */
|
|
40
|
+
goalReason() {
|
|
41
|
+
return this.goalUnavailable;
|
|
42
|
+
}
|
|
32
43
|
/** The active scan; null when the page has no Impact data. */
|
|
33
44
|
get impact() {
|
|
34
45
|
return this.scans.find((scan) => scan.kind === this.kind) ?? null;
|
|
@@ -79,6 +90,8 @@ class WpModeState {
|
|
|
79
90
|
return this.impactReason() === '';
|
|
80
91
|
if (mode === 'product')
|
|
81
92
|
return this.productReason() === '';
|
|
93
|
+
if (mode === 'goal')
|
|
94
|
+
return this.goalReason() === '';
|
|
82
95
|
return true;
|
|
83
96
|
}
|
|
84
97
|
name(mode) {
|
|
@@ -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 static readonly MODES = ['runtime', 'architecture', 'impact', 'product'];\n static readonly NAMES = ['Runtime', 'Architecture', 'Impact', 'Product'];\n static readonly SUBTITLES = [\n 'where the code can run',\n 'servers · clients · APIs',\n 'what changed',\n 'which products it belongs to',\n ];\n readonly scans: ImpactJson[] = window.__WP_IMPACT__?.scans ?? [];\n kind = '';\n mode = 'runtime';\n\n /** @param hasProducts some box belongs to a product, so the Product mode has something to show */\n constructor(\n private readonly hasProducts: boolean,\n private readonly viewer: 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 /** 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 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. */\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"]}
|
|
@@ -106,7 +106,7 @@ class GraphPageShell {
|
|
|
106
106
|
<div class="wp-brand">${this.brandIcon()}<span>${parts.title}</span></div>
|
|
107
107
|
<div class="wp-group" role="group" aria-label="Color mode">
|
|
108
108
|
<div class="wp-label">Color by</div>
|
|
109
|
-
${this.modePulldown()}
|
|
109
|
+
${this.modePulldown(parts.runtime ? graph_color_modes_1.RUNTIME_GRAPH_MODES : graph_color_modes_1.GRAPH_MODES)}
|
|
110
110
|
<p class="wp-mode-reason" id="wp-impact-reason" hidden></p>
|
|
111
111
|
${this.impactKinds('drawer')}
|
|
112
112
|
</div>
|
|
@@ -138,13 +138,13 @@ class GraphPageShell {
|
|
|
138
138
|
* menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still
|
|
139
139
|
* switch.
|
|
140
140
|
*/
|
|
141
|
-
modePulldown() {
|
|
142
|
-
const first =
|
|
141
|
+
modePulldown(modes) {
|
|
142
|
+
const first = modes[0];
|
|
143
143
|
return `<div class="wp-pulldown">
|
|
144
144
|
<button type="button" class="wp-pill-trigger" id="wp-mode-trigger" aria-haspopup="menu" aria-controls="wp-mode-menu" aria-expanded="false">
|
|
145
145
|
<span class="wp-pill-text"><span class="wp-mode-name" id="wp-mode-current">${first.name}</span><span class="wp-mode-sub" id="wp-mode-current-sub">${first.subtitle}</span></span><span class="wp-caret" aria-hidden="true">▾</span>
|
|
146
146
|
</button>
|
|
147
|
-
<div class="wp-menu" id="wp-mode-menu" role="menu" aria-label="Color by" hidden>${
|
|
147
|
+
<div class="wp-menu" id="wp-mode-menu" role="menu" aria-label="Color by" hidden>${modes.map((info, index) => this.modeItem(info, index)).join('')}</div>
|
|
148
148
|
</div>`;
|
|
149
149
|
}
|
|
150
150
|
/** One menu option, two lines: the mode's name, then its grey subtitle. */
|
|
@@ -233,7 +233,8 @@ class GraphPageShell {
|
|
|
233
233
|
<p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>
|
|
234
234
|
<p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>
|
|
235
235
|
<p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors come from each project's declared product: tags; contextual nodes inherit adjacent memberships. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>
|
|
236
|
-
<p><
|
|
236
|
+
<p><strong>Goal vs Current</strong> compares the saved goal (architecture/runtime-dependencies-goal.json) with the saved current graph, Current → Goal: green added, red dashed removed (still drawn), amber changed, grey unchanged. Click a box for its before/after details, or click an arrow.</p>
|
|
237
|
+
<p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>5</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>
|
|
237
238
|
</div>`;
|
|
238
239
|
return `<div class="wp-pop wp-help" id="wp-help" role="dialog" aria-label="How to read this graph" hidden>
|
|
239
240
|
<header><span>Reading the graph</span><button type="button" class="wp-icon wp-icon-mini" id="wp-help-close" aria-label="Close help">✕</button></header>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle } from './graph-color-modes';\nimport { ProductColor } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /**\n * One chip per product (#1179), sorted, each colored from the page's ONE palette (#1244);\n * empty hides the Filter's Product group.\n */\n public readonly products: ProductColor[],\n public readonly runtime: boolean = false,\n public readonly frameworks: readonly string[] = FRAMEWORK_ORDER,\n public readonly roles: readonly string[] = ROLE_STYLES.map((style: RoleStyle): string => style.role),\n public readonly emptyState: string = '',\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown()}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-lock-toggle\" aria-pressed=\"false\">Lock <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(): string {\n const first = GRAPH_MODES[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(parts: ShellParts): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter ${parts.runtime ? 'services' : 'projects'}\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', parts.roles)}</div></fieldset>\n ${this.productGroup(parts.products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their members; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: ProductColor[]): string {\n if (products.length === 0) return '';\n const chips = products\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `${parts.emptyState}<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help(parts.runtime)}\n ${this.filterPopover(parts)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(runtime: boolean): string {\n if (runtime) return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the runtime graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>\n <p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>\n <p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>\n <p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>\n <p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors come from each project's declared product: tags; contextual nodes inherit adjacent memberships. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>\n <p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>\n </div>`;\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <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>\n <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>\n <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>\n <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>\n <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>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle, RUNTIME_GRAPH_MODES } from './graph-color-modes';\nimport { ProductColor } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /**\n * One chip per product (#1179), sorted, each colored from the page's ONE palette (#1244);\n * empty hides the Filter's Product group.\n */\n public readonly products: ProductColor[],\n public readonly runtime: boolean = false,\n public readonly frameworks: readonly string[] = FRAMEWORK_ORDER,\n public readonly roles: readonly string[] = ROLE_STYLES.map((style: RoleStyle): string => style.role),\n public readonly emptyState: string = '',\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown(parts.runtime ? RUNTIME_GRAPH_MODES : GRAPH_MODES)}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-lock-toggle\" aria-pressed=\"false\">Lock <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(modes: readonly ModeInfo[]): string {\n const first = modes[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${modes.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(parts: ShellParts): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter ${parts.runtime ? 'services' : 'projects'}\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', parts.roles)}</div></fieldset>\n ${this.productGroup(parts.products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their members; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: ProductColor[]): string {\n if (products.length === 0) return '';\n const chips = products\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `${parts.emptyState}<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help(parts.runtime)}\n ${this.filterPopover(parts)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(runtime: boolean): string {\n if (runtime) return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the runtime graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>\n <p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>\n <p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>\n <p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>\n <p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors come from each project's declared product: tags; contextual nodes inherit adjacent memberships. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>\n <p><strong>Goal vs Current</strong> compares the saved goal (architecture/runtime-dependencies-goal.json) with the saved current graph, Current → Goal: green added, red dashed removed (still drawn), amber changed, grey unchanged. Click a box for its before/after details, or click an arrow.</p>\n <p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>5</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>\n </div>`;\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <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>\n <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>\n <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>\n <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>\n <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>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
@@ -56,6 +56,8 @@ export declare class GraphRenderModel {
|
|
|
56
56
|
footer: string;
|
|
57
57
|
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
58
58
|
legend: LegendFacts;
|
|
59
|
+
/** Runtime pages only (#1239): the Goal vs Current overlay, or why it is unavailable. */
|
|
60
|
+
comparison: RuntimeComparisonView | null;
|
|
59
61
|
node(id: string, dot: string, products?: string[], queueKeys?: string[]): string;
|
|
60
62
|
/**
|
|
61
63
|
* Every product a declared box (a queue, a cron trigger, an external system) inherits from the
|
|
@@ -74,6 +76,45 @@ export declare class GraphRenderModel {
|
|
|
74
76
|
*/
|
|
75
77
|
edge(from: string, to: string, dot: string, productAttrs?: string): string;
|
|
76
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* What a runtime page carries for its Goal vs Current color mode (#1239), serialized into the page
|
|
81
|
+
* with the render model so it works opened straight from disk (`file://`, no fetch).
|
|
82
|
+
*
|
|
83
|
+
* Never a silent "no differences": an unavailable comparison says why (a missing or unusable input,
|
|
84
|
+
* naming its path and repair), and identical inputs say so explicitly with the normal graph context.
|
|
85
|
+
*/
|
|
86
|
+
export declare class RuntimeComparisonView {
|
|
87
|
+
readonly available: boolean;
|
|
88
|
+
/** Why the mode is unavailable; '' when available. */
|
|
89
|
+
readonly reason: string;
|
|
90
|
+
/** Always "Current → Goal": the direction every added/removed word is read in. */
|
|
91
|
+
readonly direction: string;
|
|
92
|
+
/** One line: counts per status, or the explicit no-differences result. */
|
|
93
|
+
readonly summary: string;
|
|
94
|
+
/** The overlay drawn in this mode; null when unavailable. */
|
|
95
|
+
readonly model: GraphRenderModel | null;
|
|
96
|
+
/** Overlay render-node id -> before/after detail lines. */
|
|
97
|
+
readonly nodeDetails: Record<string, string[]>;
|
|
98
|
+
/** Overlay render-edge id (`wp-real-edge-N`) -> before/after detail lines. */
|
|
99
|
+
readonly edgeDetails: Record<string, string[]>;
|
|
100
|
+
/** Every added, removed and changed fact, one line each, for the legend. */
|
|
101
|
+
readonly differences: string[];
|
|
102
|
+
constructor(available: boolean,
|
|
103
|
+
/** Why the mode is unavailable; '' when available. */
|
|
104
|
+
reason: string,
|
|
105
|
+
/** Always "Current → Goal": the direction every added/removed word is read in. */
|
|
106
|
+
direction: string,
|
|
107
|
+
/** One line: counts per status, or the explicit no-differences result. */
|
|
108
|
+
summary: string,
|
|
109
|
+
/** The overlay drawn in this mode; null when unavailable. */
|
|
110
|
+
model: GraphRenderModel | null,
|
|
111
|
+
/** Overlay render-node id -> before/after detail lines. */
|
|
112
|
+
nodeDetails: Record<string, string[]>,
|
|
113
|
+
/** Overlay render-edge id (`wp-real-edge-N`) -> before/after detail lines. */
|
|
114
|
+
edgeDetails: Record<string, string[]>,
|
|
115
|
+
/** Every added, removed and changed fact, one line each, for the legend. */
|
|
116
|
+
differences: string[]);
|
|
117
|
+
}
|
|
77
118
|
/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */
|
|
78
119
|
export declare class GraphFilterAssets {
|
|
79
120
|
json<T>(value: T): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GraphFilterAssets = exports.GraphRenderModel = exports.RenderEdge = exports.NodeTags = exports.RenderNode = void 0;
|
|
3
|
+
exports.GraphFilterAssets = exports.RuntimeComparisonView = exports.GraphRenderModel = exports.RenderEdge = exports.NodeTags = exports.RenderNode = void 0;
|
|
4
4
|
const dot_syntax_1 = require("./dot-syntax");
|
|
5
5
|
const graph_color_modes_1 = require("./graph-color-modes");
|
|
6
6
|
/**
|
|
@@ -74,6 +74,8 @@ class GraphRenderModel {
|
|
|
74
74
|
footer = '';
|
|
75
75
|
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
76
76
|
legend = new graph_color_modes_1.LegendFacts();
|
|
77
|
+
/** Runtime pages only (#1239): the Goal vs Current overlay, or why it is unavailable. */
|
|
78
|
+
comparison = null;
|
|
77
79
|
node(id, dot, products = [], queueKeys = []) {
|
|
78
80
|
this.nodes.push(new RenderNode(id, dot, null, null, products, queueKeys));
|
|
79
81
|
return dot;
|
|
@@ -136,6 +138,48 @@ class GraphRenderModel {
|
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
exports.GraphRenderModel = GraphRenderModel;
|
|
141
|
+
/**
|
|
142
|
+
* What a runtime page carries for its Goal vs Current color mode (#1239), serialized into the page
|
|
143
|
+
* with the render model so it works opened straight from disk (`file://`, no fetch).
|
|
144
|
+
*
|
|
145
|
+
* Never a silent "no differences": an unavailable comparison says why (a missing or unusable input,
|
|
146
|
+
* naming its path and repair), and identical inputs say so explicitly with the normal graph context.
|
|
147
|
+
*/
|
|
148
|
+
class RuntimeComparisonView {
|
|
149
|
+
available;
|
|
150
|
+
reason;
|
|
151
|
+
direction;
|
|
152
|
+
summary;
|
|
153
|
+
model;
|
|
154
|
+
nodeDetails;
|
|
155
|
+
edgeDetails;
|
|
156
|
+
differences;
|
|
157
|
+
constructor(available,
|
|
158
|
+
/** Why the mode is unavailable; '' when available. */
|
|
159
|
+
reason,
|
|
160
|
+
/** Always "Current → Goal": the direction every added/removed word is read in. */
|
|
161
|
+
direction,
|
|
162
|
+
/** One line: counts per status, or the explicit no-differences result. */
|
|
163
|
+
summary,
|
|
164
|
+
/** The overlay drawn in this mode; null when unavailable. */
|
|
165
|
+
model,
|
|
166
|
+
/** Overlay render-node id -> before/after detail lines. */
|
|
167
|
+
nodeDetails,
|
|
168
|
+
/** Overlay render-edge id (`wp-real-edge-N`) -> before/after detail lines. */
|
|
169
|
+
edgeDetails,
|
|
170
|
+
/** Every added, removed and changed fact, one line each, for the legend. */
|
|
171
|
+
differences) {
|
|
172
|
+
this.available = available;
|
|
173
|
+
this.reason = reason;
|
|
174
|
+
this.direction = direction;
|
|
175
|
+
this.summary = summary;
|
|
176
|
+
this.model = model;
|
|
177
|
+
this.nodeDetails = nodeDetails;
|
|
178
|
+
this.edgeDetails = edgeDetails;
|
|
179
|
+
this.differences = differences;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.RuntimeComparisonView = RuntimeComparisonView;
|
|
139
183
|
/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */
|
|
140
184
|
class GraphFilterAssets {
|
|
141
185
|
json(value) {
|