@webpieces/nx-webpieces-rules 0.4.872 → 0.4.874

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 (35) hide show
  1. package/README.md +31 -18
  2. package/package.json +7 -7
  3. package/src/executors/visualize/executor.d.ts +17 -3
  4. package/src/executors/visualize/executor.js +53 -33
  5. package/src/executors/visualize/executor.js.map +1 -1
  6. package/src/lib/graph-color-modes.d.ts +56 -34
  7. package/src/lib/graph-color-modes.js +111 -100
  8. package/src/lib/graph-color-modes.js.map +1 -1
  9. package/src/lib/graph-filter.client.d.ts +13 -2
  10. package/src/lib/graph-filter.client.js +27 -7
  11. package/src/lib/graph-filter.client.js.map +1 -1
  12. package/src/lib/graph-impact.d.ts +37 -3
  13. package/src/lib/graph-impact.js +42 -11
  14. package/src/lib/graph-impact.js.map +1 -1
  15. package/src/lib/graph-legend.js +1 -1
  16. package/src/lib/graph-legend.js.map +1 -1
  17. package/src/lib/graph-level-bands.d.ts +8 -0
  18. package/src/lib/graph-level-bands.js +13 -1
  19. package/src/lib/graph-level-bands.js.map +1 -1
  20. package/src/lib/graph-node-menu.js +10 -108
  21. package/src/lib/graph-node-menu.js.map +1 -1
  22. package/src/lib/graph-page-shell.d.ts +31 -1
  23. package/src/lib/graph-page-shell.js +75 -7
  24. package/src/lib/graph-page-shell.js.map +1 -1
  25. package/src/lib/graph-page-styles.d.ts +14 -3
  26. package/src/lib/graph-page-styles.js +89 -18
  27. package/src/lib/graph-page-styles.js.map +1 -1
  28. package/src/lib/graph-render-model.d.ts +14 -6
  29. package/src/lib/graph-render-model.js +22 -8
  30. package/src/lib/graph-render-model.js.map +1 -1
  31. package/src/lib/graph-visualizer.client.d.ts +82 -5
  32. package/src/lib/graph-visualizer.client.js +351 -23
  33. package/src/lib/graph-visualizer.client.js.map +1 -1
  34. package/src/lib/graph-visualizer.js +1 -1
  35. package/src/lib/graph-visualizer.js.map +1 -1
package/README.md CHANGED
@@ -109,23 +109,27 @@ their existing checks independently of visualization.
109
109
  ### Color modes and the page shell
110
110
 
111
111
  `architecture/dependencies.html` opens with the graph filling the viewport and every control
112
- in a collapsible dark drawer on the left: the color mode, a type-to-search **Lock** field
113
- (`/` focuses it, `Esc` unlocks), a **Hide unconnected** toggle, the legend (with a pop-out
114
- button) and the edge key. Help is the **?** popover, zoom floats bottom-right, and
115
- **Responsibilities** opens a right-hand panel from the drawer footer.
116
-
117
- The graph answers one question at a time. Pick a mode in the drawer, from **Mode ▸** in any
118
- box's menu, or with keys `1` `2` `3`; the choice is remembered per viewer and deep-linkable as
119
- `#runtime`, `#architecture` or `#impact`.
120
-
121
- - **Runtime** — where the code can run. Base runtimes are equal vertical stripes in the fixed
122
- order browser | node | react-native; angular and react are an inset inside the browser
123
- stripe, express inside the node stripe. A box may depend on a box that carries every color it
124
- has. A box with several stripes or an inset carries its name on a light plate between two
125
- stripe bands, so the text never straddles two colors.
112
+ in a collapsible charcoal drawer on the left (violet accent): the **Color by** pulldown, the
113
+ **Filter** button, a type-to-search **Lock** field (`/` focuses it, `Esc` unlocks), a **Hide
114
+ unconnected** toggle, the legend (with a pop-out button) and the edge key. Help is the **?**
115
+ popover, zoom floats bottom-right, and **Responsibilities** opens a right-hand panel from the
116
+ drawer footer.
117
+
118
+ The graph answers one question at a time. Pick a mode from the drawer's **Color by** pulldown
119
+ (the one place to switch) or with keys `1` `2` `3`; the choice is remembered per viewer and
120
+ deep-linkable as `#runtime`, `#architecture` or `#impact`. Every box carries the same three
121
+ lines in every mode: `L3 name` (level dimmed, name bold), its role, and every framework tag.
122
+
123
+ - **Runtime** — where the code can run. One runtime is a solid fill. One runtime plus a
124
+ specialization (angular/react inside browser, express inside node) is nested: a frame in the
125
+ base color around a rounded inner box in the specialization's color, which carries the text.
126
+ Several runtimes are full-height vertical stripes in the fixed order browser | node |
127
+ react-native, the text written across them (a specialization there shows in line 3 only). A
128
+ box may depend on a box that carries every color it has.
126
129
  - **Architecture** — servers · clients · APIs. A solid fill by role (server, client, app, bundle,
127
130
  api-lib, api-client, designed-lib, lib); framework color is not shown.
128
- - **Impact** — what this branch touches. `architecture:generate` runs
131
+ - **Impact** — what this branch touches. `architecture:generate` and `architecture:visualize`
132
+ (`pnpm arch:visualize`, no regenerate needed) both run
129
133
  `nx show projects --affected --base=<fork point> --json` (no `--head`, so uncommitted and
130
134
  untracked work counts). Projects that own a changed file, by nx's own file ownership, are solid
131
135
  amber ("touched — files changed on this branch"); the rest of the affected set is light amber
@@ -135,8 +139,17 @@ box's menu, or with keys `1` `2` `3`; the choice is remembered per viewer and de
135
139
  it is written to `architecture/.impact/dependencies.impact.js`, never into the committed html or
136
140
  json. That directory holds its own `.gitignore` (`*`), so a consuming repo needs no gitignore
137
141
  entry. When nx or git cannot answer, no sidecar is written and Impact is disabled with a
138
- one-line reason; generation never fails over it. `architecture:visualize` leaves the sidecar
139
- as it is.
142
+ one-line reason; neither command fails over it. Impact state is never written on a box: color
143
+ and legend only. When files changed but no project owns one (a lockfile, the workspace
144
+ manifest), the legend names that workspace-global cause for the everything-affected result.
145
+
146
+ **Filter** opens a popover of three groups that INTERSECT: *Changes on this branch* (Everything ·
147
+ Changed · Changed + what uses them (nx affected) · Changed + what they use · Whole build of this
148
+ branch, each with its project count; disabled with Impact's reason when there is no Impact
149
+ data), *Runtime* chips and *Role* chips (a box matches a group when it carries any selected
150
+ chip). "Show N projects" applies it; active filters show as removable pills in the top bar and
151
+ a count badge on the drawer button. Every remaining box keeps its own L-row, a level left empty
152
+ is a thin labeled band, and edges between remaining boxes stay drawn.
140
153
 
141
154
  The legend is generated per mode from the projects actually drawn, and the framework colors
142
155
  are keyed off `KNOWN_FRAMEWORKS` (`@webpieces/rules-sdk`), the same list the framework-tag
@@ -152,7 +165,7 @@ APIs and systems. The anchor indicator and all surviving node menus offer **Turn
152
165
  Filter**, restoring the full layout and independent Lock state. Responsibilities
153
166
  follow the retained set intersected with architecture Lock. Architecture Lock pins
154
167
  its chain in the foreground; hover adds a temporary chain without dimming locked
155
- nodes or edges, and leaving restores the Lock chain. The locked anchor keeps an amber outline,
168
+ nodes or edges, and leaving restores the Lock chain. The locked anchor keeps a violet outline,
156
169
  and hover never changes responsibilities. A filtered-out Lock is suspended until
157
170
  the full graph returns. Hover and menu dismissal
158
171
  do not clear filtering, and no filter is persisted across reloads. Saved graph facts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/nx-webpieces-rules",
3
- "version": "0.4.872",
3
+ "version": "0.4.874",
4
4
  "description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -20,12 +20,12 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "@webpieces/api-doc-model": "0.4.872",
24
- "@webpieces/core-util": "0.4.872",
25
- "@webpieces/eslint-rules": "0.4.872",
26
- "@webpieces/rules-config": "0.4.872",
23
+ "@webpieces/api-doc-model": "0.4.874",
24
+ "@webpieces/core-util": "0.4.874",
25
+ "@webpieces/eslint-rules": "0.4.874",
26
+ "@webpieces/rules-config": "0.4.874",
27
27
  "madge": "8.0.0",
28
- "@webpieces/rules-sdk": "0.4.872"
28
+ "@webpieces/rules-sdk": "0.4.874"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@nx/devkit": ">=18.0.0"
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "devDependencies": {
44
- "@webpieces/tooling-testkit": "0.4.872"
44
+ "@webpieces/tooling-testkit": "0.4.874"
45
45
  },
46
46
  "types": "./src/index.d.ts",
47
47
  "bin": {
@@ -1,17 +1,31 @@
1
1
  /**
2
2
  * Visualize Executor
3
3
  *
4
- * Generates visual representations of the architecture graph (DOT + HTML)
5
- * and opens the visualization in a browser.
4
+ * Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens
5
+ * the visualization in a browser. It never regenerates the graph: the architecture facts stay the
6
+ * saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the
7
+ * thing that goes stale when you switch branches — so viewing refreshes it with the same
8
+ * `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.
6
9
  *
7
10
  * Usage:
8
- * nx run architecture:visualize
11
+ * nx run architecture:visualize (pnpm arch:visualize)
9
12
  */
10
13
  import type { ExecutorContext } from '@nx/devkit';
14
+ import { SavedSnapshot } from '../../lib/saved-snapshot';
15
+ import { GraphVisualizer } from '../../lib/graph-visualizer';
16
+ import { ImpactRefresh } from '../../lib/graph-impact';
11
17
  export interface VisualizeExecutorOptions {
12
18
  graphPath?: string;
13
19
  }
14
20
  export interface ExecutorResult {
15
21
  success: boolean;
16
22
  }
23
+ /** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */
24
+ export declare class SavedGraphViewer {
25
+ private readonly visualizer;
26
+ private readonly impact;
27
+ private readonly snapshot;
28
+ constructor(visualizer?: GraphVisualizer, impact?: ImpactRefresh, snapshot?: SavedSnapshot);
29
+ view(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult>;
30
+ }
17
31
  export default function runExecutor(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -2,51 +2,71 @@
2
2
  /**
3
3
  * Visualize Executor
4
4
  *
5
- * Generates visual representations of the architecture graph (DOT + HTML)
6
- * and opens the visualization in a browser.
5
+ * Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens
6
+ * the visualization in a browser. It never regenerates the graph: the architecture facts stay the
7
+ * saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the
8
+ * thing that goes stale when you switch branches — so viewing refreshes it with the same
9
+ * `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.
7
10
  *
8
11
  * Usage:
9
- * nx run architecture:visualize
12
+ * nx run architecture:visualize (pnpm arch:visualize)
10
13
  */
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SavedGraphViewer = void 0;
12
16
  exports.default = runExecutor;
17
+ const tslib_1 = require("tslib");
18
+ const path = tslib_1.__importStar(require("path"));
13
19
  const saved_snapshot_1 = require("../../lib/saved-snapshot");
14
20
  const graph_loader_1 = require("../../lib/graph-loader");
15
21
  const graph_visualizer_1 = require("../../lib/graph-visualizer");
22
+ const graph_impact_1 = require("../../lib/graph-impact");
16
23
  const rules_config_1 = require("@webpieces/rules-config");
17
24
  const toError_1 = require("../../toError");
18
- async function runExecutor(options, context) {
19
- const graphPath = options.graphPath ?? graph_loader_1.DEFAULT_GRAPH_PATH;
20
- const workspaceRoot = context.root;
21
- const snapshot = new saved_snapshot_1.SavedSnapshot();
22
- console.log('\n🎨 Architecture Visualization\n');
23
- console.log(snapshot.message());
24
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx
25
- try {
26
- // Load the saved graph
27
- console.log('📂 Loading saved graph...');
28
- const graphFile = snapshot.loadProjects(workspaceRoot, graphPath);
29
- const graph = graphFile.projects;
30
- // Generate visualization
31
- console.log('🎨 Generating visualization...');
32
- const visualizer = new graph_visualizer_1.GraphVisualizer();
33
- const vizPaths = visualizer.writeVisualization(graph, workspaceRoot);
34
- console.log(`✅ Generated: ${vizPaths.htmlPath}`);
35
- // Try to open in browser
36
- console.log('\n🌐 Opening visualization in browser...');
37
- if (visualizer.openVisualization(vizPaths.htmlPath)) {
38
- console.log('✅ Browser opened');
25
+ /** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */
26
+ class SavedGraphViewer {
27
+ visualizer;
28
+ impact;
29
+ snapshot;
30
+ constructor(visualizer = new graph_visualizer_1.GraphVisualizer(), impact = new graph_impact_1.ImpactRefresh(), snapshot = new saved_snapshot_1.SavedSnapshot()) {
31
+ this.visualizer = visualizer;
32
+ this.impact = impact;
33
+ this.snapshot = snapshot;
34
+ }
35
+ async view(options, context) {
36
+ const graphPath = options.graphPath ?? graph_loader_1.DEFAULT_GRAPH_PATH;
37
+ const workspaceRoot = context.root;
38
+ console.log('\n🎨 Architecture Visualization\n');
39
+ console.log(this.snapshot.message());
40
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx
41
+ try {
42
+ console.log('📂 Loading saved graph...');
43
+ const graph = this.snapshot.loadProjects(workspaceRoot, graphPath).projects;
44
+ console.log('🎨 Generating visualization...');
45
+ const vizPaths = this.visualizer.writeVisualization(graph, workspaceRoot);
46
+ console.log(`✅ Generated: ${vizPaths.htmlPath}`);
47
+ // Impact is optional: it reports, it never throws, and it never touches committed files.
48
+ console.log('🟠 Refreshing this branch\'s impact (nx affected since the fork point)...');
49
+ console.log(await this.impact.run(path.dirname(vizPaths.htmlPath), workspaceRoot, graph));
50
+ console.log('\n🌐 Opening visualization in browser...');
51
+ if (this.visualizer.openVisualization(vizPaths.htmlPath)) {
52
+ console.log('✅ Browser opened');
53
+ }
54
+ else {
55
+ console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);
56
+ }
57
+ return { success: true };
39
58
  }
40
- else {
41
- console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);
59
+ catch (err) {
60
+ const error = (0, toError_1.toError)(err);
61
+ const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
62
+ console.error(`❌ Visualization failed for ${graphPath}:`, rendered);
63
+ return { success: false };
42
64
  }
43
- return { success: true };
44
- }
45
- catch (err) {
46
- const error = (0, toError_1.toError)(err);
47
- const rendered = error instanceof rules_config_1.RuleFailError ? (0, rules_config_1.renderRuleFailForHuman)(error) : error.message;
48
- console.error(`❌ Visualization failed for ${graphPath}:`, rendered);
49
- return { success: false };
50
65
  }
51
66
  }
67
+ exports.SavedGraphViewer = SavedGraphViewer;
68
+ // webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here
69
+ async function runExecutor(options, context) {
70
+ return new SavedGraphViewer().view(options, context);
71
+ }
52
72
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAiBH,8BAuCC;AArDD,6DAAyD;AACzD,yDAA4D;AAC5D,iEAA6D;AAC7D,0DAAgF;AAChF,2CAAwC;AAUzB,KAAK,UAAU,WAAW,CACrC,OAAiC,EACjC,OAAwB;IAExB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iCAAkB,CAAC;IAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IACnC,MAAM,QAAQ,GAAkB,IAAI,8BAAa,EAAE,CAAC;IAEpD,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IAEhC,uIAAuI;IACvI,IAAI,CAAC;QACD,uBAAuB;QACvB,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC;QAEjC,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,IAAI,kCAAe,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEjD,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,IAAI,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,QAAQ,GAAG,KAAK,YAAY,4BAAa,CAAC,CAAC,CAAC,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;QAChG,OAAO,CAAC,KAAK,CAAC,8BAA8B,SAAS,GAAG,EAAE,QAAQ,CAAC,CAAC;QACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;AACL,CAAC","sourcesContent":["/**\n * Visualize Executor\n *\n * Generates visual representations of the architecture graph (DOT + HTML)\n * and opens the visualization in a browser.\n *\n * Usage:\n * nx run architecture:visualize\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { SavedSnapshot } from '../../lib/saved-snapshot';\nimport { DEFAULT_GRAPH_PATH } from '../../lib/graph-loader';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { toError } from '../../toError';\n\nexport interface VisualizeExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\nexport default async function runExecutor(\n options: VisualizeExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;\n const workspaceRoot = context.root;\n const snapshot: SavedSnapshot = new SavedSnapshot();\n\n console.log('\\n🎨 Architecture Visualization\\n');\n console.log(snapshot.message());\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx\n try {\n // Load the saved graph\n console.log('📂 Loading saved graph...');\n const graphFile = snapshot.loadProjects(workspaceRoot, graphPath);\n const graph = graphFile.projects;\n\n // Generate visualization\n console.log('🎨 Generating visualization...');\n const visualizer = new GraphVisualizer();\n const vizPaths = visualizer.writeVisualization(graph, workspaceRoot);\n console.log(`✅ Generated: ${vizPaths.htmlPath}`);\n\n // Try to open in browser\n console.log('\\n🌐 Opening visualization in browser...');\n if (visualizer.openVisualization(vizPaths.htmlPath)) {\n console.log('✅ Browser opened');\n } else {\n console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n }\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error(`❌ Visualization failed for ${graphPath}:`, rendered);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/executors/visualize/executor.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAiEH,8BAKC;;AApED,mDAA6B;AAE7B,6DAAyD;AACzD,yDAA4D;AAC5D,iEAA6D;AAC7D,yDAAuD;AACvD,0DAAgF;AAChF,2CAAwC;AAUxC,8FAA8F;AAC9F,MAAa,gBAAgB;IAEJ;IACA;IACA;IAHrB,YACqB,aAA8B,IAAI,kCAAe,EAAE,EACnD,SAAwB,IAAI,4BAAa,EAAE,EAC3C,WAA0B,IAAI,8BAAa,EAAE;QAF7C,eAAU,GAAV,UAAU,CAAyC;QACnD,WAAM,GAAN,MAAM,CAAqC;QAC3C,aAAQ,GAAR,QAAQ,CAAqC;IAC/D,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,OAAiC,EAAE,OAAwB;QAClE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iCAAkB,CAAC;QAC1D,MAAM,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;QAEnC,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAErC,uIAAuI;QACvI,IAAI,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC;YAE5E,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YAEjD,yFAAyF;YACzF,OAAO,CAAC,GAAG,CAAC,2EAA2E,CAAC,CAAC;YACzF,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;YAE1F,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;YACxD,IAAI,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvD,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACJ,OAAO,CAAC,GAAG,CAAC,2CAA2C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChF,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,QAAQ,GAAG,KAAK,YAAY,4BAAa,CAAC,CAAC,CAAC,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;YAChG,OAAO,CAAC,KAAK,CAAC,8BAA8B,SAAS,GAAG,EAAE,QAAQ,CAAC,CAAC;YACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;CACJ;AA1CD,4CA0CC;AAED,gHAAgH;AACjG,KAAK,UAAU,WAAW,CACrC,OAAiC,EACjC,OAAwB;IAExB,OAAO,IAAI,gBAAgB,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC","sourcesContent":["/**\n * Visualize Executor\n *\n * Renders the SAVED architecture graph (DOT + HTML), refreshes this branch's Impact sidecar, and opens\n * the visualization in a browser. It never regenerates the graph: the architecture facts stay the\n * saved snapshot. Impact, though, is per-branch and gitignored (graph-impact.ts) — it is exactly the\n * thing that goes stale when you switch branches — so viewing refreshes it with the same\n * `nx show projects --affected --base=<fork point>` scan generate runs, against the saved graph.\n *\n * Usage:\n * nx run architecture:visualize (pnpm arch:visualize)\n */\n\nimport * as path from 'path';\nimport type { ExecutorContext } from '@nx/devkit';\nimport { SavedSnapshot } from '../../lib/saved-snapshot';\nimport { DEFAULT_GRAPH_PATH } from '../../lib/graph-loader';\nimport { GraphVisualizer } from '../../lib/graph-visualizer';\nimport { ImpactRefresh } from '../../lib/graph-impact';\nimport { RuleFailError, renderRuleFailForHuman } from '@webpieces/rules-config';\nimport { toError } from '../../toError';\n\nexport interface VisualizeExecutorOptions {\n graphPath?: string;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\n/** The view pipeline: load the saved graph, write the page, refresh Impact, open the page. */\nexport class SavedGraphViewer {\n constructor(\n private readonly visualizer: GraphVisualizer = new GraphVisualizer(),\n private readonly impact: ImpactRefresh = new ImpactRefresh(),\n private readonly snapshot: SavedSnapshot = new SavedSnapshot(),\n ) {}\n\n async view(options: VisualizeExecutorOptions, context: ExecutorContext): Promise<ExecutorResult> {\n const graphPath = options.graphPath ?? DEFAULT_GRAPH_PATH;\n const workspaceRoot = context.root;\n\n console.log('\\n🎨 Architecture Visualization\\n');\n console.log(this.snapshot.message());\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- Nx command rendering boundary; success:false propagates failure to Nx\n try {\n console.log('📂 Loading saved graph...');\n const graph = this.snapshot.loadProjects(workspaceRoot, graphPath).projects;\n\n console.log('🎨 Generating visualization...');\n const vizPaths = this.visualizer.writeVisualization(graph, workspaceRoot);\n console.log(`✅ Generated: ${vizPaths.htmlPath}`);\n\n // Impact is optional: it reports, it never throws, and it never touches committed files.\n console.log('🟠 Refreshing this branch\\'s impact (nx affected since the fork point)...');\n console.log(await this.impact.run(path.dirname(vizPaths.htmlPath), workspaceRoot, graph));\n\n console.log('\\n🌐 Opening visualization in browser...');\n if (this.visualizer.openVisualization(vizPaths.htmlPath)) {\n console.log('✅ Browser opened');\n } else {\n console.log(`⚠️ Could not auto-open. Open manually: ${vizPaths.htmlPath}`);\n }\n\n return { success: true };\n } catch (err: unknown) {\n const error = toError(err);\n const rendered = error instanceof RuleFailError ? renderRuleFailForHuman(error) : error.message;\n console.error(`❌ Visualization failed for ${graphPath}:`, rendered);\n return { success: false };\n }\n }\n}\n\n// webpieces-disable no-function-outside-class -- nx executor module: nx resolves a default-export function here\nexport default async function runExecutor(\n options: VisualizeExecutorOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n return new SavedGraphViewer().view(options, context);\n}\n"]}
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Graph color modes (#1155)
2
+ * Graph color modes (#1155, restyled by #1158)
3
3
  *
4
4
  * The architecture graph answers ONE question at a time instead of asking every box to encode two
5
5
  * (fill = framework AND border = role). Each box is emitted once per mode, so the page can switch
6
- * modes and re-render without regenerating anything:
6
+ * modes and re-render without regenerating anything. Every mode writes the SAME three lines on a box
7
+ * (level + name, role, every framework tag); only the color changes:
7
8
  *
8
- * - RUNTIME "where the code can run". Base runtimes are equal vertical stripes in the fixed
9
- * order browser | node | react-native; a specialization (angular/react inside
10
- * browser, express inside node) is an inset inside its base stripe. The dependency
11
- * rule becomes visual: a box may depend on a box carrying every color it has. The
12
- * name and meta sit on a light label PLATE across the middle, so they read on every
13
- * stripe combination while the stripes and insets stay visible above and below it.
9
+ * - RUNTIME "where the code can run". One runtime is a solid fill. One runtime plus a
10
+ * specialization (angular/react inside browser, express inside node) is NESTED: an
11
+ * outer frame in the base color around an inner rounded box in the specialization's
12
+ * color, carrying the text. Several runtimes are full-height vertical stripes in the
13
+ * fixed order browser | node | react-native, with the text written across them. The
14
+ * dependency rule becomes visual: a box may depend on a box carrying every color it has.
14
15
  * - ARCHITECTURE "servers · clients · APIs". A solid fill by ROLE; framework color is not shown.
15
16
  * - IMPACT "what this branch touches". Projects owning a changed file are solid amber
16
17
  * ("touched"); the rest of nx's affected set is light amber ("affected": their ci
@@ -18,7 +19,7 @@
18
19
  * affected is light slate with a dashed border ("build input": compiled or restored
19
20
  * from cache, unchanged); everything else is grey. Which box is which is only known
20
21
  * per branch, so every box carries all four variants and the page picks one from the
21
- * sidecar (graph-impact.ts).
22
+ * sidecar (graph-impact.ts). The state is never written on the box: color + legend.
22
23
  */
23
24
  import { KnownFramework } from '@webpieces/rules-sdk';
24
25
  export declare enum GraphMode {
@@ -26,7 +27,7 @@ export declare enum GraphMode {
26
27
  ARCHITECTURE = "architecture",
27
28
  IMPACT = "impact"
28
29
  }
29
- /** One selectable mode as the drawer and the node menu show it. */
30
+ /** One selectable mode as the drawer's Color-by pulldown shows it. */
30
31
  export declare class ModeInfo {
31
32
  readonly mode: GraphMode;
32
33
  readonly name: string;
@@ -35,25 +36,40 @@ export declare class ModeInfo {
35
36
  }
36
37
  /** In key order: `1` selects the first, `2` the second, `3` the third. */
37
38
  export declare const GRAPH_MODES: readonly ModeInfo[];
38
- /** A framework value's color and the BASE runtime it is drawn inside (itself, for a base). */
39
+ /** Text on a light fill. */
40
+ export declare const DARK_TEXT = "#1a1c22";
41
+ /** Text on a dark fill. */
42
+ export declare const LIGHT_TEXT = "#ffffff";
43
+ /**
44
+ * A framework value's color, the text color that reads on it, and the BASE runtime it is drawn
45
+ * inside (itself, for a base).
46
+ */
39
47
  export declare class FrameworkStyle {
40
48
  readonly framework: KnownFramework;
41
49
  readonly base: KnownFramework;
42
50
  readonly color: string;
51
+ readonly fontColor: string;
43
52
  readonly description: string;
44
- constructor(framework: KnownFramework, base: KnownFramework, color: string, description: string);
53
+ constructor(framework: KnownFramework, base: KnownFramework, color: string, fontColor: string, description: string);
45
54
  isBase(): boolean;
46
55
  }
47
56
  /**
48
57
  * Keyed by the shared vocabulary, so a framework added to KNOWN_FRAMEWORKS without a color here is a
49
58
  * COMPILE error — not a box silently painted grey and missing from the legend, which is exactly how
50
- * react-native shipped (#1155).
59
+ * react-native shipped (#1155). Every base is mid-light, so dark text reads on every stripe.
51
60
  */
52
61
  export declare const FRAMEWORK_STYLES: Record<KnownFramework, FrameworkStyle>;
53
62
  /** Stripe order, left to right. Fixed, so the same set always reads the same way. */
54
63
  export declare const BASE_ORDER: readonly KnownFramework[];
64
+ /**
65
+ * The Filter popover's Runtime chips, and the order line 3 of a box lists framework tags in: the
66
+ * bases first, then the specializations.
67
+ */
68
+ export declare const FRAMEWORK_ORDER: readonly KnownFramework[];
55
69
  /** A box with no known framework tag. */
56
70
  export declare const NO_RUNTIME_COLOR = "#e4e6ec";
71
+ /** The class Graphviz stamps on a multi-runtime box's `<g>`: its outline is its LAST shape. */
72
+ export declare const STRIPED_CLASS = "wp-striped";
57
73
  /** A role's solid fill and the text color that stays readable on it. */
58
74
  export declare class RoleStyle {
59
75
  readonly role: string;
@@ -62,7 +78,10 @@ export declare class RoleStyle {
62
78
  readonly description: string;
63
79
  constructor(role: string, fill: string, fontColor: string, description: string);
64
80
  }
65
- /** Every role role-resolver knows, in legend order (runnable things first). */
81
+ /**
82
+ * Every role role-resolver knows, in legend order (runnable things first) — also the Filter popover's
83
+ * Role chips. `bundle` is indigo so no box color equals the page's violet accent.
84
+ */
66
85
  export declare const ROLE_STYLES: readonly RoleStyle[];
67
86
  /** The four impact shades (graph-impact.ts decides which box gets which). */
68
87
  export declare const IMPACT_TOUCHED = "#f5a524";
@@ -70,9 +89,7 @@ export declare const IMPACT_AFFECTED = "#fde3b0";
70
89
  export declare const IMPACT_BUILD_INPUT = "#e3e9f2";
71
90
  export declare const IMPACT_BUILD_INPUT_BORDER = "#7c8aa3";
72
91
  export declare const IMPACT_UNTOUCHED = "#eef0f4";
73
- /** The light plate the name and meta sit on, across every stripe of a multi-color box. */
74
- export declare const LABEL_PLATE = "#fbfbfd";
75
- /** What a box's per-mode rendering is computed from. */
92
+ /** What a box's per-mode rendering — and the page's Filter — is computed from. */
76
93
  export declare class NodeFacts {
77
94
  readonly nodeId: string;
78
95
  readonly shortName: string;
@@ -110,27 +127,32 @@ export declare class NodeModeStyler {
110
127
  dots(facts: NodeFacts): NodeModeDots;
111
128
  /** Fold one drawn box into the legend facts. */
112
129
  record(legend: LegendFacts, facts: NodeFacts): void;
113
- private statement;
114
- private plainLabel;
115
- private runtimeMeta;
116
- private envMeta;
117
- private architectureAttrs;
118
130
  /**
119
- * Runtime mode. A box in ONE base runtime with no specialization is a plain fill — its dark text
120
- * reads on any of the pastel base colors. Every other box (several runtimes, or a specialization)
121
- * is an HTML-like table of three rows: a band of equal stripes, a light PLATE carrying the name and
122
- * meta across all of them, and the same stripes again. The stripes and insets stay visible above
123
- * and below the text, and the text never straddles two colors.
131
+ * Line 3 of every box: every framework tag, bases before specializations, any tag outside the
132
+ * shared vocabulary last (it still runs somewhere somebody declared).
124
133
  */
125
- private runtime;
134
+ frameworkLine(frameworks: string[]): string;
135
+ /**
136
+ * The three lines, identical in every mode: `L3 name` (level dimmed, name bold), the role, and
137
+ * the framework tags. The level is dimmed by giving its text color an alpha channel, which
138
+ * Graphviz writes as `fill-opacity`, so it dims on any fill.
139
+ */
140
+ labelText(facts: NodeFacts, fontColor: string): string;
126
141
  /** `filled` for one runtime; equal `striped` weights for several (the last takes the remainder). */
127
142
  stripeAttrs(bases: KnownFramework[]): string;
143
+ private statement;
144
+ /** A solid fill, the three lines on it. `extra` carries border attributes. */
145
+ private plain;
146
+ /**
147
+ * Runtime mode. No runtime, or one runtime with no specialization: a solid fill. One runtime with a
148
+ * specialization: NESTED. Several runtimes: full-height stripes with the text across them (a
149
+ * specialization on such a box shows in line 3 only).
150
+ */
151
+ private runtime;
128
152
  /**
129
- * Stripe band / plate / stripe band. Every stripe cell asks for the same minimum width (an
130
- * estimate of the text's width split evenly), so the stripes come out equal and the plate spans
131
- * exactly their total.
153
+ * An outer cell in the base color whose padding is the visible frame, holding an inner ROUNDED
154
+ * table in the specialization's color that carries the text. The node's own polygon stays its
155
+ * FIRST shape, so the hover glow and the lock outline land on the outer box only.
132
156
  */
133
- private plateTable;
134
- /** One stripe of a band: the base color, with its specialization inset inside a margin of it. */
135
- private stripeCell;
157
+ private nestedTable;
136
158
  }