@webpieces/nx-webpieces-rules 0.4.884 → 0.4.886

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 (45) hide show
  1. package/README.md +41 -8
  2. package/package.json +7 -7
  3. package/src/executors/visualize-runtime/executor.js +13 -1
  4. package/src/executors/visualize-runtime/executor.js.map +1 -1
  5. package/src/lib/dot-syntax.js +26 -0
  6. package/src/lib/dot-syntax.js.map +1 -1
  7. package/src/lib/graph-color-modes.d.ts +2 -1
  8. package/src/lib/graph-color-modes.js +5 -2
  9. package/src/lib/graph-color-modes.js.map +1 -1
  10. package/src/lib/graph-impact.js +1 -0
  11. package/src/lib/graph-impact.js.map +1 -1
  12. package/src/lib/graph-legend.d.ts +1 -1
  13. package/src/lib/graph-legend.js.map +1 -1
  14. package/src/lib/graph-mode-state.client.d.ts +32 -0
  15. package/src/lib/graph-mode-state.client.js +141 -0
  16. package/src/lib/graph-mode-state.client.js.map +1 -0
  17. package/src/lib/graph-page-shell.d.ts +5 -1
  18. package/src/lib/graph-page-shell.js +31 -12
  19. package/src/lib/graph-page-shell.js.map +1 -1
  20. package/src/lib/graph-render-model.d.ts +7 -5
  21. package/src/lib/graph-render-model.js +11 -8
  22. package/src/lib/graph-render-model.js.map +1 -1
  23. package/src/lib/graph-visualizer.client.d.ts +11 -46
  24. package/src/lib/graph-visualizer.client.js +103 -160
  25. package/src/lib/graph-visualizer.client.js.map +1 -1
  26. package/src/lib/graph-visualizer.js +2 -1
  27. package/src/lib/graph-visualizer.js.map +1 -1
  28. package/src/lib/runtime-details.d.ts +4 -1
  29. package/src/lib/runtime-details.js +102 -55
  30. package/src/lib/runtime-details.js.map +1 -1
  31. package/src/lib/runtime-html-page.d.ts +13 -40
  32. package/src/lib/runtime-html-page.js +96 -129
  33. package/src/lib/runtime-html-page.js.map +1 -1
  34. package/src/lib/runtime-viewer-adapter.d.ts +16 -0
  35. package/src/lib/runtime-viewer-adapter.js +58 -0
  36. package/src/lib/runtime-viewer-adapter.js.map +1 -0
  37. package/src/lib/runtime-visualizer.client.d.ts +9 -19
  38. package/src/lib/runtime-visualizer.client.js +75 -75
  39. package/src/lib/runtime-visualizer.client.js.map +1 -1
  40. package/src/lib/runtime-visualizer.d.ts +2 -16
  41. package/src/lib/runtime-visualizer.js +22 -44
  42. package/src/lib/runtime-visualizer.js.map +1 -1
  43. package/src/lib/runtime-viz-options.d.ts +19 -0
  44. package/src/lib/runtime-viz-options.js +22 -0
  45. package/src/lib/runtime-viz-options.js.map +1 -0
@@ -1,153 +1,120 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeHtmlPage = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const graph_visualizer_1 = require("./graph-visualizer");
4
8
  const graph_render_model_1 = require("./graph-render-model");
5
9
  const runtime_details_1 = require("./runtime-details");
10
+ const runtime_viz_options_1 = require("./runtime-viz-options");
6
11
  const graph_navigation_1 = require("./graph-navigation");
7
12
  const saved_snapshot_1 = require("./saved-snapshot");
8
- const graph_visualizer_1 = require("./graph-visualizer");
13
+ const graph_visualizer_2 = require("./graph-visualizer");
9
14
  const graph_node_menu_1 = require("./graph-node-menu");
10
- const runtime_viz_theme_1 = require("./runtime-viz-theme");
11
- /**
12
- * The runtime-architecture HTML page: its styles, the shared floating node menu, the graph host
13
- * and the legend.
14
- *
15
- * A CLASS rather than a bag of module functions so the client-text seam can be a constructor
16
- * parameter the way GraphVisualizer's is: the default reads the COMPILED sibling, which exists in
17
- * dist and in the published tarball but NOT in a source checkout, and a unit test running from
18
- * source hands in the text itself instead of requiring the package to have been built first.
19
- */
15
+ const graph_page_shell_1 = require("./graph-page-shell");
16
+ const graph_page_styles_1 = require("./graph-page-styles");
17
+ const graph_legend_1 = require("./graph-legend");
18
+ const graph_products_1 = require("./graph-products");
19
+ const graph_color_modes_1 = require("./graph-color-modes");
20
+ const graph_impact_1 = require("./graph-impact");
21
+ const graph_responsibilities_1 = require("./graph-responsibilities");
22
+ const dot_syntax_1 = require("./dot-syntax");
23
+ /** Runtime facts and shape adapter inside the same shell/controller as the project viewer. */
20
24
  class RuntimeHtmlPage {
21
25
  clientJs;
22
26
  filterJs;
23
- filterAssets = new graph_render_model_1.GraphFilterAssets();
24
- snapshot = new saved_snapshot_1.SavedSnapshot();
25
- /** The ONE floating-node-menu implementation, shared with dependencies.html and every design.html. */
26
- nodeMenu = new graph_node_menu_1.GraphNodeMenu();
27
- constructor(clientJs = () => (0, graph_visualizer_1.readCompiledClient)('runtime-visualizer.client.js'), filterJs = () => (0, graph_visualizer_1.readCompiledClient)('graph-filter.client.js')) {
27
+ sharedJs;
28
+ assets = new graph_render_model_1.GraphFilterAssets();
29
+ menu = new graph_node_menu_1.GraphNodeMenu();
30
+ navigation = new graph_navigation_1.GraphNavigation(graph_navigation_1.NavigationLayout.FLOATING);
31
+ constructor(clientJs = () => (0, graph_visualizer_2.readCompiledClient)('runtime-visualizer.client.js'), filterJs = () => (0, graph_visualizer_2.readCompiledClient)('graph-filter.client.js'), sharedJs = () => (0, graph_visualizer_2.readCompiledClient)('graph-visualizer.client.js')) {
28
32
  this.clientJs = clientJs;
29
33
  this.filterJs = filterJs;
34
+ this.sharedJs = sharedJs;
30
35
  }
31
- render(model, title, graph, showExternalNodes = true) {
32
- return `<!DOCTYPE html>
33
- <html>
34
- <head>
35
- <!-- REQUIRED: the cron node's label is a literal ⏰, and the DOT is embedded in this file. With
36
- no declared charset the browser falls back to a locale guess and renders it as mojibake
37
- ("â °") whenever the page is served without a charset header. -->
38
- <meta charset="utf-8">
39
- <title>${title}</title>
40
- <script src="https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js"></script>
41
- <style>${this.styles()}</style>
42
- </head>
43
- <body>
44
- <h1>${title}</h1>
45
- ${this.snapshot.html()}
46
- <p class="hint">💡 <strong>Click any box</strong> for its menu — <strong>Lock</strong> dims every other box and every arrow so one service, queue, datastore or external system stands alone; <strong>Unlock</strong> restores the whole picture.</p>
47
- <p class="hint"><strong>Filter Unconnected</strong> keeps incoming and outgoing chains and compacts the picture. <strong>Turn off Filter</strong> restores it.</p>
48
- ${this.productChips(graph)}
49
- ${this.filterAssets.html()}
50
- <div id="graph"></div>
51
- ${(0, runtime_viz_theme_1.legendHtml)()}
52
- <script>${this.nodeMenu.script()}</script>
53
- <script>${this.filterJs()}</script>
54
- <script>${this.script(model)}</script>
55
- <script>${new graph_navigation_1.GraphNavigation(graph_navigation_1.NavigationLayout.TOOLBAR).script()}</script>
56
- <script>${new runtime_details_1.RuntimeDetails(graph, showExternalNodes).script()}</script>
57
- </body>
58
- </html>`;
36
+ render(model, title, graph, options = new runtime_viz_options_1.RuntimeVizOptions()) {
37
+ model.viewer = 'runtime';
38
+ const frameworks = [
39
+ ...new Set(model.nodes.flatMap((node) => node.tags?.frameworks ?? [])),
40
+ ].sort();
41
+ const roles = [
42
+ ...new Set(model.nodes.flatMap((node) => (node.tags === null ? [] : [node.tags.role]))),
43
+ ].sort();
44
+ const products = [...new Set(model.nodes.flatMap((node) => node.products))].sort();
45
+ const parts = new graph_page_shell_1.ShellParts(title, this.lockControl(model), this.legend(model), this.edgeKey(), this.assets.html() + '<span id="wp-context-count"></span>', new saved_snapshot_1.SavedSnapshot().html(), this.responsibilities(graph, options), products, true, frameworks, roles, '<div class="wp-empty" id="wp-empty" hidden>No matching services. Open Filter and Clear all, or turn off Hide unconnected.</div>');
46
+ const shared = this.sharedJs()
47
+ .split(graph_visualizer_2.CLIENT_MODEL_PLACEHOLDER)
48
+ .join(this.assets.json(model))
49
+ .split(graph_visualizer_2.CLIENT_DESIGN_LINKS_PLACEHOLDER)
50
+ .join(this.assets.json(this.designLinks(graph, options)));
51
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
52
+ <title>${(0, dot_syntax_1.htmlLabelText)(title)}</title>
53
+ <script src="https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js"></script>
54
+ <script src="${graph_impact_1.IMPACT_SIDECAR_SRC}"></script>
55
+ <style>${this.menu.styles()}${this.menu.dimStyles('#graph')}${this.assets.styles()}${this.navigation.styles()}${new graph_page_styles_1.GraphPageStyles().css()}
56
+ .wp-graph-details { position:fixed; z-index:60; max-width:min(520px,calc(100vw - 24px)); max-height:70vh; overflow:auto; background:var(--wp-surface); border:1px solid var(--wp-line); border-radius:10px; padding:14px; box-shadow:0 8px 24px #0002; }
57
+ .wp-graph-details button { cursor:pointer; } .wp-api-detail { cursor:pointer; text-decoration:underline; }
58
+ .wp-api-detail:focus-visible { outline:3px solid var(--wp-accent); } #wp-context-count { font-size:11px; color:var(--wp-muted); }
59
+ .wp-empty { position:absolute; top:70px; left:16px; padding:16px; background:var(--wp-surface); border-radius:10px; }
60
+ </style></head><body data-wp-project-facts="${options.projects !== null}">${new graph_page_shell_1.GraphPageShell().body(parts)}
61
+ <script>${this.menu.script()}</script><script>${this.filterJs()}</script><script>${this.navigation.script()}</script>
62
+ <script>${new runtime_details_1.RuntimeDetails(graph, options.showExternalNodes, model).script()}</script>
63
+ <script>${this.clientJs()}</script><script>${shared}</script></body></html>`;
59
64
  }
60
- /**
61
- * The product chip row (#1179): one chip per product a service belongs to. Pressing chips keeps
62
- * the UNION of their services (each product's tagged services plus everything they reach at
63
- * runtime) and the queues, triggers and systems attached to them; it intersects with Filter
64
- * Unconnected. Omitted when no service belongs to a product. The page always opens unfiltered.
65
- */
66
- productChips(graph) {
67
- const products = new Set();
68
- for (const service of Object.values(graph.services)) {
69
- if (service.drawOnGraph === false)
65
+ designLinks(graph, options) {
66
+ const links = [];
67
+ for (const name of Object.keys(graph.services).sort()) {
68
+ if (graph.services[name].drawOnGraph === false)
69
+ continue;
70
+ const design = options.projects?.[name]?.designFile?.replace(/design\.json$/, 'design.html');
71
+ if (!design ||
72
+ !options.workspaceRoot ||
73
+ !fs.existsSync(path.join(options.workspaceRoot, design)))
70
74
  continue;
71
- for (const product of service.products ?? [])
72
- products.add(product);
75
+ links.push(new graph_visualizer_1.DesignLink(name, path.posix.relative('tmp/webpieces', design)));
73
76
  }
74
- if (products.size === 0)
75
- return '';
76
- const chips = [...products]
77
- .sort()
78
- .map((product) => `<button type="button" class="wp-chip" data-wp-product="${product}" aria-pressed="false">${product}</button>`)
77
+ return links;
78
+ }
79
+ lockControl(model) {
80
+ const entries = model.nodes
81
+ .map((node) => `<option value="${(0, dot_syntax_1.htmlLabelText)(node.id)}">${(0, dot_syntax_1.htmlLabelText)(node.id)} · ${node.tags?.role ?? 'context'}</option>`)
79
82
  .join('');
80
- return (`<div class="wp-product-filter" id="wp-product-filter" role="group" aria-label="Product filter">` +
81
- `<span class="wp-product-label">Product <small>any match</small></span>${chips}</div>`);
83
+ return `<input type="search" id="wp-lock" list="wp-lock-options" placeholder="Lock a node… ( / )" autocomplete="off" spellcheck="false"><datalist id="wp-lock-options">${entries}</datalist>`;
82
84
  }
83
- /**
84
- * The browser half lives in runtime-visualizer.client.ts (matching graph-visualizer.client.ts)
85
- * rather than in a template literal here: it renders with @viz-js/viz v3, redraws every queue
86
- * node as a true horizontal cylinder, and wires the shared node menu onto every box — more
87
- * logic than belongs inline in a .ts string. The substitution is a blind split/join, so the
88
- * render-model placeholder must appear EXACTLY ONCE in the client.
89
- */
90
- script(model) {
91
- return this.clientJs().split(graph_visualizer_1.CLIENT_MODEL_PLACEHOLDER).join(this.filterAssets.json(model));
85
+ legend(model) {
86
+ const tag = (group, value, color) => `<div class="wp-legend-row" data-wp-legend-group="${group}" data-wp-legend-value="${(0, dot_syntax_1.htmlLabelText)(value)}"><svg width="20" height="14"><rect width="20" height="14" rx="2" fill="${color}"/></svg>${(0, dot_syntax_1.htmlLabelText)(value)}</div>`;
87
+ const frameworks = Object.values(graph_color_modes_1.FRAMEWORK_STYLES)
88
+ .filter((style) => model.legend.frameworks.includes(style.framework))
89
+ .map((style) => tag('runtime', style.framework, style.color))
90
+ .join('');
91
+ const roles = [
92
+ ...new Set(model.nodes.flatMap((node) => (node.tags === null ? [] : [node.tags.role]))),
93
+ ]
94
+ .map((role) => tag('role', role, graph_color_modes_1.ROLE_STYLES.find((style) => style.role === role)?.fill ?? '#e4e6ec'))
95
+ .join('');
96
+ const products = new graph_products_1.ProductPalette(model.legend.products.map((count) => count.product)).colors
97
+ .map((color) => tag('product', color.product, color.color))
98
+ .join('');
99
+ return (`<section data-wp-legend-mode="runtime" class="wp-legend-list">${frameworks}${tag('runtime', 'unknown', '#e4e6ec')}<p>Neutral: unknown runtime. Nested: specialization; stripes: multiple runtimes. Context retains kind colors.</p></section>
100
+ <section data-wp-legend-mode="architecture" class="wp-legend-list" hidden>${roles}<p>Auxiliary nodes use kind colors, never project roles.</p></section>` +
101
+ `<section data-wp-legend-mode="impact" class="wp-legend-list" hidden>${new graph_legend_1.GraphLegend().impactRows()}</section><section data-wp-legend-mode="product" class="wp-legend-list" hidden>${products}<p>Stripes: several products; neutral: all products; white/dashed: no product. Context inherits adjacent service memberships.</p></section>` +
102
+ '<p>Queues, clocks, datastores and vendors remain context in Impact; no source ownership or CI status is implied.</p>');
92
103
  }
93
- /**
94
- * Page styles, including the shared menu stylesheet: the clickable cursor + blue glow on every
95
- * box, and the dim/undim rules the lock toggles, scoped to this page's `#graph` host. Those two
96
- * blocks are shared verbatim with architecture/dependencies.html and every design.html.
97
- *
98
- * The legend swatches are hand-drawn inline SVG on purpose: the alternative is shelling out to
99
- * Graphviz at generate time, which would make writing the HTML depend on a `dot` binary being
100
- * installed — a dependency this tool does not otherwise have, since rendering is client-side.
101
- */
102
- styles() {
103
- return `
104
- body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #f5f5f5; }
105
- h1 { text-align: center; color: #333; }
106
- .hint { text-align: center; color: #555; margin: 0 0 16px; }
107
- /* Every box is clickable and opens the shared floating menu, so the menu's own stylesheet
108
- * carries the cursor + blue glow and the dim/undim rules the lock toggles. Shared verbatim
109
- * with architecture/dependencies.html and every project's design.html. */
110
- ${this.nodeMenu.styles()}
111
- ${this.filterAssets.styles()}
112
- ${this.nodeMenu.dimStyles('#graph')}
113
- #graph { text-align: center; background: white; padding: 20px; border-radius: 8px; overflow-x: auto; }
114
- #graph svg { max-width: 100%; height: auto; }
115
- .legend {
116
- margin: 20px auto;
117
- max-width: 1100px;
118
- padding: 15px 20px;
119
- background: white;
120
- border-radius: 8px;
121
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
122
- }
123
- .legend h2 { margin-top: 0; color: #333; }
124
- .legend-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; align-items: start; }
125
- .legend-col h3 { margin: 0 0 10px; color: #333; font-size: 15px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
126
- .legend-item { margin: 9px 0; display: flex; align-items: center; gap: 10px; line-height: 1.4; color: #444; }
127
- /* Prose rows carry no swatch, so they must NOT be flex containers: flex would promote every
128
- * inline <strong>/<em>/<code> to a flex item and shred the sentence into columns. */
129
- .legend-note { margin: 9px 0; line-height: 1.5; color: #444; }
130
- .legend-box-anatomy {
131
- margin: 0 0 12px;
132
- padding: 8px 10px;
133
- background: #f7f7f7;
134
- border-radius: 4px;
135
- font-family: monospace;
136
- font-size: 12px;
137
- line-height: 1.5;
138
- color: #333;
139
- white-space: pre;
140
- overflow-x: auto;
104
+ edgeKey() {
105
+ return '<div class="wp-legend-list"><p>→ RPC · ⇢ queued/event · red: legacy cycle</p><p>Horizontal cylinder: queue<br>Upright cylinder: datastore<br>⏰: scheduler<br>Dashed box: external system</p></div>';
106
+ }
107
+ responsibilities(graph, options) {
108
+ const projects = {};
109
+ for (const [name, service] of Object.entries(graph.services)) {
110
+ projects[name] = {
111
+ ...options.projects?.[name],
112
+ level: service.level,
113
+ dependsOn: service.dependsOn,
114
+ drawOnGraph: service.drawOnGraph,
115
+ };
141
116
  }
142
- .sw { flex: 0 0 auto; display: inline-flex; }
143
- .wp-product-filter { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin: 0 0 14px; }
144
- .wp-product-label { color: #555; margin-right: 4px; }
145
- .wp-product-label small { color: #888; }
146
- .wp-chip { border: 1px solid #b8bcc8; background: white; color: #333; border-radius: 999px; padding: 3px 11px; font: inherit; font-size: 13px; cursor: pointer; }
147
- .wp-chip[aria-pressed="true"] { background: #4b44c8; border-color: #4b44c8; color: white; }
148
- code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }
149
- ${new graph_navigation_1.GraphNavigation(graph_navigation_1.NavigationLayout.TOOLBAR).styles()}
150
- @media (max-width: 900px) { .legend-columns { grid-template-columns: 1fr; } }`;
117
+ return new graph_responsibilities_1.ResponsibilitiesRenderer().generateSection(projects, options.workspaceRoot);
151
118
  }
152
119
  }
153
120
  exports.RuntimeHtmlPage = RuntimeHtmlPage;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-html-page.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-html-page.ts"],"names":[],"mappings":";;;AAAA,6DAA2E;AAC3E,uDAAmD;AAEnD,yDAAuE;AACvE,qDAAiD;AACjD,yDAAkF;AAClF,uDAAkD;AAClD,2DAAiD;AAEjD;;;;;;;;GAQG;AACH,MAAa,eAAe;IAOH;IAEA;IARJ,YAAY,GAAG,IAAI,sCAAiB,EAAE,CAAC;IACvC,QAAQ,GAAG,IAAI,8BAAa,EAAE,CAAC;IAChD,sGAAsG;IACrF,QAAQ,GAAG,IAAI,+BAAa,EAAE,CAAC;IAEhD,YACqB,WAAyB,GAAW,EAAE,CACnD,IAAA,qCAAkB,EAAC,8BAA8B,CAAC,EACrC,WAAyB,GAAW,EAAE,CACnD,IAAA,qCAAkB,EAAC,wBAAwB,CAAC;QAH/B,aAAQ,GAAR,QAAQ,CAC6B;QACrC,aAAQ,GAAR,QAAQ,CACuB;IACjD,CAAC;IAEJ,MAAM,CACF,KAAuB,EACvB,KAAa,EACb,KAAmB,EACnB,oBAA6B,IAAI;QAEjC,OAAO;;;;;;;aAOF,KAAK;;aAEL,IAAI,CAAC,MAAM,EAAE;;;UAGhB,KAAK;MACT,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;;;MAGpB,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;MACxB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE;;MAExB,IAAA,8BAAU,GAAE;cACJ,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;cACtB,IAAI,CAAC,QAAQ,EAAE;cACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;cAClB,IAAI,kCAAe,CAAC,mCAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE;cACtD,IAAI,gCAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,MAAM,EAAE;;QAE3D,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAmB;QAC5B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClD,IAAI,OAAO,CAAC,WAAW,KAAK,KAAK;gBAAE,SAAS;YAC5C,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxE,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC;aACtB,IAAI,EAAE;aACN,GAAG,CACA,CAAC,OAAe,EAAU,EAAE,CACxB,0DAA0D,OAAO,0BAA0B,OAAO,WAAW,CACpH;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,CACH,iGAAiG;YACjG,yEAAyE,KAAK,QAAQ,CACzF,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,KAAuB;QAClC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,2CAAwB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/F,CAAC;IAED;;;;;;;;OAQG;IACK,MAAM;QACV,OAAO;;;;;;;UAOL,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;UACtB,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;UAC1B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAqCjC,IAAI,kCAAe,CAAC,mCAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE;sFACsB,CAAC;IACnF,CAAC;CACJ;AAhJD,0CAgJC","sourcesContent":["import { GraphRenderModel, GraphFilterAssets } from './graph-render-model';\nimport { RuntimeDetails } from './runtime-details';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { GraphNavigation, NavigationLayout } from './graph-navigation';\nimport { SavedSnapshot } from './saved-snapshot';\nimport { CLIENT_MODEL_PLACEHOLDER, readCompiledClient } from './graph-visualizer';\nimport { GraphNodeMenu } from './graph-node-menu';\nimport { legendHtml } from './runtime-viz-theme';\n\n/**\n * The runtime-architecture HTML page: its styles, the shared floating node menu, the graph host\n * and the legend.\n *\n * A CLASS rather than a bag of module functions so the client-text seam can be a constructor\n * parameter the way GraphVisualizer's is: the default reads the COMPILED sibling, which exists in\n * dist and in the published tarball but NOT in a source checkout, and a unit test running from\n * source hands in the text itself instead of requiring the package to have been built first.\n */\nexport class RuntimeHtmlPage {\n private readonly filterAssets = new GraphFilterAssets();\n private readonly snapshot = new SavedSnapshot();\n /** The ONE floating-node-menu implementation, shared with dependencies.html and every design.html. */\n private readonly nodeMenu = new GraphNodeMenu();\n\n constructor(\n private readonly clientJs: () => string = (): string =>\n readCompiledClient('runtime-visualizer.client.js'),\n private readonly filterJs: () => string = (): string =>\n readCompiledClient('graph-filter.client.js'),\n ) {}\n\n render(\n model: GraphRenderModel,\n title: string,\n graph: RuntimeGraph,\n showExternalNodes: boolean = true,\n ): string {\n return `<!DOCTYPE html>\n<html>\n<head>\n <!-- REQUIRED: the cron node's label is a literal ⏰, and the DOT is embedded in this file. With\n no declared charset the browser falls back to a locale guess and renders it as mojibake\n (\"â °\") whenever the page is served without a charset header. -->\n <meta charset=\"utf-8\">\n <title>${title}</title>\n <script src=\"https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js\"></script>\n <style>${this.styles()}</style>\n</head>\n<body>\n <h1>${title}</h1>\n ${this.snapshot.html()}\n <p class=\"hint\">💡 <strong>Click any box</strong> for its menu — <strong>Lock</strong> dims every other box and every arrow so one service, queue, datastore or external system stands alone; <strong>Unlock</strong> restores the whole picture.</p>\n <p class=\"hint\"><strong>Filter Unconnected</strong> keeps incoming and outgoing chains and compacts the picture. <strong>Turn off Filter</strong> restores it.</p>\n ${this.productChips(graph)}\n ${this.filterAssets.html()}\n <div id=\"graph\"></div>\n ${legendHtml()}\n <script>${this.nodeMenu.script()}</script>\n <script>${this.filterJs()}</script>\n <script>${this.script(model)}</script>\n <script>${new GraphNavigation(NavigationLayout.TOOLBAR).script()}</script>\n <script>${new RuntimeDetails(graph, showExternalNodes).script()}</script>\n</body>\n</html>`;\n }\n\n /**\n * The product chip row (#1179): one chip per product a service belongs to. Pressing chips keeps\n * the UNION of their services (each product's tagged services plus everything they reach at\n * runtime) and the queues, triggers and systems attached to them; it intersects with Filter\n * Unconnected. Omitted when no service belongs to a product. The page always opens unfiltered.\n */\n productChips(graph: RuntimeGraph): string {\n const products = new Set<string>();\n for (const service of Object.values(graph.services)) {\n if (service.drawOnGraph === false) continue;\n for (const product of service.products ?? []) products.add(product);\n }\n if (products.size === 0) return '';\n const chips = [...products]\n .sort()\n .map(\n (product: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-product=\"${product}\" aria-pressed=\"false\">${product}</button>`,\n )\n .join('');\n return (\n `<div class=\"wp-product-filter\" id=\"wp-product-filter\" role=\"group\" aria-label=\"Product filter\">` +\n `<span class=\"wp-product-label\">Product <small>any match</small></span>${chips}</div>`\n );\n }\n\n /**\n * The browser half lives in runtime-visualizer.client.ts (matching graph-visualizer.client.ts)\n * rather than in a template literal here: it renders with @viz-js/viz v3, redraws every queue\n * node as a true horizontal cylinder, and wires the shared node menu onto every box — more\n * logic than belongs inline in a .ts string. The substitution is a blind split/join, so the\n * render-model placeholder must appear EXACTLY ONCE in the client.\n */\n private script(model: GraphRenderModel): string {\n return this.clientJs().split(CLIENT_MODEL_PLACEHOLDER).join(this.filterAssets.json(model));\n }\n\n /**\n * Page styles, including the shared menu stylesheet: the clickable cursor + blue glow on every\n * box, and the dim/undim rules the lock toggles, scoped to this page's `#graph` host. Those two\n * blocks are shared verbatim with architecture/dependencies.html and every design.html.\n *\n * The legend swatches are hand-drawn inline SVG on purpose: the alternative is shelling out to\n * Graphviz at generate time, which would make writing the HTML depend on a `dot` binary being\n * installed — a dependency this tool does not otherwise have, since rendering is client-side.\n */\n private styles(): string {\n return `\n body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #f5f5f5; }\n h1 { text-align: center; color: #333; }\n .hint { text-align: center; color: #555; margin: 0 0 16px; }\n /* Every box is clickable and opens the shared floating menu, so the menu's own stylesheet\n * carries the cursor + blue glow and the dim/undim rules the lock toggles. Shared verbatim\n * with architecture/dependencies.html and every project's design.html. */\n ${this.nodeMenu.styles()}\n ${this.filterAssets.styles()}\n ${this.nodeMenu.dimStyles('#graph')}\n #graph { text-align: center; background: white; padding: 20px; border-radius: 8px; overflow-x: auto; }\n #graph svg { max-width: 100%; height: auto; }\n .legend {\n margin: 20px auto;\n max-width: 1100px;\n padding: 15px 20px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n .legend h2 { margin-top: 0; color: #333; }\n .legend-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; align-items: start; }\n .legend-col h3 { margin: 0 0 10px; color: #333; font-size: 15px; border-bottom: 1px solid #eee; padding-bottom: 5px; }\n .legend-item { margin: 9px 0; display: flex; align-items: center; gap: 10px; line-height: 1.4; color: #444; }\n /* Prose rows carry no swatch, so they must NOT be flex containers: flex would promote every\n * inline <strong>/<em>/<code> to a flex item and shred the sentence into columns. */\n .legend-note { margin: 9px 0; line-height: 1.5; color: #444; }\n .legend-box-anatomy {\n margin: 0 0 12px;\n padding: 8px 10px;\n background: #f7f7f7;\n border-radius: 4px;\n font-family: monospace;\n font-size: 12px;\n line-height: 1.5;\n color: #333;\n white-space: pre;\n overflow-x: auto;\n }\n .sw { flex: 0 0 auto; display: inline-flex; }\n .wp-product-filter { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; margin: 0 0 14px; }\n .wp-product-label { color: #555; margin-right: 4px; }\n .wp-product-label small { color: #888; }\n .wp-chip { border: 1px solid #b8bcc8; background: white; color: #333; border-radius: 999px; padding: 3px 11px; font: inherit; font-size: 13px; cursor: pointer; }\n .wp-chip[aria-pressed=\"true\"] { background: #4b44c8; border-color: #4b44c8; color: white; }\n code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\n ${new GraphNavigation(NavigationLayout.TOOLBAR).styles()}\n @media (max-width: 900px) { .legend-columns { grid-template-columns: 1fr; } }`;\n }\n}\n"]}
1
+ {"version":3,"file":"runtime-html-page.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-html-page.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { DesignLink } from './graph-visualizer';\nimport { GraphRenderModel, GraphFilterAssets } from './graph-render-model';\nimport { RuntimeDetails } from './runtime-details';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { RuntimeVizOptions } from './runtime-viz-options';\nimport { GraphNavigation, NavigationLayout } from './graph-navigation';\nimport { SavedSnapshot } from './saved-snapshot';\nimport {\n CLIENT_MODEL_PLACEHOLDER,\n CLIENT_DESIGN_LINKS_PLACEHOLDER,\n readCompiledClient,\n} from './graph-visualizer';\nimport { GraphNodeMenu } from './graph-node-menu';\nimport { GraphPageShell, ShellParts } from './graph-page-shell';\nimport { GraphPageStyles } from './graph-page-styles';\nimport { GraphLegend } from './graph-legend';\nimport { ProductPalette } from './graph-products';\nimport { FRAMEWORK_STYLES, ROLE_STYLES } from './graph-color-modes';\nimport { IMPACT_SIDECAR_SRC } from './graph-impact';\nimport { ResponsibilitiesRenderer } from './graph-responsibilities';\nimport { htmlLabelText } from './dot-syntax';\nimport type { EnhancedGraph } from './graph-sorter';\n\n/** Runtime facts and shape adapter inside the same shell/controller as the project viewer. */\nexport class RuntimeHtmlPage {\n private readonly assets = new GraphFilterAssets();\n private readonly menu = new GraphNodeMenu();\n private readonly navigation = new GraphNavigation(NavigationLayout.FLOATING);\n\n constructor(\n private readonly clientJs: () => string = (): string =>\n readCompiledClient('runtime-visualizer.client.js'),\n private readonly filterJs: () => string = (): string =>\n readCompiledClient('graph-filter.client.js'),\n private readonly sharedJs: () => string = (): string =>\n readCompiledClient('graph-visualizer.client.js'),\n ) {}\n\n render(\n model: GraphRenderModel,\n title: string,\n graph: RuntimeGraph,\n options: RuntimeVizOptions = new RuntimeVizOptions(),\n ): string {\n model.viewer = 'runtime';\n const frameworks = [\n ...new Set(model.nodes.flatMap((node) => node.tags?.frameworks ?? [])),\n ].sort();\n const roles = [\n ...new Set(model.nodes.flatMap((node) => (node.tags === null ? [] : [node.tags.role]))),\n ].sort();\n const products = [...new Set(model.nodes.flatMap((node) => node.products))].sort();\n const parts = new ShellParts(\n title,\n this.lockControl(model),\n this.legend(model),\n this.edgeKey(),\n this.assets.html() + '<span id=\"wp-context-count\"></span>',\n new SavedSnapshot().html(),\n this.responsibilities(graph, options),\n products,\n true,\n frameworks,\n roles,\n '<div class=\"wp-empty\" id=\"wp-empty\" hidden>No matching services. Open Filter and Clear all, or turn off Hide unconnected.</div>',\n );\n const shared = this.sharedJs()\n .split(CLIENT_MODEL_PLACEHOLDER)\n .join(this.assets.json(model))\n .split(CLIENT_DESIGN_LINKS_PLACEHOLDER)\n .join(this.assets.json(this.designLinks(graph, options)));\n return `<!DOCTYPE html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${htmlLabelText(title)}</title>\n<script src=\"https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js\"></script>\n<script src=\"${IMPACT_SIDECAR_SRC}\"></script>\n<style>${this.menu.styles()}${this.menu.dimStyles('#graph')}${this.assets.styles()}${this.navigation.styles()}${new GraphPageStyles().css()}\n.wp-graph-details { position:fixed; z-index:60; max-width:min(520px,calc(100vw - 24px)); max-height:70vh; overflow:auto; background:var(--wp-surface); border:1px solid var(--wp-line); border-radius:10px; padding:14px; box-shadow:0 8px 24px #0002; }\n.wp-graph-details button { cursor:pointer; } .wp-api-detail { cursor:pointer; text-decoration:underline; }\n.wp-api-detail:focus-visible { outline:3px solid var(--wp-accent); } #wp-context-count { font-size:11px; color:var(--wp-muted); }\n.wp-empty { position:absolute; top:70px; left:16px; padding:16px; background:var(--wp-surface); border-radius:10px; }\n</style></head><body data-wp-project-facts=\"${options.projects !== null}\">${new GraphPageShell().body(parts)}\n<script>${this.menu.script()}</script><script>${this.filterJs()}</script><script>${this.navigation.script()}</script>\n<script>${new RuntimeDetails(graph, options.showExternalNodes, model).script()}</script>\n<script>${this.clientJs()}</script><script>${shared}</script></body></html>`;\n }\n\n private designLinks(graph: RuntimeGraph, options: RuntimeVizOptions): DesignLink[] {\n const links: DesignLink[] = [];\n for (const name of Object.keys(graph.services).sort()) {\n if (graph.services[name].drawOnGraph === false) continue;\n const design = options.projects?.[name]?.designFile?.replace(\n /design\\.json$/,\n 'design.html',\n );\n if (\n !design ||\n !options.workspaceRoot ||\n !fs.existsSync(path.join(options.workspaceRoot, design))\n )\n continue;\n links.push(new DesignLink(name, path.posix.relative('tmp/webpieces', design)));\n }\n return links;\n }\n\n private lockControl(model: GraphRenderModel): string {\n const entries = model.nodes\n .map(\n (node) =>\n `<option value=\"${htmlLabelText(node.id)}\">${htmlLabelText(node.id)} · ${node.tags?.role ?? 'context'}</option>`,\n )\n .join('');\n return `<input type=\"search\" id=\"wp-lock\" list=\"wp-lock-options\" placeholder=\"Lock a node… ( / )\" autocomplete=\"off\" spellcheck=\"false\"><datalist id=\"wp-lock-options\">${entries}</datalist>`;\n }\n\n private legend(model: GraphRenderModel): string {\n const tag = (group: string, value: string, color: string): string =>\n `<div class=\"wp-legend-row\" data-wp-legend-group=\"${group}\" data-wp-legend-value=\"${htmlLabelText(value)}\"><svg width=\"20\" height=\"14\"><rect width=\"20\" height=\"14\" rx=\"2\" fill=\"${color}\"/></svg>${htmlLabelText(value)}</div>`;\n const frameworks = Object.values(FRAMEWORK_STYLES)\n .filter((style) => model.legend.frameworks.includes(style.framework))\n .map((style) => tag('runtime', style.framework, style.color))\n .join('');\n const roles = [\n ...new Set(model.nodes.flatMap((node) => (node.tags === null ? [] : [node.tags.role]))),\n ]\n .map((role) =>\n tag(\n 'role',\n role,\n ROLE_STYLES.find((style) => style.role === role)?.fill ?? '#e4e6ec',\n ),\n )\n .join('');\n const products = new ProductPalette(\n model.legend.products.map((count) => count.product),\n ).colors\n .map((color) => tag('product', color.product, color.color))\n .join('');\n return (\n `<section data-wp-legend-mode=\"runtime\" class=\"wp-legend-list\">${frameworks}${tag('runtime', 'unknown', '#e4e6ec')}<p>Neutral: unknown runtime. Nested: specialization; stripes: multiple runtimes. Context retains kind colors.</p></section>\n<section data-wp-legend-mode=\"architecture\" class=\"wp-legend-list\" hidden>${roles}<p>Auxiliary nodes use kind colors, never project roles.</p></section>` +\n `<section data-wp-legend-mode=\"impact\" class=\"wp-legend-list\" hidden>${new GraphLegend().impactRows()}</section><section data-wp-legend-mode=\"product\" class=\"wp-legend-list\" hidden>${products}<p>Stripes: several products; neutral: all products; white/dashed: no product. Context inherits adjacent service memberships.</p></section>` +\n '<p>Queues, clocks, datastores and vendors remain context in Impact; no source ownership or CI status is implied.</p>'\n );\n }\n\n private edgeKey(): string {\n return '<div class=\"wp-legend-list\"><p>→ RPC · ⇢ queued/event · red: legacy cycle</p><p>Horizontal cylinder: queue<br>Upright cylinder: datastore<br>⏰: scheduler<br>Dashed box: external system</p></div>';\n }\n\n private responsibilities(graph: RuntimeGraph, options: RuntimeVizOptions): string {\n const projects: EnhancedGraph = {};\n for (const [name, service] of Object.entries(graph.services)) {\n projects[name] = {\n ...options.projects?.[name],\n level: service.level,\n dependsOn: service.dependsOn,\n drawOnGraph: service.drawOnGraph,\n };\n }\n return new ResponsibilitiesRenderer().generateSection(projects, options.workspaceRoot);\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { EnhancedGraph } from './graph-sorter';
2
+ import type { RuntimeGraph, RuntimeService } from './runtime-graph-model';
3
+ import { GraphRenderModel } from './graph-render-model';
4
+ import { LevelBand } from './graph-level-bands';
5
+ /** Saved project facts enrich presentation without changing runtime derivation or topology. */
6
+ export declare class RuntimeViewerAdapter {
7
+ private readonly graph;
8
+ private readonly projects;
9
+ private readonly styler;
10
+ private readonly palette;
11
+ constructor(graph: RuntimeGraph, projects: EnhancedGraph | null);
12
+ service(model: GraphRenderModel, name: string, service: RuntimeService): string;
13
+ levelBands(model: GraphRenderModel): LevelBand[];
14
+ /** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */
15
+ contextModes(model: GraphRenderModel): void;
16
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeViewerAdapter = void 0;
4
+ const graph_render_model_1 = require("./graph-render-model");
5
+ const graph_color_modes_1 = require("./graph-color-modes");
6
+ const graph_products_1 = require("./graph-products");
7
+ const graph_level_bands_1 = require("./graph-level-bands");
8
+ /** Saved project facts enrich presentation without changing runtime derivation or topology. */
9
+ class RuntimeViewerAdapter {
10
+ graph;
11
+ projects;
12
+ styler = new graph_color_modes_1.NodeModeStyler();
13
+ palette;
14
+ constructor(graph, projects) {
15
+ this.graph = graph;
16
+ this.projects = projects;
17
+ this.palette = new graph_products_1.ProductPalette([
18
+ ...new Set(Object.values(graph.services)
19
+ .filter((service) => service.drawOnGraph !== false)
20
+ .flatMap((service) => service.products ?? [])),
21
+ ].sort());
22
+ }
23
+ service(model, name, service) {
24
+ const project = this.projects?.[name];
25
+ const role = service.role ?? project?.role ?? 'unknown';
26
+ const facts = new graph_color_modes_1.NodeFacts(name, name.split('/').pop() ?? name, service.level, role, project?.framework ?? [], service.products ?? [], `Implements (${service.implements.length})${service.serviceName === undefined ? '' : ` · "${service.serviceName}"`}`);
27
+ this.styler.record(model.legend, facts, this.palette);
28
+ return model.styledNode(facts, this.styler.dots(facts, this.palette));
29
+ }
30
+ levelBands(model) {
31
+ return [...new Set(model.nodes.map((node) => node.tags.level))]
32
+ .sort((a, b) => b - a)
33
+ .map((level) => new graph_level_bands_1.LevelBand(level, model.nodes.filter((node) => node.tags?.level === level).map((node) => node.id)));
34
+ }
35
+ /** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */
36
+ contextModes(model) {
37
+ for (let index = 0; index < model.nodes.length; index++) {
38
+ const node = model.nodes[index];
39
+ if (node.tags !== null)
40
+ continue;
41
+ const colors = node.products.map((product) => this.palette.colorOf(product).color);
42
+ const fill = colors.length === 0
43
+ ? graph_products_1.PRODUCT_NONE_FILL
44
+ : this.palette.sharedByAll(node.products)
45
+ ? graph_products_1.PRODUCT_SHARED_FILL
46
+ : colors.join(':');
47
+ // Graphviz cylinders cannot stripe; a gradient preserves recognizable kind geometry.
48
+ const recolored = node.dot.replace(/fillcolor="[^"]*"/, `fillcolor="${fill}"`);
49
+ const product = colors.length === 0
50
+ ? recolored.replace(/style="[^"]*"/, 'style="filled,dashed"')
51
+ : recolored;
52
+ const modes = new graph_color_modes_1.NodeModeDots(node.dot, node.dot, node.dot, node.dot, node.dot, node.dot, product);
53
+ model.nodes[index] = new graph_render_model_1.RenderNode(node.id, node.dot, modes, null, node.products, node.queueKeys);
54
+ }
55
+ }
56
+ }
57
+ exports.RuntimeViewerAdapter = RuntimeViewerAdapter;
58
+ //# sourceMappingURL=runtime-viewer-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-viewer-adapter.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-viewer-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph, RuntimeService } from './runtime-graph-model';\nimport { GraphRenderModel, RenderNode } from './graph-render-model';\nimport { NodeFacts, NodeModeDots, NodeModeStyler } from './graph-color-modes';\nimport { ProductPalette, PRODUCT_SHARED_FILL, PRODUCT_NONE_FILL } from './graph-products';\nimport { LevelBand } from './graph-level-bands';\n\n/** Saved project facts enrich presentation without changing runtime derivation or topology. */\nexport class RuntimeViewerAdapter {\n private readonly styler = new NodeModeStyler();\n private readonly palette: ProductPalette;\n\n constructor(\n private readonly graph: RuntimeGraph,\n private readonly projects: EnhancedGraph | null,\n ) {\n this.palette = new ProductPalette(\n [\n ...new Set(\n Object.values(graph.services)\n .filter((service) => service.drawOnGraph !== false)\n .flatMap((service) => service.products ?? []),\n ),\n ].sort(),\n );\n }\n\n service(model: GraphRenderModel, name: string, service: RuntimeService): string {\n const project = this.projects?.[name];\n const role = service.role ?? project?.role ?? 'unknown';\n const facts = new NodeFacts(\n name,\n name.split('/').pop() ?? name,\n service.level,\n role,\n project?.framework ?? [],\n service.products ?? [],\n `Implements (${service.implements.length})${service.serviceName === undefined ? '' : ` · \"${service.serviceName}\"`}`,\n );\n this.styler.record(model.legend, facts, this.palette);\n return model.styledNode(facts, this.styler.dots(facts, this.palette));\n }\n\n levelBands(model: GraphRenderModel): LevelBand[] {\n return [...new Set(model.nodes.map((node: RenderNode) => node.tags!.level))]\n .sort((a: number, b: number) => b - a)\n .map(\n (level: number) =>\n new LevelBand(\n level,\n model.nodes.filter((node: RenderNode) => node.tags?.level === level).map((node: RenderNode) => node.id),\n ),\n );\n }\n\n /** Context keeps its shape/kind palette; Impact never paints a queue as a source owner. */\n contextModes(model: GraphRenderModel): void {\n for (let index = 0; index < model.nodes.length; index++) {\n const node = model.nodes[index];\n if (node.tags !== null) continue;\n const colors = node.products.map((product) => this.palette.colorOf(product).color);\n const fill =\n colors.length === 0\n ? PRODUCT_NONE_FILL\n : this.palette.sharedByAll(node.products)\n ? PRODUCT_SHARED_FILL\n : colors.join(':');\n // Graphviz cylinders cannot stripe; a gradient preserves recognizable kind geometry.\n const recolored = node.dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${fill}\"`);\n const product =\n colors.length === 0\n ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"')\n : recolored;\n const modes = new NodeModeDots(\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n node.dot,\n product,\n );\n model.nodes[index] = new RenderNode(\n node.id,\n node.dot,\n modes,\n null,\n node.products,\n node.queueKeys,\n );\n }\n }\n}\n"]}
@@ -29,6 +29,7 @@ declare class QueueCylinders {
29
29
  * inside the very label it is meant to contain.
30
30
  */
31
31
  private reshape;
32
+ private bodyPath;
32
33
  /**
33
34
  * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach
34
35
  * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes
@@ -45,24 +46,13 @@ declare class QueueCylinders {
45
46
  private boundsOf;
46
47
  private pathEl;
47
48
  }
48
- /** Runtime keeps the single-box Lock while Filter follows the full directed chain. */
49
- declare class RuntimeNodeMenu {
50
- private readonly svg;
51
- private readonly lock;
52
- private readonly page;
53
- constructor(svg: SVGSVGElement, lock: WpNodeLock, page: RuntimePage);
54
- wire(): void;
49
+ /** Shape adapter for the shared viewer controller; no separate runtime control state. */
50
+ declare class RuntimeImplementsPayload {
51
+ readonly name: string;
52
+ readonly node: SVGGElement;
53
+ constructor(name: string, node: SVGGElement);
55
54
  }
56
- declare class RuntimePage extends WpFilterPage {
57
- private lock;
58
- /** The pressed product chips (#1179); empty keeps every box. */
59
- private readonly products;
60
- /** Each product chip toggles itself and redraws; the narrowing happens in narrow(). */
61
- protected wireControls(): void;
62
- /** Pressed chips keep the UNION of their products' boxes, intersected with Filter Unconnected. */
63
- protected narrow(retained: Set<string>): Set<string>;
64
- protected usesFullDot(): boolean;
65
- protected captureBinding(svg: SVGSVGElement | null): () => void;
66
- protected prepareSvg(svg: SVGSVGElement): void;
67
- protected wireSvg(svg: SVGSVGElement): void;
55
+ declare class RuntimeViewerBindingImpl implements RuntimeViewerBinding {
56
+ prepare(svg: SVGSVGElement): void;
57
+ implementsItem(name: string, node: SVGGElement): WpNodeMenuItem | null;
68
58
  }