@webpieces/nx-webpieces-rules 0.4.853 → 0.4.855

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +14 -0
  2. package/executors.json +5 -0
  3. package/package.json +7 -7
  4. package/src/executors/generate/executor.js +31 -55
  5. package/src/executors/generate/executor.js.map +1 -1
  6. package/src/executors/runtime-wiring-check/executor.d.ts +10 -0
  7. package/src/executors/runtime-wiring-check/executor.js +42 -0
  8. package/src/executors/runtime-wiring-check/executor.js.map +1 -0
  9. package/src/executors/runtime-wiring-check/schema.json +14 -0
  10. package/src/executors/validate-api-lib-tag/executor.js +5 -3
  11. package/src/executors/validate-api-lib-tag/executor.js.map +1 -1
  12. package/src/executors/validate-api-relations/executor.js +11 -8
  13. package/src/executors/validate-api-relations/executor.js.map +1 -1
  14. package/src/executors/validate-architecture-unchanged/executor.d.ts +1 -1
  15. package/src/executors/validate-architecture-unchanged/executor.js +22 -24
  16. package/src/executors/validate-architecture-unchanged/executor.js.map +1 -1
  17. package/src/index.d.ts +1 -0
  18. package/src/index.js +5 -1
  19. package/src/index.js.map +1 -1
  20. package/src/lib/api-usage/api-contract-approval.d.ts +7 -0
  21. package/src/lib/api-usage/api-contract-approval.js +50 -0
  22. package/src/lib/api-usage/api-contract-approval.js.map +1 -0
  23. package/src/lib/api-usage/api-contract-evidence.d.ts +6 -0
  24. package/src/lib/api-usage/api-contract-evidence.js +41 -0
  25. package/src/lib/api-usage/api-contract-evidence.js.map +1 -0
  26. package/src/lib/api-usage/api-relations-validator.d.ts +13 -2
  27. package/src/lib/api-usage/api-relations-validator.js +23 -0
  28. package/src/lib/api-usage/api-relations-validator.js.map +1 -1
  29. package/src/lib/api-usage/api-relations.d.ts +3 -0
  30. package/src/lib/api-usage/api-relations.js.map +1 -1
  31. package/src/lib/api-usage/api-scanner.d.ts +40 -0
  32. package/src/lib/api-usage/api-scanner.js +7 -1
  33. package/src/lib/api-usage/api-scanner.js.map +1 -1
  34. package/src/lib/graph-comparator.js +11 -1
  35. package/src/lib/graph-comparator.js.map +1 -1
  36. package/src/lib/graph-filter.client.d.ts +40 -0
  37. package/src/lib/graph-filter.client.js +190 -0
  38. package/src/lib/graph-filter.client.js.map +1 -0
  39. package/src/lib/graph-level-bands.d.ts +0 -18
  40. package/src/lib/graph-level-bands.js +4 -3
  41. package/src/lib/graph-level-bands.js.map +1 -1
  42. package/src/lib/graph-loader.js +12 -6
  43. package/src/lib/graph-loader.js.map +1 -1
  44. package/src/lib/graph-navigation.d.ts +5 -0
  45. package/src/lib/graph-navigation.js +102 -0
  46. package/src/lib/graph-navigation.js.map +1 -0
  47. package/src/lib/graph-node-menu.d.ts +1 -0
  48. package/src/lib/graph-node-menu.js +29 -0
  49. package/src/lib/graph-node-menu.js.map +1 -1
  50. package/src/lib/graph-render-model.d.ts +32 -0
  51. package/src/lib/graph-render-model.js +76 -0
  52. package/src/lib/graph-render-model.js.map +1 -0
  53. package/src/lib/graph-sorter.d.ts +2 -0
  54. package/src/lib/graph-sorter.js.map +1 -1
  55. package/src/lib/graph-visualizer.client.d.ts +14 -73
  56. package/src/lib/graph-visualizer.client.js +71 -243
  57. package/src/lib/graph-visualizer.client.js.map +1 -1
  58. package/src/lib/graph-visualizer.d.ts +8 -15
  59. package/src/lib/graph-visualizer.js +48 -26
  60. package/src/lib/graph-visualizer.js.map +1 -1
  61. package/src/lib/runtime-details.d.ts +13 -0
  62. package/src/lib/runtime-details.js +191 -0
  63. package/src/lib/runtime-details.js.map +1 -0
  64. package/src/lib/runtime-graph-model.d.ts +4 -1
  65. package/src/lib/runtime-graph-model.js.map +1 -1
  66. package/src/lib/runtime-graph.js +10 -4
  67. package/src/lib/runtime-graph.js.map +1 -1
  68. package/src/lib/runtime-html-page.d.ts +7 -3
  69. package/src/lib/runtime-html-page.js +19 -6
  70. package/src/lib/runtime-html-page.js.map +1 -1
  71. package/src/lib/runtime-visualizer.client.d.ts +8 -19
  72. package/src/lib/runtime-visualizer.client.js +28 -41
  73. package/src/lib/runtime-visualizer.client.js.map +1 -1
  74. package/src/lib/runtime-visualizer.d.ts +3 -45
  75. package/src/lib/runtime-visualizer.js +77 -79
  76. package/src/lib/runtime-visualizer.js.map +1 -1
  77. package/src/lib/runtime-viz-theme.js +4 -3
  78. package/src/lib/runtime-viz-theme.js.map +1 -1
  79. package/src/lib/runtime-wiring/DeclarationCompileAssertions.d.ts +4 -0
  80. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js +27 -0
  81. package/src/lib/runtime-wiring/DeclarationCompileAssertions.js.map +1 -0
  82. package/src/lib/runtime-wiring/approved-graph.d.ts +8 -0
  83. package/src/lib/runtime-wiring/approved-graph.js +115 -0
  84. package/src/lib/runtime-wiring/approved-graph.js.map +1 -0
  85. package/src/lib/runtime-wiring/assembler.d.ts +24 -0
  86. package/src/lib/runtime-wiring/assembler.js +116 -0
  87. package/src/lib/runtime-wiring/assembler.js.map +1 -0
  88. package/src/lib/runtime-wiring/codec.d.ts +14 -0
  89. package/src/lib/runtime-wiring/codec.js +162 -0
  90. package/src/lib/runtime-wiring/codec.js.map +1 -0
  91. package/src/lib/runtime-wiring/declaration.d.ts +68 -0
  92. package/src/lib/runtime-wiring/declaration.js +88 -0
  93. package/src/lib/runtime-wiring/declaration.js.map +1 -0
  94. package/src/lib/runtime-wiring/source-extractor.d.ts +28 -0
  95. package/src/lib/runtime-wiring/source-extractor.js +253 -0
  96. package/src/lib/runtime-wiring/source-extractor.js.map +1 -0
  97. package/src/lib/runtime-wiring/source-values.d.ts +21 -0
  98. package/src/lib/runtime-wiring/source-values.js +204 -0
  99. package/src/lib/runtime-wiring/source-values.js.map +1 -0
  100. package/src/lib/runtime-wiring/verification.d.ts +9 -0
  101. package/src/lib/runtime-wiring/verification.js +56 -0
  102. package/src/lib/runtime-wiring/verification.js.map +1 -0
  103. package/src/plugin.d.ts +0 -21
  104. package/src/plugin.js +12 -7
  105. package/src/plugin.js.map +1 -1
  106. package/src/runtime-wiring-targets.d.ts +7 -0
  107. package/src/runtime-wiring-targets.js +58 -0
  108. package/src/runtime-wiring-targets.js.map +1 -0
  109. package/src/validation-targets.js +10 -2
  110. package/src/validation-targets.js.map +1 -1
@@ -1,3 +1,5 @@
1
+ import { GraphRenderModel } from './graph-render-model';
2
+ import type { RuntimeGraph } from './runtime-graph-model';
1
3
  /**
2
4
  * The runtime-architecture HTML page: its styles, the shared floating node menu, the graph host
3
5
  * and the legend.
@@ -9,17 +11,19 @@
9
11
  */
10
12
  export declare class RuntimeHtmlPage {
11
13
  private readonly clientJs;
14
+ private readonly filterJs;
15
+ private readonly filterAssets;
12
16
  private readonly snapshot;
13
17
  /** The ONE floating-node-menu implementation, shared with dependencies.html and every design.html. */
14
18
  private readonly nodeMenu;
15
- constructor(clientJs?: () => string);
16
- render(dot: string, title: string): string;
19
+ constructor(clientJs?: () => string, filterJs?: () => string);
20
+ render(model: GraphRenderModel, title: string, graph: RuntimeGraph, showExternalNodes?: boolean): string;
17
21
  /**
18
22
  * The browser half lives in runtime-visualizer.client.ts (matching graph-visualizer.client.ts)
19
23
  * rather than in a template literal here: it renders with @viz-js/viz v3, redraws every queue
20
24
  * node as a true horizontal cylinder, and wires the shared node menu onto every box — more
21
25
  * logic than belongs inline in a .ts string. The substitution is a blind split/join, so the
22
- * placeholder must appear EXACTLY ONCE in the client.
26
+ * render-model placeholder must appear EXACTLY ONCE in the client.
23
27
  */
24
28
  private script;
25
29
  /**
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeHtmlPage = void 0;
4
+ const graph_render_model_1 = require("./graph-render-model");
5
+ const runtime_details_1 = require("./runtime-details");
6
+ const graph_navigation_1 = require("./graph-navigation");
4
7
  const saved_snapshot_1 = require("./saved-snapshot");
5
8
  const graph_visualizer_1 = require("./graph-visualizer");
6
9
  const graph_node_menu_1 = require("./graph-node-menu");
@@ -16,13 +19,16 @@ const runtime_viz_theme_1 = require("./runtime-viz-theme");
16
19
  */
17
20
  class RuntimeHtmlPage {
18
21
  clientJs;
22
+ filterJs;
23
+ filterAssets = new graph_render_model_1.GraphFilterAssets();
19
24
  snapshot = new saved_snapshot_1.SavedSnapshot();
20
25
  /** The ONE floating-node-menu implementation, shared with dependencies.html and every design.html. */
21
26
  nodeMenu = new graph_node_menu_1.GraphNodeMenu();
22
- constructor(clientJs = () => (0, graph_visualizer_1.readCompiledClient)('runtime-visualizer.client.js')) {
27
+ constructor(clientJs = () => (0, graph_visualizer_1.readCompiledClient)('runtime-visualizer.client.js'), filterJs = () => (0, graph_visualizer_1.readCompiledClient)('graph-filter.client.js')) {
23
28
  this.clientJs = clientJs;
29
+ this.filterJs = filterJs;
24
30
  }
25
- render(dot, title) {
31
+ render(model, title, graph, showExternalNodes = true) {
26
32
  return `<!DOCTYPE html>
27
33
  <html>
28
34
  <head>
@@ -38,10 +44,15 @@ class RuntimeHtmlPage {
38
44
  <h1>${title}</h1>
39
45
  ${this.snapshot.html()}
40
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.filterAssets.html()}
41
49
  <div id="graph"></div>
42
50
  ${(0, runtime_viz_theme_1.legendHtml)()}
43
51
  <script>${this.nodeMenu.script()}</script>
44
- <script>${this.script(dot)}</script>
52
+ <script>${this.filterJs()}</script>
53
+ <script>${this.script(model)}</script>
54
+ <script>${new graph_navigation_1.GraphNavigation().script()}</script>
55
+ <script>${new runtime_details_1.RuntimeDetails(graph, showExternalNodes).script()}</script>
45
56
  </body>
46
57
  </html>`;
47
58
  }
@@ -50,10 +61,10 @@ class RuntimeHtmlPage {
50
61
  * rather than in a template literal here: it renders with @viz-js/viz v3, redraws every queue
51
62
  * node as a true horizontal cylinder, and wires the shared node menu onto every box — more
52
63
  * logic than belongs inline in a .ts string. The substitution is a blind split/join, so the
53
- * placeholder must appear EXACTLY ONCE in the client.
64
+ * render-model placeholder must appear EXACTLY ONCE in the client.
54
65
  */
55
- script(dot) {
56
- return this.clientJs().split(graph_visualizer_1.CLIENT_DOT_PLACEHOLDER).join(JSON.stringify(dot));
66
+ script(model) {
67
+ return this.clientJs().split(graph_visualizer_1.CLIENT_MODEL_PLACEHOLDER).join(this.filterAssets.json(model));
57
68
  }
58
69
  /**
59
70
  * Page styles, including the shared menu stylesheet: the clickable cursor + blue glow on every
@@ -73,6 +84,7 @@ class RuntimeHtmlPage {
73
84
  * carries the cursor + blue glow and the dim/undim rules the lock toggles. Shared verbatim
74
85
  * with architecture/dependencies.html and every project's design.html. */
75
86
  ${this.nodeMenu.styles()}
87
+ ${this.filterAssets.styles()}
76
88
  ${this.nodeMenu.dimStyles('#graph')}
77
89
  #graph { text-align: center; background: white; padding: 20px; border-radius: 8px; overflow-x: auto; }
78
90
  #graph svg { max-width: 100%; height: auto; }
@@ -105,6 +117,7 @@ class RuntimeHtmlPage {
105
117
  }
106
118
  .sw { flex: 0 0 auto; display: inline-flex; }
107
119
  code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }
120
+ ${new graph_navigation_1.GraphNavigation().styles()}
108
121
  @media (max-width: 900px) { .legend-columns { grid-template-columns: 1fr; } }`;
109
122
  }
110
123
  }
@@ -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,qDAAiD;AACjD,yDAAgF;AAChF,uDAAkD;AAClD,2DAAiD;AAEjD;;;;;;;;GAQG;AACH,MAAa,eAAe;IAMH;IALJ,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;QADrC,aAAQ,GAAR,QAAQ,CAC6B;IACvD,CAAC;IAEJ,MAAM,CAAC,GAAW,EAAE,KAAa;QAC7B,OAAO;;;;;;;aAOF,KAAK;;aAEL,IAAI,CAAC,MAAM,EAAE;;;UAGhB,KAAK;MACT,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;;;MAGpB,IAAA,8BAAU,GAAE;cACJ,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;cACtB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;;QAEtB,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACK,MAAM,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,yCAAsB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,CAAC;IAED;;;;;;;;OAQG;IACK,MAAM;QACV,OAAO;;;;;;;UAOL,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;UACtB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sFAgC2C,CAAC;IACnF,CAAC;CACJ;AAjGD,0CAiGC","sourcesContent":["import { SavedSnapshot } from './saved-snapshot';\nimport { CLIENT_DOT_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 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 ) {}\n\n render(dot: string, title: string): 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 <div id=\"graph\"></div>\n ${legendHtml()}\n <script>${this.nodeMenu.script()}</script>\n <script>${this.script(dot)}</script>\n</body>\n</html>`;\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 * placeholder must appear EXACTLY ONCE in the client.\n */\n private script(dot: string): string {\n return this.clientJs().split(CLIENT_DOT_PLACEHOLDER).join(JSON.stringify(dot));\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.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 code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\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":";;;AAAA,6DAA2E;AAC3E,uDAAmD;AAEnD,yDAAqD;AACrD,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,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,EAAE,CAAC,MAAM,EAAE;cAC9B,IAAI,gCAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC,MAAM,EAAE;;QAE3D,CAAC;IACL,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAgCjC,IAAI,kCAAe,EAAE,CAAC,MAAM,EAAE;sFAC8C,CAAC;IACnF,CAAC;CACJ;AAhHD,0CAgHC","sourcesContent":["import { GraphRenderModel, GraphFilterAssets } from './graph-render-model';\nimport { RuntimeDetails } from './runtime-details';\nimport type { RuntimeGraph } from './runtime-graph-model';\nimport { GraphNavigation } 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.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().script()}</script>\n <script>${new RuntimeDetails(graph, showExternalNodes).script()}</script>\n</body>\n</html>`;\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 code { background: #f2f2f2; padding: 1px 4px; border-radius: 3px; font-family: monospace; }\n ${new GraphNavigation().styles()}\n @media (max-width: 900px) { .legend-columns { grid-template-columns: 1fr; } }`;\n }\n}\n"]}
@@ -45,28 +45,17 @@ declare class QueueCylinders {
45
45
  private boundsOf;
46
46
  private pathEl;
47
47
  }
48
- /**
49
- * Every node of the runtime graph opens the shared floating menu.
50
- *
51
- * There is exactly ONE item, and it is the lock. NO "View Design": a node here is a running SERVICE,
52
- * a queue, a datastore or a third-party system, not an nx project, so there is no design.html to point
53
- * at and the item is ABSENT rather than present-and-dead.
54
- *
55
- * LOCK means the literal thing the box shows: dim every other node and every edge, light the locked
56
- * box alone. That is `WpNodeLock`, the same lock a design page uses — this page has no lock dropdown
57
- * and no responsibilities list, so there is no second control for it to fall out of step with, and
58
- * the menu label is derived from the lock's own state on every open.
59
- */
48
+ /** Runtime keeps the single-box Lock while Filter follows the full directed chain. */
60
49
  declare class RuntimeNodeMenu {
61
50
  private readonly svg;
62
51
  private readonly lock;
63
- constructor(svg: SVGSVGElement);
52
+ private readonly page;
53
+ constructor(svg: SVGSVGElement, lock: WpNodeLock, page: RuntimePage);
64
54
  wire(): void;
65
55
  }
66
- /**
67
- * Renders the DOT, reshapes the queues, wires the node menu, and reports a failure into the page
68
- * rather than only the console.
69
- */
70
- declare class RuntimePage {
71
- render(): void;
56
+ declare class RuntimePage extends WpFilterPage {
57
+ private lock;
58
+ protected captureBinding(svg: SVGSVGElement | null): () => void;
59
+ protected prepareSvg(svg: SVGSVGElement): void;
60
+ protected wireSvg(svg: SVGSVGElement): void;
72
61
  }
@@ -13,7 +13,7 @@
13
13
  * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);
14
14
  * 2. upgrade every queue node into a TRUE horizontal cylinder;
15
15
  * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one
16
- * architecture/dependencies.html and every design.html use) whose only item here is Lock/Unlock.
16
+ * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.
17
17
  *
18
18
  * Why (2) is post-processing rather than a shape:
19
19
  *
@@ -160,58 +160,45 @@ class QueueCylinders {
160
160
  return el;
161
161
  }
162
162
  }
163
- /**
164
- * Every node of the runtime graph opens the shared floating menu.
165
- *
166
- * There is exactly ONE item, and it is the lock. NO "View Design": a node here is a running SERVICE,
167
- * a queue, a datastore or a third-party system, not an nx project, so there is no design.html to point
168
- * at and the item is ABSENT rather than present-and-dead.
169
- *
170
- * LOCK means the literal thing the box shows: dim every other node and every edge, light the locked
171
- * box alone. That is `WpNodeLock`, the same lock a design page uses — this page has no lock dropdown
172
- * and no responsibilities list, so there is no second control for it to fall out of step with, and
173
- * the menu label is derived from the lock's own state on every open.
174
- */
163
+ /** Runtime keeps the single-box Lock while Filter follows the full directed chain. */
175
164
  class RuntimeNodeMenu {
176
165
  svg;
177
166
  lock;
178
- constructor(svg) {
167
+ page;
168
+ constructor(svg, lock, page) {
179
169
  this.svg = svg;
180
- this.lock = new WpNodeLock(svg);
170
+ this.lock = lock;
171
+ this.page = page;
181
172
  }
182
173
  wire() {
183
174
  WpNodeMenu.wire(this.svg, (name, node) => {
184
175
  const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';
185
- return [new WpNodeMenuItem(label, () => { this.lock.toggle(name, node); })];
176
+ return [new WpNodeMenuItem(label, () => { this.lock.toggle(name, node); }), this.page.filterItem(name)];
186
177
  });
187
178
  }
188
179
  }
189
- /**
190
- * Renders the DOT, reshapes the queues, wires the node menu, and reports a failure into the page
191
- * rather than only the console.
192
- */
193
- class RuntimePage {
194
- render() {
195
- Viz.instance()
196
- .then((viz) => {
197
- const element = viz.renderSVGElement(__DOT__);
198
- new QueueCylinders().applyTo(element);
199
- const host = document.getElementById('graph');
200
- if (host === null)
201
- return;
202
- host.appendChild(element);
203
- // AFTER the cylinders: wiring reads each node's <title>, which the redraw leaves
204
- // alone, but the clickable class belongs on the shape that is finally there.
205
- new RuntimeNodeMenu(element).wire();
206
- })
207
- // webpieces-disable no-any-unknown -- a promise rejection reason is untyped BY THE LANGUAGE (any value can be thrown), and this browser script cannot import the repo's toError helper; it is stringified, never dereferenced
208
- .catch((err) => {
209
- console.error(err);
210
- const host = document.getElementById('graph');
211
- if (host !== null)
212
- host.innerHTML = '<pre>' + String(err) + '</pre>';
180
+ class RuntimePage extends WpFilterPage {
181
+ lock = null;
182
+ captureBinding(svg) {
183
+ const previous = this.lock;
184
+ return () => {
185
+ this.lock = previous;
186
+ if (svg !== null)
187
+ this.lock?.rebind(svg);
188
+ };
189
+ }
190
+ prepareSvg(svg) { new QueueCylinders().applyTo(svg); }
191
+ wireSvg(svg) {
192
+ if (this.lock === null)
193
+ this.lock = new WpNodeLock(svg);
194
+ else
195
+ this.lock.rebind(svg);
196
+ new RuntimeNodeMenu(svg, this.lock, this).wire();
197
+ svg.querySelectorAll('g.node').forEach(node => {
198
+ if (node.querySelector('title')?.textContent === this.anchor)
199
+ node.classList.add('wp-filter-anchor');
213
200
  });
214
201
  }
215
202
  }
216
- new RuntimePage().render();
203
+ new RuntimePage(__RENDER_MODEL__).render();
217
204
  //# sourceMappingURL=runtime-visualizer.client.js.map
@@ -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) whose only item here is Lock/Unlock.\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 => { this.reshape(node); });\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 => { shape.remove(); });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' L' + (box.x1 - rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +\n ' L' + (box.x0 + rx) + ',' + box.y1 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';\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' + (box.x0 + rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + 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 /**\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/**\n * Every node of the runtime graph opens the shared floating menu.\n *\n * There is exactly ONE item, and it is the lock. NO \"View Design\": a node here is a running SERVICE,\n * a queue, a datastore or a third-party system, not an nx project, so there is no design.html to point\n * at and the item is ABSENT rather than present-and-dead.\n *\n * LOCK means the literal thing the box shows: dim every other node and every edge, light the locked\n * box alone. That is `WpNodeLock`, the same lock a design page uses — this page has no lock dropdown\n * and no responsibilities list, so there is no second control for it to fall out of step with, and\n * the menu label is derived from the lock's own state on every open.\n */\nclass RuntimeNodeMenu {\n private readonly lock: WpNodeLock;\n\n constructor(private readonly svg: SVGSVGElement) {\n this.lock = new WpNodeLock(svg);\n }\n\n wire(): void {\n WpNodeMenu.wire(this.svg, (name: string, node: SVGGElement): WpNodeMenuItem[] => {\n const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';\n return [new WpNodeMenuItem(label, (): void => { this.lock.toggle(name, node); })];\n });\n }\n}\n\n/**\n * Renders the DOT, reshapes the queues, wires the node menu, and reports a failure into the page\n * rather than only the console.\n */\nclass RuntimePage {\n render(): void {\n Viz.instance()\n .then((viz: VizInstance): void => {\n const element = viz.renderSVGElement(__DOT__);\n new QueueCylinders().applyTo(element);\n const host = document.getElementById('graph');\n if (host === null) return;\n host.appendChild(element);\n // AFTER the cylinders: wiring reads each node's <title>, which the redraw leaves\n // alone, but the clickable class belongs on the shape that is finally there.\n new RuntimeNodeMenu(element).wire();\n })\n // webpieces-disable no-any-unknown -- a promise rejection reason is untyped BY THE LANGUAGE (any value can be thrown), and this browser script cannot import the repo's toError helper; it is stringified, never dereferenced\n .catch((err: unknown): void => {\n console.error(err);\n const host = document.getElementById('graph');\n if (host !== null) host.innerHTML = '<pre>' + String(err) + '</pre>';\n });\n }\n}\n\nnew RuntimePage().render();\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 => { this.reshape(node); });\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 => { shape.remove(); });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' L' + (box.x1 - rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +\n ' L' + (box.x0 + rx) + ',' + box.y1 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';\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' + (box.x0 + rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + 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 /**\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/** Runtime keeps the single-box Lock while Filter follows the full directed chain. */\nclass RuntimeNodeMenu {\n constructor(private readonly svg: SVGSVGElement, private readonly lock: WpNodeLock, private readonly page: RuntimePage) {}\n\n wire(): void {\n WpNodeMenu.wire(this.svg, (name: string, node: SVGGElement): WpNodeMenuItem[] => {\n const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';\n return [new WpNodeMenuItem(label, (): void => { this.lock.toggle(name, node); }), this.page.filterItem(name)];\n });\n }\n}\n\nclass RuntimePage extends WpFilterPage {\n private lock: WpNodeLock | null = null;\n\n protected override captureBinding(svg: SVGSVGElement | null): () => void {\n const previous = this.lock;\n return (): void => {\n this.lock = previous;\n if (svg !== null) this.lock?.rebind(svg);\n };\n }\n\n protected override prepareSvg(svg: SVGSVGElement): void { new QueueCylinders().applyTo(svg); }\n\n protected wireSvg(svg: SVGSVGElement): void {\n if (this.lock === null) this.lock = new WpNodeLock(svg);\n else this.lock.rebind(svg);\n new RuntimeNodeMenu(svg, this.lock, this).wire();\n svg.querySelectorAll<SVGGElement>('g.node').forEach(node => {\n if (node.querySelector('title')?.textContent === this.anchor) node.classList.add('wp-filter-anchor');\n });\n }\n}\n\nnew RuntimePage(__RENDER_MODEL__).render();\n"]}
@@ -1,49 +1,5 @@
1
- /**
2
- * Runtime Visualizer
3
- *
4
- * Renders the runtime microservice graph (services + inferred Z -> X edges,
5
- * each labeled with the api(s) they flow over) to DOT + interactive HTML in
6
- * tmp/webpieces/runtime-architecture.{dot,html}.
7
- *
8
- * Each service node names the contracts it IMPLEMENTS. That list used to be
9
- * collapsed into a server/client boolean and thrown away — leaving an api that a
10
- * server serves but nothing in-repo calls completely invisible, and making a
11
- * correct api design look like a detection failure. What a service USES is NOT
12
- * listed: every use already draws an outgoing arrow labeled with the same
13
- * contract, so repeating it in the box only made every box wider.
14
- *
15
- * Shape says what a node IS and line style says what a call IS. Solid = rpc: the
16
- * request follows the arrow and the response flows back. Dashed = event: it flows
17
- * in the arrow's direction and returns once it is queued. A queue is a sideways
18
- * cylinder, a datastore an upright one. A queue box lists ONE LINE PER QUEUE: the
19
- * unit underneath is still the METHOD (what Terraform creates, and what
20
- * runtime-dependencies.json records), but queues of one contract sharing the same
21
- * producers and consumers are drawn together instead of as adjacent near-identical
22
- * boxes — every queue is still named, only the node count drops.
23
- *
24
- * Calls that leave the repo (a contract NOTHING in-repo implements — firestore,
25
- * gmail, ...) are drawn as terminal nodes, so the vendor systems that actually
26
- * page you at 3am stop being missing from the picture. One that DECLARES what it
27
- * is, via an `@externalSystem` JSDoc tag or an `external:` nx tag, gets the shape
28
- * of the thing it is; the rest stay generic dashed boxes. They are RENDER-ONLY:
29
- * derivation, levels and cycle detection never see them.
30
- *
31
- * The same is true in the other direction for endpoints nothing in-repo CALLS: a
32
- * `cron` method hangs off a clock and an `external` method off a box naming the
33
- * CALLER that posts to it (`twilio`), in the same id space as the outbound
34
- * systems, so a vendor we both call and are called by is ONE box. Those are the
35
- * entry points that wake a service up at 3am, and a graph built only from in-repo
36
- * callers cannot show them at all.
37
- *
38
- * EVERY node on the rendered page is clickable and opens the SHARED floating menu
39
- * (graph-node-menu.ts — one implementation, also used by architecture/dependencies.html
40
- * and every project's design.html). Its only item is Lock/Unlock: locking dims every
41
- * other node and every edge and lights the locked box alone. There is no "View Design"
42
- * item here — a node is a running service, queue, datastore or third-party system, not
43
- * an nx project, so no design.html exists to point at and the item is absent rather than
44
- * dead. This page has no lock dropdown, so the menu is the one and only lock control.
45
- */
46
1
  import type { RuntimeGraph } from './runtime-graph';
2
+ import { GraphRenderModel } from './graph-render-model';
47
3
  /** Render options for the runtime graph. */
48
4
  export declare class RuntimeVizOptions {
49
5
  /**
@@ -62,6 +18,8 @@ export declare class RuntimeVizOptions {
62
18
  }
63
19
  /** Build the Graphviz DOT for the runtime service graph. */
64
20
  export declare function generateRuntimeDot(graph: RuntimeGraph, title?: string, options?: RuntimeVizOptions): string;
21
+ /** Capture synthetic drawable nodes and edges at their original emission sites. */
22
+ export declare function generateRuntimeRenderModel(graph: RuntimeGraph, title?: string, options?: RuntimeVizOptions): GraphRenderModel;
65
23
  export interface RuntimeVisualizationPaths {
66
24
  dotPath: string;
67
25
  htmlPath: string;