@webpieces/nx-webpieces-rules 0.4.946 → 0.4.948

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 (44) hide show
  1. package/README.md +20 -11
  2. package/package.json +7 -7
  3. package/src/executors/validate-no-file-import-cycles/executor.js +7 -4
  4. package/src/executors/validate-no-file-import-cycles/executor.js.map +1 -1
  5. package/src/index.d.ts +1 -1
  6. package/src/index.js +3 -1
  7. package/src/index.js.map +1 -1
  8. package/src/lib/graph-color-modes.d.ts +18 -12
  9. package/src/lib/graph-color-modes.js +18 -20
  10. package/src/lib/graph-color-modes.js.map +1 -1
  11. package/src/lib/graph-legend.d.ts +11 -3
  12. package/src/lib/graph-legend.js +25 -9
  13. package/src/lib/graph-legend.js.map +1 -1
  14. package/src/lib/graph-mode-state.client.d.ts +2 -1
  15. package/src/lib/graph-mode-state.client.js +3 -1
  16. package/src/lib/graph-mode-state.client.js.map +1 -1
  17. package/src/lib/graph-page-shell.js +2 -2
  18. package/src/lib/graph-page-shell.js.map +1 -1
  19. package/src/lib/graph-page-styles.js +4 -1
  20. package/src/lib/graph-page-styles.js.map +1 -1
  21. package/src/lib/graph-product-paint.client.d.ts +92 -0
  22. package/src/lib/graph-product-paint.client.js +196 -0
  23. package/src/lib/graph-product-paint.client.js.map +1 -0
  24. package/src/lib/graph-products.d.ts +54 -6
  25. package/src/lib/graph-products.js +62 -9
  26. package/src/lib/graph-products.js.map +1 -1
  27. package/src/lib/graph-render-model.d.ts +12 -12
  28. package/src/lib/graph-render-model.js +12 -14
  29. package/src/lib/graph-render-model.js.map +1 -1
  30. package/src/lib/graph-visualizer.client.d.ts +13 -1
  31. package/src/lib/graph-visualizer.client.js +27 -4
  32. package/src/lib/graph-visualizer.client.js.map +1 -1
  33. package/src/lib/graph-visualizer.d.ts +3 -3
  34. package/src/lib/graph-visualizer.js +13 -12
  35. package/src/lib/graph-visualizer.js.map +1 -1
  36. package/src/lib/runtime-goal-comparison.js +1 -1
  37. package/src/lib/runtime-goal-comparison.js.map +1 -1
  38. package/src/lib/runtime-html-page.js +3 -2
  39. package/src/lib/runtime-html-page.js.map +1 -1
  40. package/src/lib/runtime-layout.js +1 -1
  41. package/src/lib/runtime-layout.js.map +1 -1
  42. package/src/lib/runtime-viewer-adapter.d.ts +4 -1
  43. package/src/lib/runtime-viewer-adapter.js +6 -13
  44. package/src/lib/runtime-viewer-adapter.js.map +1 -1
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /*
3
+ * The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).
4
+ *
5
+ * Each product's legend row is a toggle. Painting every declared product at once gave a workspace with
6
+ * eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one
7
+ * product's footprint, or where two overlap. So the page keeps an ON set — the first two products in
8
+ * legend (sorted-name) order at EVERY load, never persisted — and repaints from it without
9
+ * regenerating anything: the page already carries each node's products and the ONE palette (#1244).
10
+ *
11
+ * Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no
12
+ * DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);
13
+ * they are spelled here because this script is not a module and cannot import them.
14
+ */
15
+ /** Which of the five looks a Product-mode node gets for one ON set. */
16
+ var WpProductPaintKind;
17
+ (function (WpProductPaintKind) {
18
+ /** The node belongs to no product: white, dashed border. */
19
+ WpProductPaintKind["NONE"] = "none";
20
+ /** Every product it carries is OFF: light grey, still visible. */
21
+ WpProductPaintKind["OFF"] = "off";
22
+ /** Exactly one of its products is ON: solid in that product's fixed color. */
23
+ WpProductPaintKind["SOLID"] = "solid";
24
+ /** Two or more of its products are ON: one stripe per ON product. */
25
+ WpProductPaintKind["STRIPES"] = "stripes";
26
+ /** Every product is ON and the node carries every one: the neutral shared fill. */
27
+ WpProductPaintKind["SHARED"] = "shared";
28
+ })(WpProductPaintKind || (WpProductPaintKind = {}));
29
+ /** How one node is painted in Product mode. Data-only. */
30
+ class WpProductPaint {
31
+ kind;
32
+ colors;
33
+ fontColor;
34
+ constructor(kind,
35
+ /** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */
36
+ colors,
37
+ /** The text color that reads on the fill. */
38
+ fontColor) {
39
+ this.kind = kind;
40
+ this.colors = colors;
41
+ this.fontColor = fontColor;
42
+ }
43
+ }
44
+ /** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */
45
+ class WpProductSelection {
46
+ /** The first two products in legend order. */
47
+ initialOn(order) {
48
+ return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));
49
+ }
50
+ }
51
+ /** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */
52
+ class WpProductPainter {
53
+ legend;
54
+ on;
55
+ /** graph-products.ts PRODUCT_SHARED_FILL. */
56
+ static SHARED_FILL = '#c9ced8';
57
+ /** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */
58
+ static NONE_FILL = '#ffffff';
59
+ static NONE_BORDER = '#8a90a0';
60
+ /** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */
61
+ static OFF_FILL = '#eef0f4';
62
+ static OFF_BORDER = '#b4b9c4';
63
+ /** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */
64
+ static DARK_TEXT = '#1a1c22';
65
+ static LIGHT_TEXT = '#ffffff';
66
+ static STRIPED_CLASS = 'wp-striped';
67
+ /** graph-products.ts PRODUCTS_ON_AT_LOAD. */
68
+ static ON_AT_LOAD = 2;
69
+ colors = new Map();
70
+ /**
71
+ * @param legend the products the legend lists, in its order, each with its FIXED palette color —
72
+ * a color is never reassigned to the ON set, so it always means the same product
73
+ * @param on the products switched ON
74
+ */
75
+ constructor(legend, on) {
76
+ this.legend = legend;
77
+ this.on = on;
78
+ for (const color of legend)
79
+ this.colors.set(color.product, color);
80
+ }
81
+ /** The paint for a node carrying `products`. */
82
+ paint(products) {
83
+ if (products.length === 0)
84
+ return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);
85
+ const on = products.filter((product) => this.isOn(product));
86
+ if (on.length === 0)
87
+ return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);
88
+ if (this.allOn() && this.legend.every((color) => products.includes(color.product)))
89
+ return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);
90
+ const colors = on.map((product) => this.colors.get(product));
91
+ if (colors.length === 1)
92
+ return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);
93
+ return new WpProductPaint(WpProductPaintKind.STRIPES, colors.map((color) => color.color), WpProductPainter.DARK_TEXT);
94
+ }
95
+ /**
96
+ * The color a cross-product dependency is dashed in (#1204): the first product it crosses into
97
+ * that is ON; null draws it plain.
98
+ */
99
+ edgeColor(crossesInto) {
100
+ const into = crossesInto.find((product) => this.isOn(product));
101
+ return into === undefined ? null : this.colors.get(into).color;
102
+ }
103
+ /** A node's Product-mode statement. */
104
+ nodeDot(template, products) {
105
+ const paint = this.paint(products);
106
+ if (template.kind === 'context')
107
+ return this.context(template.head, paint);
108
+ const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;
109
+ return `${template.head}${this.attrs(paint)}, label=<${label}>];\n`;
110
+ }
111
+ /** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */
112
+ edgeDot(edge) {
113
+ const color = this.edgeColor(edge.crossesInto);
114
+ if (color === null)
115
+ return edge.dot;
116
+ return `${edge.dot.slice(0, -2)} [style=dashed, color="${color}", penwidth=2];\n`;
117
+ }
118
+ isOn(product) {
119
+ return this.on.has(product) && this.colors.has(product);
120
+ }
121
+ /** Every product is ON — of a legend listing two or more (a lone product is its own color). */
122
+ allOn() {
123
+ return this.legend.length > 1 && this.legend.every((color) => this.on.has(color.product));
124
+ }
125
+ attrs(paint) {
126
+ const dark = WpProductPainter.DARK_TEXT;
127
+ if (paint.kind === WpProductPaintKind.NONE)
128
+ return `style="filled,dashed", fillcolor="${WpProductPainter.NONE_FILL}", fontcolor="${dark}", color="${WpProductPainter.NONE_BORDER}", penwidth=1.5`;
129
+ if (paint.kind === WpProductPaintKind.OFF)
130
+ return `style="filled", fillcolor="${WpProductPainter.OFF_FILL}", fontcolor="${dark}", color="${WpProductPainter.OFF_BORDER}"`;
131
+ if (paint.kind === WpProductPaintKind.SHARED)
132
+ return `style="filled", fillcolor="${WpProductPainter.SHARED_FILL}", fontcolor="${dark}"`;
133
+ if (paint.kind === WpProductPaintKind.SOLID)
134
+ return `style="filled", fillcolor="${paint.colors[0]}", fontcolor="${paint.fontColor}"`;
135
+ return `${this.stripes(paint.colors)}, fontcolor="${dark}"`;
136
+ }
137
+ /** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */
138
+ stripes(colors) {
139
+ const share = (1 / colors.length).toFixed(3);
140
+ const weighted = colors.map((color, index) => index === colors.length - 1 ? color : `${color};${share}`);
141
+ return `style="striped", fillcolor="${weighted.join(':')}", class="${WpProductPainter.STRIPED_CLASS}"`;
142
+ }
143
+ /**
144
+ * A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz
145
+ * cylinders cannot stripe, so several ON products are a gradient; no product is dashed.
146
+ */
147
+ context(dot, paint) {
148
+ const recolored = dot.replace(/fillcolor="[^"]*"/, `fillcolor="${this.contextFill(paint)}"`);
149
+ return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style="[^"]*"/, 'style="filled,dashed"') : recolored;
150
+ }
151
+ contextFill(paint) {
152
+ if (paint.kind === WpProductPaintKind.NONE)
153
+ return WpProductPainter.NONE_FILL;
154
+ if (paint.kind === WpProductPaintKind.OFF)
155
+ return WpProductPainter.OFF_FILL;
156
+ if (paint.kind === WpProductPaintKind.SHARED)
157
+ return WpProductPainter.SHARED_FILL;
158
+ return paint.colors.join(':');
159
+ }
160
+ }
161
+ /** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */
162
+ class WpProductToggles {
163
+ /** The products the legend lists, in its order, once each. */
164
+ products() {
165
+ const order = [];
166
+ this.inputs().forEach((input) => {
167
+ const product = input.dataset['wpProductToggle'] ?? '';
168
+ if (product !== '' && !order.includes(product))
169
+ order.push(product);
170
+ });
171
+ return order;
172
+ }
173
+ /** Each checkbox switches its product ON or OFF. */
174
+ wire(onChange) {
175
+ this.inputs().forEach((input) => {
176
+ input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));
177
+ });
178
+ }
179
+ /** Every copy of every checkbox shows the ON set. */
180
+ sync(on) {
181
+ this.inputs().forEach((input) => {
182
+ input.checked = on.has(input.dataset['wpProductToggle'] ?? '');
183
+ });
184
+ }
185
+ /** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */
186
+ painter(palette, on) {
187
+ const legend = this.products()
188
+ .map((product) => palette.find((color) => color.product === product))
189
+ .filter((color) => color !== undefined);
190
+ return new WpProductPainter(legend, new Set(on));
191
+ }
192
+ inputs() {
193
+ return document.querySelectorAll('[data-wp-product-toggle]');
194
+ }
195
+ }
196
+ //# sourceMappingURL=graph-product-paint.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-product-paint.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-product-paint.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).\n *\n * Each product's legend row is a toggle. Painting every declared product at once gave a workspace with\n * eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one\n * product's footprint, or where two overlap. So the page keeps an ON set — the first two products in\n * legend (sorted-name) order at EVERY load, never persisted — and repaints from it without\n * regenerating anything: the page already carries each node's products and the ONE palette (#1244).\n *\n * Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no\n * DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);\n * they are spelled here because this script is not a module and cannot import them.\n */\n\n/** Which of the five looks a Product-mode node gets for one ON set. */\nenum WpProductPaintKind {\n /** The node belongs to no product: white, dashed border. */\n NONE = 'none',\n /** Every product it carries is OFF: light grey, still visible. */\n OFF = 'off',\n /** Exactly one of its products is ON: solid in that product's fixed color. */\n SOLID = 'solid',\n /** Two or more of its products are ON: one stripe per ON product. */\n STRIPES = 'stripes',\n /** Every product is ON and the node carries every one: the neutral shared fill. */\n SHARED = 'shared',\n}\n\n/** How one node is painted in Product mode. Data-only. */\nclass WpProductPaint {\n constructor(\n readonly kind: WpProductPaintKind,\n /** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */\n readonly colors: string[],\n /** The text color that reads on the fill. */\n readonly fontColor: string,\n ) {}\n}\n\n/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */\nclass WpProductSelection {\n /** The first two products in legend order. */\n initialOn(order: string[]): Set<string> {\n return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));\n }\n}\n\n/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */\nclass WpProductPainter {\n /** graph-products.ts PRODUCT_SHARED_FILL. */\n static readonly SHARED_FILL = '#c9ced8';\n /** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */\n static readonly NONE_FILL = '#ffffff';\n static readonly NONE_BORDER = '#8a90a0';\n /** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */\n static readonly OFF_FILL = '#eef0f4';\n static readonly OFF_BORDER = '#b4b9c4';\n /** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */\n static readonly DARK_TEXT = '#1a1c22';\n static readonly LIGHT_TEXT = '#ffffff';\n static readonly STRIPED_CLASS = 'wp-striped';\n /** graph-products.ts PRODUCTS_ON_AT_LOAD. */\n static readonly ON_AT_LOAD = 2;\n\n private readonly colors = new Map<string, ProductColorJson>();\n\n /**\n * @param legend the products the legend lists, in its order, each with its FIXED palette color —\n * a color is never reassigned to the ON set, so it always means the same product\n * @param on the products switched ON\n */\n constructor(\n private readonly legend: ProductColorJson[],\n private readonly on: ReadonlySet<string>,\n ) {\n for (const color of legend) this.colors.set(color.product, color);\n }\n\n /** The paint for a node carrying `products`. */\n paint(products: string[]): WpProductPaint {\n if (products.length === 0) return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);\n const on = products.filter((product: string): boolean => this.isOn(product));\n if (on.length === 0) return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);\n if (this.allOn() && this.legend.every((color: ProductColorJson): boolean => products.includes(color.product)))\n return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);\n const colors = on.map((product: string): ProductColorJson => this.colors.get(product) as ProductColorJson);\n if (colors.length === 1) return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);\n return new WpProductPaint(\n WpProductPaintKind.STRIPES,\n colors.map((color: ProductColorJson): string => color.color),\n WpProductPainter.DARK_TEXT,\n );\n }\n\n /**\n * The color a cross-product dependency is dashed in (#1204): the first product it crosses into\n * that is ON; null draws it plain.\n */\n edgeColor(crossesInto: string[]): string | null {\n const into = crossesInto.find((product: string): boolean => this.isOn(product));\n return into === undefined ? null : (this.colors.get(into) as ProductColorJson).color;\n }\n\n /** A node's Product-mode statement. */\n nodeDot(template: ProductTemplateJson, products: string[]): string {\n const paint = this.paint(products);\n if (template.kind === 'context') return this.context(template.head, paint);\n const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;\n return `${template.head}${this.attrs(paint)}, label=<${label}>];\\n`;\n }\n\n /** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */\n edgeDot(edge: RenderEdgeJson): string {\n const color = this.edgeColor(edge.crossesInto);\n if (color === null) return edge.dot;\n return `${edge.dot.slice(0, -2)} [style=dashed, color=\"${color}\", penwidth=2];\\n`;\n }\n\n private isOn(product: string): boolean {\n return this.on.has(product) && this.colors.has(product);\n }\n\n /** Every product is ON — of a legend listing two or more (a lone product is its own color). */\n private allOn(): boolean {\n return this.legend.length > 1 && this.legend.every((color: ProductColorJson): boolean => this.on.has(color.product));\n }\n\n private attrs(paint: WpProductPaint): string {\n const dark = WpProductPainter.DARK_TEXT;\n if (paint.kind === WpProductPaintKind.NONE)\n return `style=\"filled,dashed\", fillcolor=\"${WpProductPainter.NONE_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.NONE_BORDER}\", penwidth=1.5`;\n if (paint.kind === WpProductPaintKind.OFF)\n return `style=\"filled\", fillcolor=\"${WpProductPainter.OFF_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.OFF_BORDER}\"`;\n if (paint.kind === WpProductPaintKind.SHARED) return `style=\"filled\", fillcolor=\"${WpProductPainter.SHARED_FILL}\", fontcolor=\"${dark}\"`;\n if (paint.kind === WpProductPaintKind.SOLID)\n return `style=\"filled\", fillcolor=\"${paint.colors[0]}\", fontcolor=\"${paint.fontColor}\"`;\n return `${this.stripes(paint.colors)}, fontcolor=\"${dark}\"`;\n }\n\n /** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */\n private stripes(colors: string[]): string {\n const share = (1 / colors.length).toFixed(3);\n const weighted = colors.map((color: string, index: number): string =>\n index === colors.length - 1 ? color : `${color};${share}`,\n );\n return `style=\"striped\", fillcolor=\"${weighted.join(':')}\", class=\"${WpProductPainter.STRIPED_CLASS}\"`;\n }\n\n /**\n * A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz\n * cylinders cannot stripe, so several ON products are a gradient; no product is dashed.\n */\n private context(dot: string, paint: WpProductPaint): string {\n const recolored = dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${this.contextFill(paint)}\"`);\n return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"') : recolored;\n }\n\n private contextFill(paint: WpProductPaint): string {\n if (paint.kind === WpProductPaintKind.NONE) return WpProductPainter.NONE_FILL;\n if (paint.kind === WpProductPaintKind.OFF) return WpProductPainter.OFF_FILL;\n if (paint.kind === WpProductPaintKind.SHARED) return WpProductPainter.SHARED_FILL;\n return paint.colors.join(':');\n }\n}\n\n/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */\nclass WpProductToggles {\n /** The products the legend lists, in its order, once each. */\n products(): string[] {\n const order: string[] = [];\n this.inputs().forEach((input: HTMLInputElement) => {\n const product = input.dataset['wpProductToggle'] ?? '';\n if (product !== '' && !order.includes(product)) order.push(product);\n });\n return order;\n }\n\n /** Each checkbox switches its product ON or OFF. */\n wire(onChange: (product: string, on: boolean) => void): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));\n });\n }\n\n /** Every copy of every checkbox shows the ON set. */\n sync(on: ReadonlySet<string>): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.checked = on.has(input.dataset['wpProductToggle'] ?? '');\n });\n }\n\n /** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */\n painter(palette: ProductColorJson[], on: ReadonlySet<string>): WpProductPainter {\n const legend = this.products()\n .map((product: string): ProductColorJson | undefined => palette.find((color: ProductColorJson): boolean => color.product === product))\n .filter((color: ProductColorJson | undefined): color is ProductColorJson => color !== undefined);\n return new WpProductPainter(legend, new Set(on));\n }\n\n private inputs(): NodeListOf<HTMLInputElement> {\n return document.querySelectorAll<HTMLInputElement>('[data-wp-product-toggle]');\n }\n}\n"]}
@@ -17,10 +17,14 @@
17
17
  * between them. A service whose project carries no tag is in no product.
18
18
  * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
19
19
  * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
20
- * order. Both viewers build it over the FULL declared product set, so they agree. Neutral means
21
- * shared by EVERY product; a dashed white box belongs to none.
20
+ * order. Both viewers build it over the FULL declared product set, so they agree, and a color is
21
+ * never reassigned when a product is toggled OFF.
22
+ * - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
23
+ * product's legend row is a toggle and the page repaints in the browser from the ON set
24
+ * (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
25
+ * there, not here.
22
26
  * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
23
- * dashed in the target product's color in the Product color mode.
27
+ * dashed in the target product's color in the Product color mode while that product is ON.
24
28
  *
25
29
  * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
26
30
  * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
@@ -42,11 +46,50 @@ export declare class ProductPaletteSlot {
42
46
  }
43
47
  /** The eight product colors, indexed by slot. Distinct from every runtime and role color. */
44
48
  export declare const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[];
45
- /** A box shared by EVERY product. */
49
+ /** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
46
50
  export declare const PRODUCT_SHARED_FILL = "#c9ced8";
47
51
  /** A box in no product: white, with a dashed border. */
48
52
  export declare const PRODUCT_NONE_FILL = "#ffffff";
49
53
  export declare const PRODUCT_NONE_BORDER = "#8a90a0";
54
+ /**
55
+ * A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
56
+ * visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
57
+ */
58
+ export declare const PRODUCT_OFF_FILL = "#eef0f4";
59
+ export declare const PRODUCT_OFF_BORDER = "#b4b9c4";
60
+ /**
61
+ * How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
62
+ * Always this many, on every load — the selection is never persisted.
63
+ */
64
+ export declare const PRODUCTS_ON_AT_LOAD = 2;
65
+ /** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
66
+ export declare enum ProductTemplateKind {
67
+ /** A project or service box: the page writes its whole statement. */
68
+ BOX = "box",
69
+ /** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
70
+ CONTEXT = "context"
71
+ }
72
+ /**
73
+ * What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
74
+ * A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
75
+ * statement is `head` (the kind statement) with its fill replaced.
76
+ */
77
+ export declare class ProductTemplate {
78
+ readonly kind: ProductTemplateKind;
79
+ /** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
80
+ readonly head: string;
81
+ /** BOX: the HTML label written in dark text; '' for CONTEXT. */
82
+ readonly darkLabel: string;
83
+ /** BOX: the HTML label written in light text; '' for CONTEXT. */
84
+ readonly lightLabel: string;
85
+ constructor(kind: ProductTemplateKind,
86
+ /** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
87
+ head: string,
88
+ /** BOX: the HTML label written in dark text; '' for CONTEXT. */
89
+ darkLabel: string,
90
+ /** BOX: the HTML label written in light text; '' for CONTEXT. */
91
+ lightLabel: string);
92
+ }
50
93
  export declare class ProductPalette {
51
94
  /** Every declared product, sorted, each with its color. */
52
95
  readonly colors: ProductColor[];
@@ -72,13 +115,18 @@ export declare class ProductPalette {
72
115
  * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
73
116
  * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
74
117
  * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
118
+ * Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
75
119
  * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
76
120
  * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
77
121
  * every crossing drawn is an allowed one.
78
122
  */
79
123
  export declare class ProductBoundary {
80
- /** The product the edge crosses into, or null when the target carries every product of the source. */
81
- crossedInto(from: readonly string[], to: readonly string[]): string | null;
124
+ /**
125
+ * The products the edge crosses into, in the target's order: the target's products its source
126
+ * lacks, else (a target narrower than its source) every product of the target. Empty when the
127
+ * target carries every product of the source. The page draws the edge in the first one that is ON.
128
+ */
129
+ crossedInto(from: readonly string[], to: readonly string[]): string[];
82
130
  }
83
131
  /**
84
132
  * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).
@@ -18,16 +18,20 @@
18
18
  * between them. A service whose project carries no tag is in no product.
19
19
  * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
20
20
  * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
21
- * order. Both viewers build it over the FULL declared product set, so they agree. Neutral means
22
- * shared by EVERY product; a dashed white box belongs to none.
21
+ * order. Both viewers build it over the FULL declared product set, so they agree, and a color is
22
+ * never reassigned when a product is toggled OFF.
23
+ * - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
24
+ * product's legend row is a toggle and the page repaints in the browser from the ON set
25
+ * (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
26
+ * there, not here.
23
27
  * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
24
- * dashed in the target product's color in the Product color mode.
28
+ * dashed in the target product's color in the Product color mode while that product is ON.
25
29
  *
26
30
  * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
27
31
  * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
28
32
  */
29
33
  Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
34
+ exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.ProductTemplate = exports.ProductTemplateKind = exports.PRODUCTS_ON_AT_LOAD = exports.PRODUCT_OFF_BORDER = exports.PRODUCT_OFF_FILL = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
31
35
  const node_crypto_1 = require("node:crypto");
32
36
  // The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than
33
37
  // imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle
@@ -67,11 +71,54 @@ exports.PRODUCT_PALETTE_SLOTS = [
67
71
  new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),
68
72
  new ProductPaletteSlot('#ff9da7', DARK_TEXT),
69
73
  ];
70
- /** A box shared by EVERY product. */
74
+ /** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
71
75
  exports.PRODUCT_SHARED_FILL = '#c9ced8';
72
76
  /** A box in no product: white, with a dashed border. */
73
77
  exports.PRODUCT_NONE_FILL = '#ffffff';
74
78
  exports.PRODUCT_NONE_BORDER = '#8a90a0';
79
+ /**
80
+ * A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
81
+ * visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
82
+ */
83
+ exports.PRODUCT_OFF_FILL = '#eef0f4';
84
+ exports.PRODUCT_OFF_BORDER = '#b4b9c4';
85
+ /**
86
+ * How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
87
+ * Always this many, on every load — the selection is never persisted.
88
+ */
89
+ exports.PRODUCTS_ON_AT_LOAD = 2;
90
+ /** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
91
+ var ProductTemplateKind;
92
+ (function (ProductTemplateKind) {
93
+ /** A project or service box: the page writes its whole statement. */
94
+ ProductTemplateKind["BOX"] = "box";
95
+ /** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
96
+ ProductTemplateKind["CONTEXT"] = "context";
97
+ })(ProductTemplateKind || (exports.ProductTemplateKind = ProductTemplateKind = {}));
98
+ /**
99
+ * What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
100
+ * A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
101
+ * statement is `head` (the kind statement) with its fill replaced.
102
+ */
103
+ class ProductTemplate {
104
+ kind;
105
+ head;
106
+ darkLabel;
107
+ lightLabel;
108
+ constructor(kind,
109
+ /** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
110
+ head,
111
+ /** BOX: the HTML label written in dark text; '' for CONTEXT. */
112
+ darkLabel,
113
+ /** BOX: the HTML label written in light text; '' for CONTEXT. */
114
+ lightLabel) {
115
+ this.kind = kind;
116
+ this.head = head;
117
+ this.darkLabel = darkLabel;
118
+ this.lightLabel = lightLabel;
119
+ }
120
+ }
121
+ exports.ProductTemplate = ProductTemplate;
75
122
  class ProductPalette {
76
123
  /** Every declared product, sorted, each with its color. */
77
124
  colors;
@@ -136,18 +183,24 @@ exports.ProductPalette = ProductPalette;
136
183
  * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
137
184
  * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
138
185
  * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
186
+ * Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
139
187
  * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
140
188
  * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
141
189
  * every crossing drawn is an allowed one.
142
190
  */
143
191
  class ProductBoundary {
144
- /** The product the edge crosses into, or null when the target carries every product of the source. */
192
+ /**
193
+ * The products the edge crosses into, in the target's order: the target's products its source
194
+ * lacks, else (a target narrower than its source) every product of the target. Empty when the
195
+ * target carries every product of the source. The page draws the edge in the first one that is ON.
196
+ */
145
197
  crossedInto(from, to) {
146
198
  if (from.length === 0 || to.length === 0)
147
- return null;
199
+ return [];
148
200
  if (from.every((product) => to.includes(product)))
149
- return null;
150
- return to.find((product) => !from.includes(product)) ?? to[0];
201
+ return [];
202
+ const added = to.filter((product) => !from.includes(product));
203
+ return added.length > 0 ? added : [...to];
151
204
  }
152
205
  }
153
206
  exports.ProductBoundary = ProductBoundary;
@@ -1 +1 @@
1
- {"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the\n * products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS\n * ONLY (#1216) on both graphs — nothing is inherited over an edge:\n *\n * - ProductMembership — the dependency graph. A project's products are exactly its own declared\n * `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,\n * and renders as \"no product\" — a box that still needs a tag. A consumer that wants every project\n * assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation\n * instead of quietly sitting outside every product (and outside the product dependency matrix,\n * which skips any edge with an untagged end — product-dependencies.ts).\n * - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's\n * declared `product:` tags. A service is never put in a product because another service calls it or\n * feeds its queue; a call from one product into another product's service shows as the runtime EDGE\n * between them. A service whose project carries no tag is in no product.\n * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each\n * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name\n * order. Both viewers build it over the FULL declared product set, so they agree. Neutral means\n * shared by EVERY product; a dashed white box belongs to none.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode.\n *\n * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into\n * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** One of the palette's eight colors, and the text color that reads on it. Data-only. */\nexport class ProductPaletteSlot {\n constructor(\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */\nexport const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[] = [\n new ProductPaletteSlot('#3f6fb5', LIGHT_TEXT),\n new ProductPaletteSlot('#f28e2b', DARK_TEXT),\n new ProductPaletteSlot('#c0392b', LIGHT_TEXT),\n new ProductPaletteSlot('#5cb8b2', DARK_TEXT),\n new ProductPaletteSlot('#3d8b3d', LIGHT_TEXT),\n new ProductPaletteSlot('#edc948', DARK_TEXT),\n new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),\n new ProductPaletteSlot('#ff9da7', DARK_TEXT),\n];\n\n/** A box shared by EVERY product. */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n /**\n * @param products the FULL declared product set. Collision resolution depends on the set, so a\n * viewer showing a subset must still pass every declared product, or its colors would\n * disagree with the other viewer's.\n */\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n const taken = new Set<number>();\n this.colors = sorted.map((product: string): ProductColor => {\n const slot = this.freeSlot(this.preferredSlot(product), taken);\n taken.add(slot);\n return this.paint(product, slot);\n });\n }\n\n /** The slot a product's name hashes to: SHA-256 of the name, first byte, mod the palette size. */\n preferredSlot(product: string): number {\n return createHash('sha256').update(product, 'utf8').digest()[0] % PRODUCT_PALETTE_SLOTS.length;\n }\n\n /** The preferred slot when free, else the next free one (wrapping); with all taken, the preferred one. */\n private freeSlot(preferred: number, taken: ReadonlySet<number>): number {\n for (let step = 0; step < PRODUCT_PALETTE_SLOTS.length; step++) {\n const slot = (preferred + step) % PRODUCT_PALETTE_SLOTS.length;\n if (!taken.has(slot)) return slot;\n }\n return preferred;\n }\n\n private paint(product: string, slot: number): ProductColor {\n const entry = PRODUCT_PALETTE_SLOTS[slot];\n return new ProductColor(product, entry.color, entry.fontColor);\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n // The assignment contract (#1244): the slot SHA-256 of the name prefers, with collisions\n // resolved in sorted-name order over the whole set — the earlier name keeps a contested slot,\n // the later takes the next free one. So adding a product recolors nobody unless it hashes onto\n // a taken slot AND sorts before its holder. Never assign by sorted index: every addition would\n // shift every later product. A product outside the set gets its preferred slot.\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n this.paint(product, this.preferredSlot(product))\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /** The product the edge crosses into, or null when the target carries every product of the source. */\n crossedInto(from: readonly string[], to: readonly string[]): string | null {\n if (from.length === 0 || to.length === 0) return null;\n if (from.every((product: string): boolean => to.includes(product))) return null;\n return to.find((product: string): boolean => !from.includes(product)) ?? to[0];\n }\n}\n\n/**\n * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted declared products. An untagged project is absent — it is in no\n * product, however many tagged projects depend on it — so with no tag anywhere the map is\n * empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const result = new Map<string, string[]>();\n seeds.forEach((products: readonly string[], project: string): void => {\n if (products.length > 0 && graph[project] !== undefined) result.set(project, [...new Set(products)].sort());\n });\n return result;\n }\n}\n\n/**\n * The runtime graph's product membership: exactly each service's project's declared `product:` tags\n * (#1216). Never derived from who calls a service or feeds its queue.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` whose project declares any. `projects` is the\n * dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);\n * a service whose project declares none gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n for (const [name, service] of Object.entries(graph.services)) {\n const declared = projects[name]?.products ?? [];\n if (declared.length > 0) service.products = [...declared].sort();\n }\n }\n}\n"]}
1
+ {"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the\n * products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS\n * ONLY (#1216) on both graphs — nothing is inherited over an edge:\n *\n * - ProductMembership — the dependency graph. A project's products are exactly its own declared\n * `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,\n * and renders as \"no product\" — a box that still needs a tag. A consumer that wants every project\n * assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation\n * instead of quietly sitting outside every product (and outside the product dependency matrix,\n * which skips any edge with an untagged end — product-dependencies.ts).\n * - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's\n * declared `product:` tags. A service is never put in a product because another service calls it or\n * feeds its queue; a call from one product into another product's service shows as the runtime EDGE\n * between them. A service whose project carries no tag is in no product.\n * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each\n * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name\n * order. Both viewers build it over the FULL declared product set, so they agree, and a color is\n * never reassigned when a product is toggled OFF.\n * - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each\n * product's legend row is a toggle and the page repaints in the browser from the ON set\n * (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed\n * there, not here.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode while that product is ON.\n *\n * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into\n * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** One of the palette's eight colors, and the text color that reads on it. Data-only. */\nexport class ProductPaletteSlot {\n constructor(\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */\nexport const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[] = [\n new ProductPaletteSlot('#3f6fb5', LIGHT_TEXT),\n new ProductPaletteSlot('#f28e2b', DARK_TEXT),\n new ProductPaletteSlot('#c0392b', LIGHT_TEXT),\n new ProductPaletteSlot('#5cb8b2', DARK_TEXT),\n new ProductPaletteSlot('#3d8b3d', LIGHT_TEXT),\n new ProductPaletteSlot('#edc948', DARK_TEXT),\n new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),\n new ProductPaletteSlot('#ff9da7', DARK_TEXT),\n];\n\n/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n/**\n * A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays\n * visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.\n */\nexport const PRODUCT_OFF_FILL = '#eef0f4';\nexport const PRODUCT_OFF_BORDER = '#b4b9c4';\n\n/**\n * How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.\n * Always this many, on every load — the selection is never persisted.\n */\nexport const PRODUCTS_ON_AT_LOAD = 2;\n\n/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */\nexport enum ProductTemplateKind {\n /** A project or service box: the page writes its whole statement. */\n BOX = 'box',\n /** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */\n CONTEXT = 'context',\n}\n\n/**\n * What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.\n * A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT\n * statement is `head` (the kind statement) with its fill replaced.\n */\nexport class ProductTemplate {\n constructor(\n public readonly kind: ProductTemplateKind,\n /** BOX: ` \"id\" [`. CONTEXT: the node's whole kind statement. */\n public readonly head: string,\n /** BOX: the HTML label written in dark text; '' for CONTEXT. */\n public readonly darkLabel: string,\n /** BOX: the HTML label written in light text; '' for CONTEXT. */\n public readonly lightLabel: string,\n ) {}\n}\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n /**\n * @param products the FULL declared product set. Collision resolution depends on the set, so a\n * viewer showing a subset must still pass every declared product, or its colors would\n * disagree with the other viewer's.\n */\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n const taken = new Set<number>();\n this.colors = sorted.map((product: string): ProductColor => {\n const slot = this.freeSlot(this.preferredSlot(product), taken);\n taken.add(slot);\n return this.paint(product, slot);\n });\n }\n\n /** The slot a product's name hashes to: SHA-256 of the name, first byte, mod the palette size. */\n preferredSlot(product: string): number {\n return createHash('sha256').update(product, 'utf8').digest()[0] % PRODUCT_PALETTE_SLOTS.length;\n }\n\n /** The preferred slot when free, else the next free one (wrapping); with all taken, the preferred one. */\n private freeSlot(preferred: number, taken: ReadonlySet<number>): number {\n for (let step = 0; step < PRODUCT_PALETTE_SLOTS.length; step++) {\n const slot = (preferred + step) % PRODUCT_PALETTE_SLOTS.length;\n if (!taken.has(slot)) return slot;\n }\n return preferred;\n }\n\n private paint(product: string, slot: number): ProductColor {\n const entry = PRODUCT_PALETTE_SLOTS[slot];\n return new ProductColor(product, entry.color, entry.fontColor);\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n // The assignment contract (#1244): the slot SHA-256 of the name prefers, with collisions\n // resolved in sorted-name order over the whole set — the earlier name keeps a contested slot,\n // the later takes the next free one. So adding a product recolors nobody unless it hashes onto\n // a taken slot AND sorts before its holder. Never assign by sorted index: every addition would\n // shift every later product. A product outside the set gets its preferred slot.\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n this.paint(product, this.preferredSlot(product))\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /**\n * The products the edge crosses into, in the target's order: the target's products its source\n * lacks, else (a target narrower than its source) every product of the target. Empty when the\n * target carries every product of the source. The page draws the edge in the first one that is ON.\n */\n crossedInto(from: readonly string[], to: readonly string[]): string[] {\n if (from.length === 0 || to.length === 0) return [];\n if (from.every((product: string): boolean => to.includes(product))) return [];\n const added = to.filter((product: string): boolean => !from.includes(product));\n return added.length > 0 ? added : [...to];\n }\n}\n\n/**\n * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted declared products. An untagged project is absent — it is in no\n * product, however many tagged projects depend on it — so with no tag anywhere the map is\n * empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const result = new Map<string, string[]>();\n seeds.forEach((products: readonly string[], project: string): void => {\n if (products.length > 0 && graph[project] !== undefined) result.set(project, [...new Set(products)].sort());\n });\n return result;\n }\n}\n\n/**\n * The runtime graph's product membership: exactly each service's project's declared `product:` tags\n * (#1216). Never derived from who calls a service or feeds its queue.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` whose project declares any. `projects` is the\n * dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);\n * a service whose project declares none gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n for (const [name, service] of Object.entries(graph.services)) {\n const declared = projects[name]?.products ?? [];\n if (declared.length > 0) service.products = [...declared].sort();\n }\n }\n}\n"]}
@@ -32,18 +32,18 @@ export declare class RenderEdge {
32
32
  readonly dot: string;
33
33
  readonly id: string;
34
34
  /**
35
- * The statement the architecture page's Product color mode draws instead of `dot` (#1204): a
36
- * dependency crossing into another product, dashed in that product's color. Null for every
37
- * other edge, which draws `dot` in every mode.
35
+ * The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
36
+ * page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
37
+ * for every other edge, which draws `dot` in every mode.
38
38
  */
39
- readonly productDot: string | null;
39
+ readonly crossesInto: string[];
40
40
  constructor(from: string, to: string, dot: string, id: string,
41
41
  /**
42
- * The statement the architecture page's Product color mode draws instead of `dot` (#1204): a
43
- * dependency crossing into another product, dashed in that product's color. Null for every
44
- * other edge, which draws `dot` in every mode.
42
+ * The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
43
+ * page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
44
+ * for every other edge, which draws `dot` in every mode.
45
45
  */
46
- productDot?: string | null);
46
+ crossesInto?: string[]);
47
47
  }
48
48
  /** The immutable drawable snapshot carried by a generated page. */
49
49
  export declare class GraphRenderModel {
@@ -70,11 +70,11 @@ export declare class GraphRenderModel {
70
70
  styledNode(facts: NodeFacts, modes: NodeModeDots): string;
71
71
  completeEndpoints(): void;
72
72
  /**
73
- * One edge. `productAttrs`, when not empty, is the attribute list the Product color mode adds to
74
- * the statement (a later list overrides an earlier one in DOT), e.g. a cross-product dependency's
75
- * dash and color; `fullDot` always carries the plain statement.
73
+ * One edge. `crossesInto`, when not empty, names the products a cross-product dependency crosses
74
+ * into: the page's Product color mode dashes it in the first ON one; `fullDot` always carries the
75
+ * plain statement.
76
76
  */
77
- edge(from: string, to: string, dot: string, productAttrs?: string): string;
77
+ edge(from: string, to: string, dot: string, crossesInto?: string[]): string;
78
78
  }
79
79
  /**
80
80
  * What a runtime page carries for its Goal vs Current color mode (#1239), serialized into the page
@@ -47,19 +47,19 @@ class RenderEdge {
47
47
  to;
48
48
  dot;
49
49
  id;
50
- productDot;
50
+ crossesInto;
51
51
  constructor(from, to, dot, id,
52
52
  /**
53
- * The statement the architecture page's Product color mode draws instead of `dot` (#1204): a
54
- * dependency crossing into another product, dashed in that product's color. Null for every
55
- * other edge, which draws `dot` in every mode.
53
+ * The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
54
+ * page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
55
+ * for every other edge, which draws `dot` in every mode.
56
56
  */
57
- productDot = null) {
57
+ crossesInto = []) {
58
58
  this.from = from;
59
59
  this.to = to;
60
60
  this.dot = dot;
61
61
  this.id = id;
62
- this.productDot = productDot;
62
+ this.crossesInto = crossesInto;
63
63
  }
64
64
  }
65
65
  exports.RenderEdge = RenderEdge;
@@ -124,16 +124,14 @@ class GraphRenderModel {
124
124
  }
125
125
  }
126
126
  /**
127
- * One edge. `productAttrs`, when not empty, is the attribute list the Product color mode adds to
128
- * the statement (a later list overrides an earlier one in DOT), e.g. a cross-product dependency's
129
- * dash and color; `fullDot` always carries the plain statement.
127
+ * One edge. `crossesInto`, when not empty, names the products a cross-product dependency crosses
128
+ * into: the page's Product color mode dashes it in the first ON one; `fullDot` always carries the
129
+ * plain statement.
130
130
  */
131
- edge(from, to, dot, productAttrs = '') {
131
+ edge(from, to, dot, crossesInto = []) {
132
132
  const id = `wp-real-edge-${this.edges.length}`;
133
- const base = `${dot.slice(0, -2)} [id="${id}"]`;
134
- const statement = `${base};\n`;
135
- const productDot = productAttrs === '' ? null : `${base} [${productAttrs}];\n`;
136
- this.edges.push(new RenderEdge(from, to, statement, id, productDot));
133
+ const statement = `${dot.slice(0, -2)} [id="${id}"];\n`;
134
+ this.edges.push(new RenderEdge(from, to, statement, id, crossesInto));
137
135
  return statement;
138
136
  }
139
137
  }