@webpieces/nx-webpieces-rules 0.4.912 → 0.4.914

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 (72) hide show
  1. package/README.md +11 -0
  2. package/executors.json +5 -0
  3. package/package.json +7 -7
  4. package/src/executors/help/executor.js +2 -0
  5. package/src/executors/help/executor.js.map +1 -1
  6. package/src/executors/visualize-runtime/executor.d.ts +3 -1
  7. package/src/executors/visualize-runtime/executor.js +9 -3
  8. package/src/executors/visualize-runtime/executor.js.map +1 -1
  9. package/src/executors/visualize-runtime-goal/executor.d.ts +27 -0
  10. package/src/executors/visualize-runtime-goal/executor.js +98 -0
  11. package/src/executors/visualize-runtime-goal/executor.js.map +1 -0
  12. package/src/executors/visualize-runtime-goal/schema.json +13 -0
  13. package/src/lib/graph-color-modes.d.ts +8 -1
  14. package/src/lib/graph-color-modes.js +11 -1
  15. package/src/lib/graph-color-modes.js.map +1 -1
  16. package/src/lib/graph-filter.client.d.ts +9 -2
  17. package/src/lib/graph-filter.client.js +15 -0
  18. package/src/lib/graph-filter.client.js.map +1 -1
  19. package/src/lib/graph-mode-state.client.d.ts +9 -2
  20. package/src/lib/graph-mode-state.client.js +17 -4
  21. package/src/lib/graph-mode-state.client.js.map +1 -1
  22. package/src/lib/graph-page-shell.js +6 -5
  23. package/src/lib/graph-page-shell.js.map +1 -1
  24. package/src/lib/graph-render-model.d.ts +41 -0
  25. package/src/lib/graph-render-model.js +45 -1
  26. package/src/lib/graph-render-model.js.map +1 -1
  27. package/src/lib/graph-visualizer.client.d.ts +7 -1
  28. package/src/lib/graph-visualizer.client.js +37 -4
  29. package/src/lib/graph-visualizer.client.js.map +1 -1
  30. package/src/lib/runtime-goal-comparison.d.ts +55 -0
  31. package/src/lib/runtime-goal-comparison.js +209 -0
  32. package/src/lib/runtime-goal-comparison.js.map +1 -0
  33. package/src/lib/runtime-goal-details.d.ts +17 -0
  34. package/src/lib/runtime-goal-details.js +118 -0
  35. package/src/lib/runtime-goal-details.js.map +1 -0
  36. package/src/lib/runtime-goal-file.d.ts +72 -0
  37. package/src/lib/runtime-goal-file.js +201 -0
  38. package/src/lib/runtime-goal-file.js.map +1 -0
  39. package/src/lib/runtime-goal-styles.d.ts +10 -0
  40. package/src/lib/runtime-goal-styles.js +25 -0
  41. package/src/lib/runtime-goal-styles.js.map +1 -0
  42. package/src/lib/runtime-goal-validator.d.ts +39 -0
  43. package/src/lib/runtime-goal-validator.js +236 -0
  44. package/src/lib/runtime-goal-validator.js.map +1 -0
  45. package/src/lib/runtime-goal-view.d.ts +22 -0
  46. package/src/lib/runtime-goal-view.js +64 -0
  47. package/src/lib/runtime-goal-view.js.map +1 -0
  48. package/src/lib/runtime-graph-diff.d.ts +107 -0
  49. package/src/lib/runtime-graph-diff.js +346 -0
  50. package/src/lib/runtime-graph-diff.js.map +1 -0
  51. package/src/lib/runtime-html-page.d.ts +2 -0
  52. package/src/lib/runtime-html-page.js +28 -3
  53. package/src/lib/runtime-html-page.js.map +1 -1
  54. package/src/lib/runtime-layout.d.ts +2 -0
  55. package/src/lib/runtime-layout.js +4 -0
  56. package/src/lib/runtime-layout.js.map +1 -1
  57. package/src/lib/runtime-union-graph.d.ts +20 -0
  58. package/src/lib/runtime-union-graph.js +94 -0
  59. package/src/lib/runtime-union-graph.js.map +1 -0
  60. package/src/lib/runtime-visualizer.client.js +7 -3
  61. package/src/lib/runtime-visualizer.client.js.map +1 -1
  62. package/src/lib/runtime-visualizer.d.ts +8 -3
  63. package/src/lib/runtime-visualizer.js +18 -10
  64. package/src/lib/runtime-visualizer.js.map +1 -1
  65. package/src/lib/runtime-viz-options.d.ts +28 -1
  66. package/src/lib/runtime-viz-options.js +36 -2
  67. package/src/lib/runtime-viz-options.js.map +1 -1
  68. package/src/plugin.js +2 -2
  69. package/src/plugin.js.map +1 -1
  70. package/src/runtime-targets.d.ts +6 -0
  71. package/src/runtime-targets.js +17 -0
  72. package/src/runtime-targets.js.map +1 -1
@@ -8,6 +8,10 @@ const graph_visualizer_1 = require("./graph-visualizer");
8
8
  const graph_render_model_1 = require("./graph-render-model");
9
9
  const runtime_details_1 = require("./runtime-details");
10
10
  const runtime_viz_options_1 = require("./runtime-viz-options");
11
+ const runtime_goal_file_1 = require("./runtime-goal-file");
12
+ const runtime_goal_details_1 = require("./runtime-goal-details");
13
+ const runtime_goal_styles_1 = require("./runtime-goal-styles");
14
+ const runtime_graph_diff_1 = require("./runtime-graph-diff");
11
15
  const graph_navigation_1 = require("./graph-navigation");
12
16
  const saved_snapshot_1 = require("./saved-snapshot");
13
17
  const graph_visualizer_2 = require("./graph-visualizer");
@@ -41,7 +45,7 @@ class RuntimeHtmlPage {
41
45
  ...new Set(model.nodes.flatMap((node) => (node.tags === null ? [] : [node.tags.role]))),
42
46
  ].sort();
43
47
  const products = this.shownProducts(model);
44
- 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>');
48
+ 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>', options.page === runtime_viz_options_1.RuntimePageKind.GOAL ? new runtime_goal_file_1.RuntimeGoalProvenance().html() : 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>');
45
49
  const shared = this.sharedJs()
46
50
  .split(graph_visualizer_2.CLIENT_MODEL_PLACEHOLDER)
47
51
  .join(this.assets.json(model))
@@ -52,13 +56,15 @@ class RuntimeHtmlPage {
52
56
  <script src="https://cdn.jsdelivr.net/npm/@viz-js/viz@3.28.0/dist/viz-global.js"></script>
53
57
  <script src="${graph_impact_1.IMPACT_SIDECAR_SRC}"></script>
54
58
  <style>${this.menu.styles()}${this.menu.dimStyles('#graph')}${this.assets.styles()}${this.navigation.styles()}${new graph_page_styles_1.GraphPageStyles().css()}
55
- .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; }
56
- .wp-graph-details button { cursor:pointer; } .wp-api-detail { cursor:pointer; text-decoration:underline; }
59
+ .wp-graph-details, .wp-goal-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; }
60
+ .wp-graph-details button, .wp-goal-details button { cursor:pointer; } .wp-api-detail { cursor:pointer; text-decoration:underline; }
57
61
  .wp-api-detail:focus-visible { outline:3px solid var(--wp-accent); } #wp-context-count { font-size:11px; color:var(--wp-muted); }
62
+ .wp-goal-edge { cursor:pointer; } .wp-goal-diff ul { margin:4px 0 0; padding-left:16px; max-height:220px; overflow:auto; }
58
63
  .wp-empty { position:absolute; top:70px; left:16px; padding:16px; background:var(--wp-surface); border-radius:10px; }
59
64
  </style></head><body data-wp-project-facts="${options.projects !== null}">${new graph_page_shell_1.GraphPageShell().body(parts)}
60
65
  <script>${this.menu.script()}</script><script>${this.filterJs()}</script><script>${this.navigation.script()}</script>
61
66
  <script>${new runtime_details_1.RuntimeDetails(graph, options.showExternalNodes, model).script()}</script>
67
+ <script>${new runtime_goal_details_1.RuntimeGoalDetails(model.comparison).script()}</script>
62
68
  <script>${this.clientJs()}</script><script>${shared}</script></body></html>`;
63
69
  }
64
70
  designLinks(graph, options) {
@@ -98,6 +104,7 @@ class RuntimeHtmlPage {
98
104
  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>
99
105
  <section data-wp-legend-mode="architecture" class="wp-legend-list" hidden>${roles}<p>Auxiliary nodes use kind colors, never project roles.</p></section>` +
100
106
  `<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>` +
107
+ this.goalLegend(model) +
101
108
  '<p>Queues, clocks, datastores and vendors remain context in Impact; no source ownership or CI status is implied.</p>');
102
109
  }
103
110
  /**
@@ -109,6 +116,24 @@ class RuntimeHtmlPage {
109
116
  const shown = new Set(model.nodes.flatMap((node) => node.products));
110
117
  return model.legend.palette.colors.filter((color) => shown.has(color.product));
111
118
  }
119
+ /** Goal vs Current (#1239): the direction, the counts (or why it is unavailable), the key and every difference. */
120
+ goalLegend(model) {
121
+ const comparison = model.comparison;
122
+ if (comparison === null || !comparison.available)
123
+ return `<section data-wp-legend-mode="goal" class="wp-legend-list" hidden><p>Goal vs Current unavailable: ${(0, dot_syntax_1.htmlLabelText)(comparison?.reason ?? 'no comparison data on this page')}</p></section>`;
124
+ const rows = [runtime_graph_diff_1.RuntimeDiffStatus.ADDED, runtime_graph_diff_1.RuntimeDiffStatus.REMOVED, runtime_graph_diff_1.RuntimeDiffStatus.CHANGED, runtime_graph_diff_1.RuntimeDiffStatus.UNCHANGED]
125
+ .map((status) => {
126
+ const style = runtime_goal_styles_1.DIFF_STYLES[status];
127
+ const dash = status === runtime_graph_diff_1.RuntimeDiffStatus.REMOVED ? ' stroke-dasharray="3 2"' : '';
128
+ return `<div class="wp-legend-row wp-goal-row"><svg width="20" height="14"><rect x="1" y="1" width="18" height="12" rx="2" fill="${style.fill}" stroke="${style.border}" stroke-width="2"${dash}/></svg>${style.word}</div>`;
129
+ })
130
+ .join('');
131
+ const differences = comparison.differences.map((line) => `<li>${(0, dot_syntax_1.htmlLabelText)(line)}</li>`).join('');
132
+ return (`<section data-wp-legend-mode="goal" class="wp-legend-list wp-goal-diff" hidden><p><strong>${(0, dot_syntax_1.htmlLabelText)(comparison.direction)}</strong> · ${(0, dot_syntax_1.htmlLabelText)(comparison.summary)}</p>${rows}` +
133
+ '<p>Arrows use the same colors; a removed arrow is dotted. A moved level reads L4 → L2. Click a box (Goal vs Current details) or an arrow for before/after.</p>' +
134
+ (differences === '' ? '' : `<details><summary>Differences (${comparison.differences.length})</summary><ul>${differences}</ul></details>`) +
135
+ '</section>');
136
+ }
112
137
  edgeKey() {
113
138
  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>';
114
139
  }
@@ -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,+CAAyB;AACzB,mDAA6B;AAC7B,yDAAgD;AAChD,6DAA2E;AAC3E,uDAAmD;AAEnD,+DAA0D;AAC1D,yDAAuE;AACvE,qDAAiD;AACjD,yDAI4B;AAC5B,uDAAkD;AAClD,yDAAgE;AAChE,2DAAsD;AACtD,iDAA6C;AAE7C,2DAAoE;AACpE,iDAAoD;AACpD,qEAAoE;AACpE,6CAA6C;AAG7C,8FAA8F;AAC9F,MAAa,eAAe;IAMH;IAEA;IAEA;IATJ,MAAM,GAAG,IAAI,sCAAiB,EAAE,CAAC;IACjC,IAAI,GAAG,IAAI,+BAAa,EAAE,CAAC;IAC3B,UAAU,GAAG,IAAI,kCAAe,CAAC,mCAAgB,CAAC,QAAQ,CAAC,CAAC;IAE7E,YACqB,WAAyB,GAAW,EAAE,CACnD,IAAA,qCAAkB,EAAC,8BAA8B,CAAC,EACrC,WAAyB,GAAW,EAAE,CACnD,IAAA,qCAAkB,EAAC,wBAAwB,CAAC,EAC/B,WAAyB,GAAW,EAAE,CACnD,IAAA,qCAAkB,EAAC,4BAA4B,CAAC;QALnC,aAAQ,GAAR,QAAQ,CAC6B;QACrC,aAAQ,GAAR,QAAQ,CACuB;QAC/B,aAAQ,GAAR,QAAQ,CAC2B;IACrD,CAAC;IAEJ,MAAM,CACF,KAAuB,EACvB,KAAa,EACb,KAAmB,EACnB,UAA6B,IAAI,uCAAiB,EAAE;QAEpD,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,MAAM,UAAU,GAAG;YACf,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC;SACzE,CAAC,IAAI,EAAE,CAAC;QACT,MAAM,KAAK,GAAG;YACV,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC1F,CAAC,IAAI,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,6BAAU,CACxB,KAAK,EACL,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAClB,IAAI,CAAC,OAAO,EAAE,EACd,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,qCAAqC,EAC1D,IAAI,8BAAa,EAAE,CAAC,IAAI,EAAE,EAC1B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,EACrC,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,KAAK,EACL,iIAAiI,CACpI,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE;aACzB,KAAK,CAAC,2CAAwB,CAAC;aAC/B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aAC7B,KAAK,CAAC,kDAA+B,CAAC;aACtC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC9D,OAAO;SACN,IAAA,0BAAa,EAAC,KAAK,CAAC;;eAEd,iCAAkB;SACxB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,IAAI,mCAAe,EAAE,CAAC,GAAG,EAAE;;;;;8CAK7F,OAAO,CAAC,QAAQ,KAAK,IAAI,KAAK,IAAI,iCAAc,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;UAClG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,oBAAoB,IAAI,CAAC,QAAQ,EAAE,oBAAoB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;UACjG,IAAI,gCAAc,CAAC,KAAK,EAAE,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE;UACpE,IAAI,CAAC,QAAQ,EAAE,oBAAoB,MAAM,yBAAyB,CAAC;IACzE,CAAC;IAEO,WAAW,CAAC,KAAmB,EAAE,OAA0B;QAC/D,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACpD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK;gBAAE,SAAS;YACzD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,CACxD,eAAe,EACf,aAAa,CAChB,CAAC;YACF,IACI,CAAC,MAAM;gBACP,CAAC,OAAO,CAAC,aAAa;gBACtB,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAExD,SAAS;YACb,KAAK,CAAC,IAAI,CAAC,IAAI,6BAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,WAAW,CAAC,KAAuB;QACvC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK;aACtB,GAAG,CACA,CAAC,IAAI,EAAE,EAAE,CACL,kBAAkB,IAAA,0BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,KAAK,IAAA,0BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,SAAS,WAAW,CACvH;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,kKAAkK,OAAO,aAAa,CAAC;IAClM,CAAC;IAEO,MAAM,CAAC,KAAuB;QAClC,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,KAAa,EAAU,EAAE,CAChE,oDAAoD,KAAK,2BAA2B,IAAA,0BAAa,EAAC,KAAK,CAAC,2EAA2E,KAAK,YAAY,IAAA,0BAAa,EAAC,KAAK,CAAC,QAAQ,CAAC;QACrO,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,oCAAgB,CAAC;aAC7C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;aACpE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;aAC5D,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,MAAM,KAAK,GAAG;YACV,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC1F;aACI,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACV,GAAG,CACC,MAAM,EACN,IAAI,EACJ,+BAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,SAAS,CACtE,CACJ;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;aACrC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;aAC1D,IAAI,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,CACH,iEAAiE,UAAU,GAAG,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;4EAClD,KAAK,wEAAwE;YAC7I,uEAAuE,IAAI,0BAAW,EAAE,CAAC,UAAU,EAAE,kFAAkF,QAAQ,6IAA6I;YAC5U,sHAAsH,CACzH,CAAC;IACN,CAAC;IAED;;;;OAIG;IACK,aAAa,CAAC,KAAuB;QACzC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAmB,EAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1G,CAAC;IAEO,OAAO;QACX,OAAO,oMAAoM,CAAC;IAChN,CAAC;IAEO,gBAAgB,CAAC,KAAmB,EAAE,OAA0B;QACpE,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,QAAQ,CAAC,IAAI,CAAC,GAAG;gBACb,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;gBAC3B,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,WAAW,EAAE,OAAO,CAAC,WAAW;aACnC,CAAC;QACN,CAAC;QACD,OAAO,IAAI,iDAAwB,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3F,CAAC;CACJ;AAlJD,0CAkJC","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 { ProductColor } 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 = this.shownProducts(model);\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 = this.shownProducts(model)\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 /**\n * The products some drawn box carries, colored from the ONE palette the boxes were painted with —\n * built over every declared product (#1244), so a product keeps the dependency viewer's color even\n * though this page lists only the products it shows.\n */\n private shownProducts(model: GraphRenderModel): ProductColor[] {\n const shown = new Set(model.nodes.flatMap((node) => node.products));\n return model.legend.palette.colors.filter((color: ProductColor): boolean => shown.has(color.product));\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"]}
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, RuntimePageKind } from './runtime-viz-options';\nimport { RuntimeGoalProvenance } from './runtime-goal-file';\nimport { RuntimeGoalDetails } from './runtime-goal-details';\nimport { DIFF_STYLES } from './runtime-goal-styles';\nimport { RuntimeDiffStatus } from './runtime-graph-diff';\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 { ProductColor } 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 = this.shownProducts(model);\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 options.page === RuntimePageKind.GOAL ? new RuntimeGoalProvenance().html() : 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, .wp-goal-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, .wp-goal-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-goal-edge { cursor:pointer; } .wp-goal-diff ul { margin:4px 0 0; padding-left:16px; max-height:220px; overflow:auto; }\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>${new RuntimeGoalDetails(model.comparison).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 = this.shownProducts(model)\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 this.goalLegend(model) +\n '<p>Queues, clocks, datastores and vendors remain context in Impact; no source ownership or CI status is implied.</p>'\n );\n }\n\n /**\n * The products some drawn box carries, colored from the ONE palette the boxes were painted with —\n * built over every declared product (#1244), so a product keeps the dependency viewer's color even\n * though this page lists only the products it shows.\n */\n private shownProducts(model: GraphRenderModel): ProductColor[] {\n const shown = new Set(model.nodes.flatMap((node) => node.products));\n return model.legend.palette.colors.filter((color: ProductColor): boolean => shown.has(color.product));\n }\n\n /** Goal vs Current (#1239): the direction, the counts (or why it is unavailable), the key and every difference. */\n private goalLegend(model: GraphRenderModel): string {\n const comparison = model.comparison;\n if (comparison === null || !comparison.available)\n return `<section data-wp-legend-mode=\"goal\" class=\"wp-legend-list\" hidden><p>Goal vs Current unavailable: ${htmlLabelText(comparison?.reason ?? 'no comparison data on this page')}</p></section>`;\n const rows = [RuntimeDiffStatus.ADDED, RuntimeDiffStatus.REMOVED, RuntimeDiffStatus.CHANGED, RuntimeDiffStatus.UNCHANGED]\n .map((status: RuntimeDiffStatus) => {\n const style = DIFF_STYLES[status];\n const dash = status === RuntimeDiffStatus.REMOVED ? ' stroke-dasharray=\"3 2\"' : '';\n return `<div class=\"wp-legend-row wp-goal-row\"><svg width=\"20\" height=\"14\"><rect x=\"1\" y=\"1\" width=\"18\" height=\"12\" rx=\"2\" fill=\"${style.fill}\" stroke=\"${style.border}\" stroke-width=\"2\"${dash}/></svg>${style.word}</div>`;\n })\n .join('');\n const differences = comparison.differences.map((line: string) => `<li>${htmlLabelText(line)}</li>`).join('');\n return (\n `<section data-wp-legend-mode=\"goal\" class=\"wp-legend-list wp-goal-diff\" hidden><p><strong>${htmlLabelText(comparison.direction)}</strong> · ${htmlLabelText(comparison.summary)}</p>${rows}` +\n '<p>Arrows use the same colors; a removed arrow is dotted. A moved level reads L4 → L2. Click a box (Goal vs Current details) or an arrow for before/after.</p>' +\n (differences === '' ? '' : `<details><summary>Differences (${comparison.differences.length})</summary><ul>${differences}</ul></details>`) +\n '</section>'\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"]}
@@ -2,5 +2,7 @@ import { GraphRenderModel } from './graph-render-model';
2
2
  /** Saved levels own layout; real feedback arrows remain visible without moving ranks. */
3
3
  export declare class RuntimeLayout {
4
4
  render(model: GraphRenderModel): string;
5
+ /** The DOT for the model's current statements, without re-deciding constraints (restyled overlays). */
6
+ assemble(model: GraphRenderModel): string;
5
7
  private unconstrained;
6
8
  }
@@ -17,6 +17,10 @@ class RuntimeLayout {
17
17
  continue;
18
18
  model.edges[index] = new graph_render_model_1.RenderEdge(edge.from, edge.to, this.unconstrained(edge.dot), edge.id, edge.productDot === null ? null : this.unconstrained(edge.productDot));
19
19
  }
20
+ return this.assemble(model);
21
+ }
22
+ /** The DOT for the model's current statements, without re-deciding constraints (restyled overlays). */
23
+ assemble(model) {
20
24
  return (model.header +
21
25
  model.nodes.map((node) => node.dot).join('') +
22
26
  new graph_level_bands_1.LevelBandLayout().dot(model.bands) +
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-layout.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-layout.ts"],"names":[],"mappings":";;;AAAA,6DAAoE;AACpE,2DAAsD;AAEtD,yFAAyF;AACzF,MAAa,aAAa;IACtB,MAAM,CAAC,KAAuB;QAC1B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAE;gBAAE,SAAS;YAC5D,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,+BAAU,CAC/B,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAC5B,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CACxE,CAAC;QACN,CAAC;QACD,OAAO,CACH,KAAK,CAAC,MAAM;YACZ,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,IAAI,mCAAe,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;YACtC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,KAAK,CAAC,MAAM,CACf,CAAC;IACN,CAAC;IAEO,aAAa,CAAC,GAAW;QAC7B,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC;IACvD,CAAC;CACJ;AA7BD,sCA6BC","sourcesContent":["import { GraphRenderModel, RenderEdge } from './graph-render-model';\nimport { LevelBandLayout } from './graph-level-bands';\n\n/** Saved levels own layout; real feedback arrows remain visible without moving ranks. */\nexport class RuntimeLayout {\n render(model: GraphRenderModel): string {\n const levels = new Map<string, number>();\n for (const band of model.bands) {\n for (const name of band.nodeNames) levels.set(name, band.level);\n }\n for (let index = 0; index < model.edges.length; index++) {\n const edge = model.edges[index];\n if (levels.get(edge.from)! > levels.get(edge.to)!) continue;\n model.edges[index] = new RenderEdge(\n edge.from,\n edge.to,\n this.unconstrained(edge.dot),\n edge.id,\n edge.productDot === null ? null : this.unconstrained(edge.productDot),\n );\n }\n return (\n model.header +\n model.nodes.map((node) => node.dot).join('') +\n new LevelBandLayout().dot(model.bands) +\n model.edges.map((edge) => edge.dot).join('') +\n model.footer\n );\n }\n\n private unconstrained(dot: string): string {\n return `${dot.slice(0, -2)} [constraint=false];\\n`;\n }\n}\n"]}
1
+ {"version":3,"file":"runtime-layout.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-layout.ts"],"names":[],"mappings":";;;AAAA,6DAAoE;AACpE,2DAAsD;AAEtD,yFAAyF;AACzF,MAAa,aAAa;IACtB,MAAM,CAAC,KAAuB;QAC1B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpE,CAAC;QACD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAE,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAE;gBAAE,SAAS;YAC5D,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,+BAAU,CAC/B,IAAI,CAAC,IAAI,EACT,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAC5B,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CACxE,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,uGAAuG;IACvG,QAAQ,CAAC,KAAuB;QAC5B,OAAO,CACH,KAAK,CAAC,MAAM;YACZ,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,IAAI,mCAAe,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;YACtC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5C,KAAK,CAAC,MAAM,CACf,CAAC;IACN,CAAC;IAEO,aAAa,CAAC,GAAW;QAC7B,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,wBAAwB,CAAC;IACvD,CAAC;CACJ;AAlCD,sCAkCC","sourcesContent":["import { GraphRenderModel, RenderEdge } from './graph-render-model';\nimport { LevelBandLayout } from './graph-level-bands';\n\n/** Saved levels own layout; real feedback arrows remain visible without moving ranks. */\nexport class RuntimeLayout {\n render(model: GraphRenderModel): string {\n const levels = new Map<string, number>();\n for (const band of model.bands) {\n for (const name of band.nodeNames) levels.set(name, band.level);\n }\n for (let index = 0; index < model.edges.length; index++) {\n const edge = model.edges[index];\n if (levels.get(edge.from)! > levels.get(edge.to)!) continue;\n model.edges[index] = new RenderEdge(\n edge.from,\n edge.to,\n this.unconstrained(edge.dot),\n edge.id,\n edge.productDot === null ? null : this.unconstrained(edge.productDot),\n );\n }\n return this.assemble(model);\n }\n\n /** The DOT for the model's current statements, without re-deciding constraints (restyled overlays). */\n assemble(model: GraphRenderModel): string {\n return (\n model.header +\n model.nodes.map((node) => node.dot).join('') +\n new LevelBandLayout().dot(model.bands) +\n model.edges.map((edge) => edge.dot).join('') +\n model.footer\n );\n }\n\n private unconstrained(dot: string): string {\n return `${dot.slice(0, -2)} [constraint=false];\\n`;\n }\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { RuntimeGraph } from './runtime-graph-model';
2
+ /**
3
+ * The OVERLAY drawn by Goal vs Current (#1239): every goal element, plus every element only the
4
+ * current graph has, so removals stay visible instead of silently disappearing.
5
+ *
6
+ * Presentation only. It is never levelled, never cycle-checked and never saved: an overlay of two
7
+ * architectures can contain a cycle neither input has, and that is not an architectural finding.
8
+ * Every element keeps the level its OWN graph saved — retained and added elements sit at their goal
9
+ * level, removed ones at their current level — so the shared renderer's strict level bands still
10
+ * order the rows and only real upward edges become non-constraining feedback edges.
11
+ */
12
+ export declare class RuntimeUnionGraph {
13
+ build(current: RuntimeGraph, goal: RuntimeGraph): RuntimeGraph;
14
+ /** Edges matched on (transport, from, to, queue); a matched edge carries both graphs' contracts. */
15
+ private edges;
16
+ private missing;
17
+ private externals;
18
+ private remotes;
19
+ private copy;
20
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeUnionGraph = void 0;
4
+ /**
5
+ * The OVERLAY drawn by Goal vs Current (#1239): every goal element, plus every element only the
6
+ * current graph has, so removals stay visible instead of silently disappearing.
7
+ *
8
+ * Presentation only. It is never levelled, never cycle-checked and never saved: an overlay of two
9
+ * architectures can contain a cycle neither input has, and that is not an architectural finding.
10
+ * Every element keeps the level its OWN graph saved — retained and added elements sit at their goal
11
+ * level, removed ones at their current level — so the shared renderer's strict level bands still
12
+ * order the rows and only real upward edges become non-constraining feedback edges.
13
+ */
14
+ class RuntimeUnionGraph {
15
+ build(current, goal) {
16
+ const union = this.copy(goal);
17
+ const old = this.copy(current);
18
+ for (const [name, service] of Object.entries(old.services))
19
+ union.services[name] ??= service;
20
+ for (const [name, api] of Object.entries(old.apis))
21
+ union.apis[name] ??= api;
22
+ for (const [key, queue] of Object.entries(old.queues))
23
+ union.queues[key] ??= queue;
24
+ for (const [api, queue] of Object.entries(old.contractQueues))
25
+ union.contractQueues[api] ??= queue;
26
+ union.runtimeEdges = this.edges(union.runtimeEdges, old.runtimeEdges);
27
+ union.triggers = this.missing(union.triggers, old.triggers, (trigger) => `${trigger.kind} ${trigger.api}.${trigger.method} ${trigger.service}`);
28
+ union.unresolvedUses = this.missing(union.unresolvedUses, old.unresolvedUses, (use) => `${use.service} ${use.api}`);
29
+ this.externals(union, old);
30
+ this.remotes(union, old);
31
+ union.maxLevel = Math.max(union.maxLevel, old.maxLevel);
32
+ return union;
33
+ }
34
+ /** Edges matched on (transport, from, to, queue); a matched edge carries both graphs' contracts. */
35
+ edges(goal, current) {
36
+ const key = (edge) => `${edge.type ?? 'rpc'} ${edge.from} ${edge.to} ${edge.queue ?? ''}`;
37
+ const byKey = new Map();
38
+ for (const edge of goal)
39
+ byKey.set(key(edge), edge);
40
+ const out = [...goal];
41
+ for (const edge of current) {
42
+ const match = byKey.get(key(edge));
43
+ if (match === undefined) {
44
+ byKey.set(key(edge), edge);
45
+ out.push(edge);
46
+ }
47
+ else
48
+ match.via = [...new Set([...match.via, ...edge.via])].sort();
49
+ }
50
+ return out;
51
+ }
52
+ missing(goal, current, key) {
53
+ const seen = new Set(goal.map(key));
54
+ return [...goal, ...current.filter((value) => !seen.has(key(value)))];
55
+ }
56
+ externals(union, old) {
57
+ if (union.externalSystems === undefined && old.externalSystems === undefined)
58
+ return;
59
+ const systems = (union.externalSystems ??= {});
60
+ for (const [id, system] of Object.entries(old.externalSystems ?? {})) {
61
+ const kept = systems[id];
62
+ if (kept === undefined) {
63
+ systems[id] = system;
64
+ continue;
65
+ }
66
+ kept.usedBy = [...new Set([...kept.usedBy, ...system.usedBy])].sort();
67
+ kept.apis = [...new Set([...kept.apis, ...system.apis])].sort();
68
+ }
69
+ }
70
+ remotes(union, old) {
71
+ if (union.remoteServices === undefined && old.remoteServices === undefined)
72
+ return;
73
+ const remotes = (union.remoteServices ??= {});
74
+ for (const [name, remote] of Object.entries(old.remoteServices ?? {})) {
75
+ const kept = remotes[name];
76
+ if (kept === undefined) {
77
+ remotes[name] = remote;
78
+ continue;
79
+ }
80
+ for (const call of remote.calledBy) {
81
+ const match = kept.calledBy.find((entry) => entry.from === call.from && entry.type === call.type);
82
+ if (match === undefined)
83
+ kept.calledBy.push(call);
84
+ else
85
+ match.via = [...new Set([...match.via, ...call.via])].sort();
86
+ }
87
+ }
88
+ }
89
+ copy(graph) {
90
+ return JSON.parse(JSON.stringify(graph));
91
+ }
92
+ }
93
+ exports.RuntimeUnionGraph = RuntimeUnionGraph;
94
+ //# sourceMappingURL=runtime-union-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-union-graph.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-union-graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RuntimeEdge,\n RuntimeGraph,\n RuntimeRemoteCall,\n RuntimeTrigger,\n RuntimeUnresolved,\n} from './runtime-graph-model';\n\n/**\n * The OVERLAY drawn by Goal vs Current (#1239): every goal element, plus every element only the\n * current graph has, so removals stay visible instead of silently disappearing.\n *\n * Presentation only. It is never levelled, never cycle-checked and never saved: an overlay of two\n * architectures can contain a cycle neither input has, and that is not an architectural finding.\n * Every element keeps the level its OWN graph saved — retained and added elements sit at their goal\n * level, removed ones at their current level — so the shared renderer's strict level bands still\n * order the rows and only real upward edges become non-constraining feedback edges.\n */\nexport class RuntimeUnionGraph {\n build(current: RuntimeGraph, goal: RuntimeGraph): RuntimeGraph {\n const union = this.copy(goal);\n const old = this.copy(current);\n for (const [name, service] of Object.entries(old.services)) union.services[name] ??= service;\n for (const [name, api] of Object.entries(old.apis)) union.apis[name] ??= api;\n for (const [key, queue] of Object.entries(old.queues)) union.queues[key] ??= queue;\n for (const [api, queue] of Object.entries(old.contractQueues)) union.contractQueues[api] ??= queue;\n union.runtimeEdges = this.edges(union.runtimeEdges, old.runtimeEdges);\n union.triggers = this.missing(union.triggers, old.triggers, (trigger: RuntimeTrigger) => `${trigger.kind} ${trigger.api}.${trigger.method} ${trigger.service}`);\n union.unresolvedUses = this.missing(union.unresolvedUses, old.unresolvedUses, (use: RuntimeUnresolved) => `${use.service} ${use.api}`);\n this.externals(union, old);\n this.remotes(union, old);\n union.maxLevel = Math.max(union.maxLevel, old.maxLevel);\n return union;\n }\n\n /** Edges matched on (transport, from, to, queue); a matched edge carries both graphs' contracts. */\n private edges(goal: RuntimeEdge[], current: RuntimeEdge[]): RuntimeEdge[] {\n const key = (edge: RuntimeEdge): string => `${edge.type ?? 'rpc'} ${edge.from} ${edge.to} ${edge.queue ?? ''}`;\n const byKey = new Map<string, RuntimeEdge>();\n for (const edge of goal) byKey.set(key(edge), edge);\n const out = [...goal];\n for (const edge of current) {\n const match = byKey.get(key(edge));\n if (match === undefined) {\n byKey.set(key(edge), edge);\n out.push(edge);\n } else match.via = [...new Set([...match.via, ...edge.via])].sort();\n }\n return out;\n }\n\n private missing<T>(goal: T[], current: T[], key: (value: T) => string): T[] {\n const seen = new Set(goal.map(key));\n return [...goal, ...current.filter((value: T) => !seen.has(key(value)))];\n }\n\n private externals(union: RuntimeGraph, old: RuntimeGraph): void {\n if (union.externalSystems === undefined && old.externalSystems === undefined) return;\n const systems = (union.externalSystems ??= {});\n for (const [id, system] of Object.entries(old.externalSystems ?? {})) {\n const kept = systems[id];\n if (kept === undefined) {\n systems[id] = system;\n continue;\n }\n kept.usedBy = [...new Set([...kept.usedBy, ...system.usedBy])].sort();\n kept.apis = [...new Set([...kept.apis, ...system.apis])].sort();\n }\n }\n\n private remotes(union: RuntimeGraph, old: RuntimeGraph): void {\n if (union.remoteServices === undefined && old.remoteServices === undefined) return;\n const remotes = (union.remoteServices ??= {});\n for (const [name, remote] of Object.entries(old.remoteServices ?? {})) {\n const kept = remotes[name];\n if (kept === undefined) {\n remotes[name] = remote;\n continue;\n }\n for (const call of remote.calledBy) {\n const match = kept.calledBy.find((entry: RuntimeRemoteCall) => entry.from === call.from && entry.type === call.type);\n if (match === undefined) kept.calledBy.push(call);\n else match.via = [...new Set([...match.via, ...call.via])].sort();\n }\n }\n }\n\n private copy(graph: RuntimeGraph): RuntimeGraph {\n return JSON.parse(JSON.stringify(graph)) as RuntimeGraph;\n }\n}\n"]}
@@ -82,6 +82,8 @@ class QueueCylinders {
82
82
  const first = shapes[0];
83
83
  const fill = first.getAttribute('fill') ?? 'none';
84
84
  const stroke = first.getAttribute('stroke') ?? 'black';
85
+ // A dashed outline (Goal vs Current's removed queue, #1239) survives the redraw.
86
+ const dash = first.getAttribute('stroke-dasharray');
85
87
  // Measured BEFORE the shapes go: the record separator is where the label text starts.
86
88
  const separator = this.separatorX(shapes);
87
89
  shapes.forEach((shape) => {
@@ -107,8 +109,8 @@ class QueueCylinders {
107
109
  const anchor = node.firstChild;
108
110
  if (anchor === null || anchor.nextSibling === null)
109
111
  return;
110
- node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);
111
- node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);
112
+ node.insertBefore(this.pathEl(fill, stroke, body, dash), anchor.nextSibling);
113
+ node.insertBefore(this.pathEl('none', stroke, cap, dash), anchor.nextSibling.nextSibling);
112
114
  // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty
113
115
  // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the
114
116
  // cap lands. Shifting it again double-counts that offset and pushes the longest line out
@@ -193,10 +195,12 @@ class QueueCylinders {
193
195
  return null;
194
196
  return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));
195
197
  }
196
- pathEl(fill, stroke, d) {
198
+ pathEl(fill, stroke, d, dash) {
197
199
  const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');
198
200
  el.setAttribute('fill', fill);
199
201
  el.setAttribute('stroke', stroke);
202
+ if (dash !== null)
203
+ el.setAttribute('stroke-dasharray', dash);
200
204
  el.setAttribute('d', d);
201
205
  return el;
202
206
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => {\n this.reshape(node);\n });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => {\n shape.remove();\n });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body = this.bodyPath(box, rx, ry);\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n private bodyPath(box: Bounds, rx: number, ry: number): string {\n return (\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' L' +\n (box.x1 - rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x1 - rx) +\n ',' +\n box.y1 +\n ' L' +\n (box.x0 + rx) +\n ',' +\n box.y1 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' Z'\n );\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Shape adapter for the shared viewer controller; no separate runtime control state. */\nclass RuntimeImplementsPayload {\n constructor(\n readonly name: string,\n readonly node: SVGGElement,\n ) {}\n}\n\nclass RuntimeViewerBindingImpl implements RuntimeViewerBinding {\n prepare(svg: SVGSVGElement): void {\n new QueueCylinders().applyTo(svg);\n // Keep identity titles readable by shared controllers without native SVG tooltips.\n svg.querySelectorAll('g.node > title, g.edge > title').forEach((title) => {\n title.parentElement?.setAttribute('aria-label', title.textContent ?? '');\n const identity = document.createElement('title');\n identity.textContent = title.textContent;\n title.replaceWith(identity);\n });\n }\n implementsItem(name: string, node: SVGGElement): WpNodeMenuItem | null {\n if (\n !node.querySelector('text') ||\n !Array.from(node.querySelectorAll('text')).some((text) =>\n text.textContent?.startsWith('Implements ('),\n )\n )\n return null;\n return new WpNodeMenuItem('Implements', (): void => {\n document.dispatchEvent(\n new CustomEvent('wp-runtime-implements', {\n detail: new RuntimeImplementsPayload(name, node),\n }),\n );\n });\n }\n}\nwindow.__WP_RUNTIME__ = new RuntimeViewerBindingImpl();\n"]}
1
+ {"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => {\n this.reshape(node);\n });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // A dashed outline (Goal vs Current's removed queue, #1239) survives the redraw.\n const dash = first.getAttribute('stroke-dasharray');\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => {\n shape.remove();\n });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body = this.bodyPath(box, rx, ry);\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body, dash), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap, dash), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n private bodyPath(box: Bounds, rx: number, ry: number): string {\n return (\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' L' +\n (box.x1 - rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x1 - rx) +\n ',' +\n box.y1 +\n ' L' +\n (box.x0 + rx) +\n ',' +\n box.y1 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' Z'\n );\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string, dash: string | null): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n if (dash !== null) el.setAttribute('stroke-dasharray', dash);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Shape adapter for the shared viewer controller; no separate runtime control state. */\nclass RuntimeImplementsPayload {\n constructor(\n readonly name: string,\n readonly node: SVGGElement,\n ) {}\n}\n\nclass RuntimeViewerBindingImpl implements RuntimeViewerBinding {\n prepare(svg: SVGSVGElement): void {\n new QueueCylinders().applyTo(svg);\n // Keep identity titles readable by shared controllers without native SVG tooltips.\n svg.querySelectorAll('g.node > title, g.edge > title').forEach((title) => {\n title.parentElement?.setAttribute('aria-label', title.textContent ?? '');\n const identity = document.createElement('title');\n identity.textContent = title.textContent;\n title.replaceWith(identity);\n });\n }\n implementsItem(name: string, node: SVGGElement): WpNodeMenuItem | null {\n if (\n !node.querySelector('text') ||\n !Array.from(node.querySelectorAll('text')).some((text) =>\n text.textContent?.startsWith('Implements ('),\n )\n )\n return null;\n return new WpNodeMenuItem('Implements', (): void => {\n document.dispatchEvent(\n new CustomEvent('wp-runtime-implements', {\n detail: new RuntimeImplementsPayload(name, node),\n }),\n );\n });\n }\n}\nwindow.__WP_RUNTIME__ = new RuntimeViewerBindingImpl();\n"]}
@@ -1,7 +1,8 @@
1
1
  import type { RuntimeGraph } from './runtime-graph';
2
2
  import { GraphRenderModel } from './graph-render-model';
3
3
  import { RuntimeVizOptions } from './runtime-viz-options';
4
- export { RuntimeVizOptions } from './runtime-viz-options';
4
+ import type { RuntimeComparisonView } from './graph-render-model';
5
+ export { RuntimeVizOptions, RuntimePageKind } from './runtime-viz-options';
5
6
  /** Build the Graphviz DOT for the runtime service graph. */
6
7
  export declare function generateRuntimeDot(graph: RuntimeGraph, title?: string, options?: RuntimeVizOptions): string;
7
8
  /** Capture synthetic drawable nodes and edges at their original emission sites. */
@@ -10,5 +11,9 @@ export interface RuntimeVisualizationPaths {
10
11
  dotPath: string;
11
12
  htmlPath: string;
12
13
  }
13
- /** Write the DOT + HTML renderings to tmp/webpieces/. */
14
- export declare function writeRuntimeVisualization(graph: RuntimeGraph, workspaceRoot: string, title?: string, options?: RuntimeVizOptions): RuntimeVisualizationPaths;
14
+ /**
15
+ * Write the DOT + HTML renderings to tmp/webpieces/: `runtime-architecture.*` for the current graph,
16
+ * `runtime-architecture-goal.*` for the goal (#1239), so neither page ever overwrites the other.
17
+ * `comparison` is the Goal vs Current overlay the page carries (or why it is unavailable).
18
+ */
19
+ export declare function writeRuntimeVisualization(graph: RuntimeGraph, workspaceRoot: string, title: string, options: RuntimeVizOptions, comparison: RuntimeComparisonView): RuntimeVisualizationPaths;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RuntimeVizOptions = void 0;
3
+ exports.RuntimePageKind = exports.RuntimeVizOptions = void 0;
4
4
  exports.generateRuntimeDot = generateRuntimeDot;
5
5
  exports.generateRuntimeRenderModel = generateRuntimeRenderModel;
6
6
  exports.writeRuntimeVisualization = writeRuntimeVisualization;
@@ -67,6 +67,7 @@ const PAIR_SEP = '|';
67
67
  const runtime_viz_options_1 = require("./runtime-viz-options");
68
68
  var runtime_viz_options_2 = require("./runtime-viz-options");
69
69
  Object.defineProperty(exports, "RuntimeVizOptions", { enumerable: true, get: function () { return runtime_viz_options_2.RuntimeVizOptions; } });
70
+ Object.defineProperty(exports, "RuntimePageKind", { enumerable: true, get: function () { return runtime_viz_options_2.RuntimePageKind; } });
70
71
  /**
71
72
  * The DISPLAY name of a project/service: scope stripped. LABELS ONLY.
72
73
  *
@@ -176,7 +177,7 @@ function queueBoxDot(id, body, producers, consumers, model, queueKeys, level) {
176
177
  * anything else stays separate, because merging it would assert a routing that does not exist.
177
178
  */
178
179
  // webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
179
- function groupQueues(byQueue, graph) {
180
+ function groupQueues(byQueue, graph, tags) {
180
181
  const groups = new Map();
181
182
  // Sorted keys → the first member of every group, and therefore its node id, is deterministic.
182
183
  for (const key of [...byQueue.keys()].sort()) {
@@ -189,6 +190,7 @@ function groupQueues(byQueue, graph) {
189
190
  graph.queues[key].level,
190
191
  producers.join(','),
191
192
  consumers.join(','),
193
+ tags[key] ?? '',
192
194
  ].join(PAIR_SEP);
193
195
  const existing = groups.get(mergeKey);
194
196
  if (existing === undefined)
@@ -211,7 +213,7 @@ function groupQueues(byQueue, graph) {
211
213
  * one is stuck) at a fraction of the node count.
212
214
  */
213
215
  // webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
214
- function queuesDot(graph, hidden, model) {
216
+ function queuesDot(graph, hidden, model, options) {
215
217
  const queued = graph.runtimeEdges.filter((e) => e.type === 'pubsub' && !hidden.has(e.from) && !hidden.has(e.to));
216
218
  if (queued.length === 0)
217
219
  return '';
@@ -234,7 +236,7 @@ function queuesDot(graph, hidden, model) {
234
236
  const ends = byContract.get(api);
235
237
  dot += queueBoxDot(`queue__${dotId(api)}`, `${(0, dot_syntax_1.recordValue)(getShortName(api))} queue`, [...ends.producers].sort(), [...ends.consumers].sort(), model, [`queue:${api}`], graph.contractQueues[api].level);
236
238
  }
237
- for (const group of groupQueues(byQueue, graph)) {
239
+ for (const group of groupQueues(byQueue, graph, options.queueGroupTags)) {
238
240
  const body = group.members
239
241
  .map((key) => queueLine(key, graph.queues[key]))
240
242
  .join('\\n');
@@ -485,7 +487,7 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
485
487
  continue;
486
488
  dot += model.edge(edge.from, edge.to, edgeDot(edge));
487
489
  }
488
- dot += queuesDot(graph, hidden, model);
490
+ dot += queuesDot(graph, hidden, model, options);
489
491
  dot += remoteServicesDot(graph, hidden, model);
490
492
  dot += triggerDot(graph, hidden, model);
491
493
  if (options.showExternalNodes) {
@@ -495,7 +497,7 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
495
497
  model.bands = adapter.levelBands(model);
496
498
  const footerStart = dot.length;
497
499
  dot += '\n labelloc="t";\n';
498
- dot += ` label="${(0, dot_syntax_1.dotValue)(title)}\\n(from architecture/runtime-dependencies.json)";\n`;
500
+ dot += ` label="${(0, dot_syntax_1.dotValue)(title)}\\n(from ${(0, dot_syntax_1.dotValue)(options.source())})";\n`;
499
501
  dot += ' fontsize=20;\n';
500
502
  dot += '}\n';
501
503
  // Nothing downstream parses this DOT until a human opens the page, so parse-shape is checked
@@ -509,16 +511,22 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
509
511
  (0, dot_syntax_1.assertValidDot)(model.fullDot, 'runtime-architecture.dot');
510
512
  return model;
511
513
  }
512
- /** Write the DOT + HTML renderings to tmp/webpieces/. */
513
- function writeRuntimeVisualization(graph, workspaceRoot, title = 'WebPieces Runtime Architecture', options = new runtime_viz_options_1.RuntimeVizOptions()) {
514
+ /**
515
+ * Write the DOT + HTML renderings to tmp/webpieces/: `runtime-architecture.*` for the current graph,
516
+ * `runtime-architecture-goal.*` for the goal (#1239), so neither page ever overwrites the other.
517
+ * `comparison` is the Goal vs Current overlay the page carries (or why it is unavailable).
518
+ */
519
+ function writeRuntimeVisualization(graph, workspaceRoot, title, options, comparison) {
514
520
  const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');
515
521
  if (!fs.existsSync(outputDir))
516
522
  fs.mkdirSync(outputDir, { recursive: true });
517
523
  const model = generateRuntimeRenderModel(graph, title, options);
524
+ model.comparison = comparison;
518
525
  const dot = model.fullDot;
519
- const dotPath = path.join(outputDir, 'runtime-architecture.dot');
526
+ const base = options.page === runtime_viz_options_1.RuntimePageKind.GOAL ? 'runtime-architecture-goal' : 'runtime-architecture';
527
+ const dotPath = path.join(outputDir, `${base}.dot`);
520
528
  fs.writeFileSync(dotPath, dot, 'utf-8');
521
- const htmlPath = path.join(outputDir, 'runtime-architecture.html');
529
+ const htmlPath = path.join(outputDir, `${base}.html`);
522
530
  fs.writeFileSync(htmlPath, new runtime_html_page_1.RuntimeHtmlPage().render(model, title, graph, options), 'utf-8');
523
531
  return { dotPath, htmlPath };
524
532
  }