@formicoidea/labre-framework-wardley 0.60.0 → 1.0.0

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.
package/dist/natures.js CHANGED
@@ -20,8 +20,8 @@ import { WARDLEY_ROLE } from './roles.js';
20
20
  *
21
21
  * ## Why it applies to `wardley:component` and not to `'*'`
22
22
  *
23
- * A nature qualifies a component. `market` and `ecosystem` specialise
24
- * `wardley:component`, so they get it for free through `roleIsA` — that is the
23
+ * A nature qualifies a component. `market`, `ecosystem` and `method`
24
+ * specialise `wardley:component`, so they get it for free through `roleIsA` — that is the
25
25
  * entire reason role hierarchy is data. The `anchor` (a user / need) is
26
26
  * deliberately NOT a child of `component` and is deliberately NOT qualified
27
27
  * here: a need has no nature, it has a demand. The map itself, the change
@@ -78,6 +78,68 @@ export const WARDLEY_COMPETITION = {
78
78
  struggle: `${WARDLEY_COMPETITION_TAG_ID}/struggle`,
79
79
  establish: `${WARDLEY_COMPETITION_TAG_ID}/establish`,
80
80
  };
81
+ /**
82
+ * The **method** tag — how a component + method is sourced, run or staffed.
83
+ *
84
+ * The third tag of this pack, written on `wardley:method` alone. Since that
85
+ * role specialises `wardley:component` (ADR 0046), a method node is offered
86
+ * the nature AND this tag in the same dropdown, while a plain component — the
87
+ * parent, which a child's tag never reaches — is offered the nature only.
88
+ *
89
+ * ## Nine values in one tag, not a category tag and a recommendation tag
90
+ *
91
+ * Simon Wardley's three method families each say one thing per evolution
92
+ * phase: a buying policy makes, buys or outsources; project management runs
93
+ * agile, lean or six sigma; attitudes are pioneers, settlers or town planners.
94
+ * The wardley-map-renderer JSON spells the same thing as a pair —
95
+ * `method: { category, recommendation }`, recommendation one of Uncharted /
96
+ * Transitional / Industrialized — and the two encodings are the same nine
97
+ * facts. One value per fact is the shape that admits nothing else: two tags
98
+ * would let a board say "Transitional" with no family, which nothing can paint
99
+ * and no reader can name. The pair stays one pure lookup away in `method.ts`,
100
+ * both directions.
101
+ *
102
+ * The family is still shown twice (PO decision, 2026-10-28): each value has its
103
+ * `group` — the family's title, which the picker prints as a sub-title above
104
+ * its three values — and each of the nine has its own prefabricated ring
105
+ * (`METHOD_STYLE`), which is how the map tells them apart. No family beyond
106
+ * these three: a category the renderer config invented is not a method here.
107
+ *
108
+ * `cardinality: 'single'` because the renderer gives a component ONE method,
109
+ * and because the drawing has one ring to show it with.
110
+ *
111
+ * Labels are the practitioners' own words — Make, Buy, Outsource in the PO's
112
+ * wording — short on purpose: the trigger of the dropdown prints the values
113
+ * in force ("Activity, Buy").
114
+ */
115
+ export const WARDLEY_METHOD_TAG_ID = 'wardley:method';
116
+ /** The nine methods, as value ids. Ids are forever; a def is only deprecated. */
117
+ export const WARDLEY_METHOD = {
118
+ make: `${WARDLEY_METHOD_TAG_ID}/make`,
119
+ buy: `${WARDLEY_METHOD_TAG_ID}/buy`,
120
+ outsource: `${WARDLEY_METHOD_TAG_ID}/outsource`,
121
+ agile: `${WARDLEY_METHOD_TAG_ID}/agile`,
122
+ lean: `${WARDLEY_METHOD_TAG_ID}/lean`,
123
+ sixSigma: `${WARDLEY_METHOD_TAG_ID}/six-sigma`,
124
+ pioneers: `${WARDLEY_METHOD_TAG_ID}/pioneers`,
125
+ settlers: `${WARDLEY_METHOD_TAG_ID}/settlers`,
126
+ townPlanners: `${WARDLEY_METHOD_TAG_ID}/town-planners`,
127
+ };
128
+ /** The three families' titles: the picker's sub-titles and the legend's. */
129
+ export const WARDLEY_METHOD_GROUP = {
130
+ buyingPolicy: {
131
+ label: 'Buying policy',
132
+ labelKey: 'com.labre.wardley.tag.method.group.buying-policy',
133
+ },
134
+ projectManagement: {
135
+ label: 'Project management',
136
+ labelKey: 'com.labre.wardley.tag.method.group.project-management',
137
+ },
138
+ attitudes: {
139
+ label: 'Attitudes',
140
+ labelKey: 'com.labre.wardley.tag.method.group.attitudes',
141
+ },
142
+ };
81
143
  export const WARDLEY_TAG_DEFS = {
82
144
  formatVersion: 1,
83
145
  // The id of this PACK, not of the framework: several packs may extend
@@ -96,12 +158,13 @@ export const WARDLEY_TAG_DEFS = {
96
158
  descriptionKey: 'com.labre.wardley.tag.nature.description',
97
159
  cardinality: 'single',
98
160
  appliesTo: [WARDLEY_ROLE.component],
99
- // No `order`, on either tag of this pack: an absent order sorts by SEED
161
+ // No `order`, on any tag of this pack: an absent order sorts by SEED
100
162
  // order, which is what puts the library's pack ahead of a client's
101
163
  // extension without either having to know about the other. A number here
102
- // would be a claim about packs that do not exist yet — and it would say
103
- // nothing anyway, since the two tags below apply to disjoint roles and
104
- // are therefore never offered in the same dropdown.
164
+ // would be a claim about packs that do not exist yet. Seed order is also
165
+ // the one dropdown that holds two of them — a method node's — right:
166
+ // what the component IS (this tag) reads before how it is sourced
167
+ // (`wardley:method`, last).
105
168
  values: [
106
169
  {
107
170
  id: WARDLEY_NATURE.activity,
@@ -168,5 +231,90 @@ export const WARDLEY_TAG_DEFS = {
168
231
  },
169
232
  ],
170
233
  },
234
+ {
235
+ id: WARDLEY_METHOD_TAG_ID,
236
+ label: 'Method',
237
+ labelKey: 'com.labre.wardley.tag.method',
238
+ description: 'How this component is sourced, run or staffed: a buying policy, a project management method or an attitude, at the phase it suits.',
239
+ descriptionKey: 'com.labre.wardley.tag.method.description',
240
+ cardinality: 'single',
241
+ appliesTo: [WARDLEY_ROLE.method],
242
+ // Family by family, and within one family in evolution order: the
243
+ // dropdown reads as the three rows of Wardley's own table.
244
+ values: [
245
+ {
246
+ id: WARDLEY_METHOD.make,
247
+ group: WARDLEY_METHOD_GROUP.buyingPolicy,
248
+ label: 'Make',
249
+ labelKey: 'com.labre.wardley.tag.method.make',
250
+ description: 'Buying policy, uncharted: make it yourself.',
251
+ descriptionKey: 'com.labre.wardley.tag.method.make.description',
252
+ },
253
+ {
254
+ id: WARDLEY_METHOD.buy,
255
+ group: WARDLEY_METHOD_GROUP.buyingPolicy,
256
+ label: 'Buy',
257
+ labelKey: 'com.labre.wardley.tag.method.buy',
258
+ description: 'Buying policy, transitional: buy a product.',
259
+ descriptionKey: 'com.labre.wardley.tag.method.buy.description',
260
+ },
261
+ {
262
+ id: WARDLEY_METHOD.outsource,
263
+ group: WARDLEY_METHOD_GROUP.buyingPolicy,
264
+ label: 'Outsource',
265
+ labelKey: 'com.labre.wardley.tag.method.outsource',
266
+ description: 'Buying policy, industrialized: rent it as a commodity or a utility.',
267
+ descriptionKey: 'com.labre.wardley.tag.method.outsource.description',
268
+ },
269
+ {
270
+ id: WARDLEY_METHOD.agile,
271
+ group: WARDLEY_METHOD_GROUP.projectManagement,
272
+ label: 'Agile',
273
+ labelKey: 'com.labre.wardley.tag.method.agile',
274
+ description: 'Project management, uncharted: explore and adapt.',
275
+ descriptionKey: 'com.labre.wardley.tag.method.agile.description',
276
+ },
277
+ {
278
+ id: WARDLEY_METHOD.lean,
279
+ group: WARDLEY_METHOD_GROUP.projectManagement,
280
+ label: 'Lean',
281
+ labelKey: 'com.labre.wardley.tag.method.lean',
282
+ description: 'Project management, transitional: remove waste.',
283
+ descriptionKey: 'com.labre.wardley.tag.method.lean.description',
284
+ },
285
+ {
286
+ id: WARDLEY_METHOD.sixSigma,
287
+ group: WARDLEY_METHOD_GROUP.projectManagement,
288
+ label: 'Six Sigma',
289
+ labelKey: 'com.labre.wardley.tag.method.six-sigma',
290
+ description: 'Project management, industrialized: remove deviation.',
291
+ descriptionKey: 'com.labre.wardley.tag.method.six-sigma.description',
292
+ },
293
+ {
294
+ id: WARDLEY_METHOD.pioneers,
295
+ group: WARDLEY_METHOD_GROUP.attitudes,
296
+ label: 'Pioneers',
297
+ labelKey: 'com.labre.wardley.tag.method.pioneers',
298
+ description: 'Attitude, uncharted: people who explore the unknown.',
299
+ descriptionKey: 'com.labre.wardley.tag.method.pioneers.description',
300
+ },
301
+ {
302
+ id: WARDLEY_METHOD.settlers,
303
+ group: WARDLEY_METHOD_GROUP.attitudes,
304
+ label: 'Settlers',
305
+ labelKey: 'com.labre.wardley.tag.method.settlers',
306
+ description: 'Attitude, transitional: people who turn a prototype into a product.',
307
+ descriptionKey: 'com.labre.wardley.tag.method.settlers.description',
308
+ },
309
+ {
310
+ id: WARDLEY_METHOD.townPlanners,
311
+ group: WARDLEY_METHOD_GROUP.attitudes,
312
+ label: 'Town planners',
313
+ labelKey: 'com.labre.wardley.tag.method.town-planners',
314
+ description: 'Attitude, industrialized: people who industrialise into a commodity.',
315
+ descriptionKey: 'com.labre.wardley.tag.method.town-planners.description',
316
+ },
317
+ ],
318
+ },
171
319
  ],
172
320
  };
@@ -1,3 +1,5 @@
1
+ import type { NamedPalette } from '@formicoidea/labre-core/components/color-picker';
2
+ import type { ChromeWording } from '@formicoidea/labre-core/shared/services';
1
3
  /**
2
4
  * Visual constants for Wardley nodes (component + anchor) and the connector /
3
5
  * inertia presets created from the Wardley menu.
@@ -121,7 +123,10 @@ export declare const ECOSYSTEM_LABEL = "Ecosystem";
121
123
  * Method node: a component inscribed in a slightly larger outer circle whose
122
124
  * FILL color encodes the chosen method (editable via the toolbar). Glyph = the
123
125
  * outer circle (colored fill + border, the base ellipse) + a white component
124
- * circle drawn at its center. Default fill is a neutral grey.
126
+ * circle drawn at its center. Default fill is a neutral grey, still written at
127
+ * creation, but no longer what paints the ring: the ring is the method's
128
+ * prefabricated style, `make` when no `wardley:method` tag names one
129
+ * (`METHOD_STYLE`, ADR 0046).
125
130
  */
126
131
  export declare const METHOD_SIZE = 35;
127
132
  export declare const METHOD_FILL = "#d9d9d9";
@@ -129,6 +134,133 @@ export declare const METHOD: {
129
134
  centerRatio: number;
130
135
  };
131
136
  export declare const METHOD_LABEL = "Component";
137
+ /**
138
+ * The nine Wardley swatches' own names, resolved by `resolvePaletteLabel`
139
+ * (`@labre/affine-components/color-picker`) through each swatch's own
140
+ * {@link NamedPalette.labelWording} — the framework-agnostic mechanism a
141
+ * gfx module uses to name a palette without touching `Palette`
142
+ * (`packages/affine/model`, RED ZONE) or importing another framework.
143
+ *
144
+ * Exported so `../translations.ts` can list them in the manifest under
145
+ * source `chrome`, the same treatment `WARDLEY_TOOLBAR_WORDINGS` already
146
+ * gets — a swatch's name is toolbar chrome, re-rendered on every locale
147
+ * switch, never seeded into a document.
148
+ */
149
+ export declare const WARDLEY_PALETTE_WORDING_WONDER: ChromeWording;
150
+ export declare const WARDLEY_PALETTE_WORDING_PEACE: ChromeWording;
151
+ export declare const WARDLEY_PALETTE_WORDING_WAR: ChromeWording;
152
+ export declare const WARDLEY_PALETTE_WORDING_WONDER_LIGHT: ChromeWording;
153
+ export declare const WARDLEY_PALETTE_WORDING_PEACE_LIGHT: ChromeWording;
154
+ export declare const WARDLEY_PALETTE_WORDING_WAR_LIGHT: ChromeWording;
155
+ export declare const WARDLEY_PALETTE_WORDING_RED: ChromeWording;
156
+ export declare const WARDLEY_PALETTE_WORDING_INERTIA: ChromeWording;
157
+ export declare const WARDLEY_PALETTE_WORDING_METHOD_GREY: ChromeWording;
158
+ export declare const WARDLEY_PALETTE_WORDINGS: readonly ChromeWording[];
159
+ /**
160
+ * The Wardley **evolution cycle**, surfaced as ready-made swatches in the node
161
+ * colour picker: Wonder / Peace / War, the three climatic phases a component
162
+ * travels through in Simon Wardley's pattern — saturated first, then the light
163
+ * shades for a fill under a label — followed by the three colours the notation
164
+ * itself already uses (the evolution arrow's red, the inertia bar, a method's
165
+ * neutral fill).
166
+ *
167
+ * They are SHORTCUTS, never constraints: nothing in the map reads a colour, so
168
+ * an author is free to ignore them, and the custom picker stays one click away.
169
+ */
170
+ export declare const WARDLEY_PALETTES: NamedPalette[];
171
+ /**
172
+ * The two greys of the buying-policy ramp below Method grey (`METHOD_FILL`,
173
+ * which is "make"): the three steps of Wardley's own make / buy / outsource
174
+ * shading, light to dark, about 85 %, 55 % and 25 % of white. The darkest is
175
+ * one notch off OWM's `#444444` on purpose: that literal is C4's stencil grey,
176
+ * the dark tables are one table across frameworks, and Wardley's ramp must
177
+ * not take its dark value from another framework's (`notation-dark-palettes`).
178
+ * Both have a dark entry in `../dark-palette.ts` that keeps the ramp's order
179
+ * on the dark card.
180
+ */
181
+ export declare const METHOD_BUY_FILL = "#8c8c8c";
182
+ export declare const METHOD_OUTSOURCE_FILL = "#404040";
183
+ /**
184
+ * The swatch keys the method rings are made of. `swatch` accepts these and
185
+ * no other, so a key used below is always one the guard test checks.
186
+ */
187
+ export declare const METHOD_SWATCH_KEYS: readonly ["Peace", "Peace light", "War", "War light"];
188
+ /** One method's prefabricated ring: what fills it, what draws its line. */
189
+ export interface WardleyMethodStyle {
190
+ fill: string;
191
+ stroke: string;
192
+ /** The ring's line width, never thinner than the node's own rim. */
193
+ lineWidth: number;
194
+ }
195
+ /**
196
+ * The nine methods' rings, PO decision of 2026-10-28: in Labre the colour
197
+ * defines the method, family by family (ADR 0046).
198
+ *
199
+ * - **Buying policy** — Wardley's own style: the ink border, thick, around a
200
+ * grey that darkens with the phase (make, buy, outsource).
201
+ * - **Project management** — intensity of grey: the ink itself at 15 %, 35 %
202
+ * and 55 %, with no border of another colour. Ink with an alpha tail, so the
203
+ * dark card paints the same intensities in its own light ink (a stored
204
+ * `#rrggbbaa` keeps its alpha through the tint).
205
+ * - **Attitudes** — the Wardley palette's own swatches, a "transparent" fill
206
+ * being the light variant of the border's swatch (PO decision,
207
+ * 2026-10-28): pioneers Peace light in a Peace border, settlers solid
208
+ * Peace, town planners War light in a War border.
209
+ *
210
+ * Told apart family by family by the border (ink, none, hue) and within a
211
+ * family by the fill, and none of them is a plain component's white disc.
212
+ *
213
+ * A method node with no method tag paints `make`, the buying-policy family's
214
+ * first value: the default style of the node IS that family (PO decision,
215
+ * 2026-10-28), so its stored fill no longer shows on any method node.
216
+ */
217
+ export declare const METHOD_STYLE: {
218
+ readonly make: {
219
+ readonly fill: "#d9d9d9";
220
+ readonly stroke: string;
221
+ readonly lineWidth: 2;
222
+ };
223
+ readonly buy: {
224
+ readonly fill: "#8c8c8c";
225
+ readonly stroke: string;
226
+ readonly lineWidth: 2;
227
+ };
228
+ readonly outsource: {
229
+ readonly fill: "#404040";
230
+ readonly stroke: string;
231
+ readonly lineWidth: 2;
232
+ };
233
+ readonly agile: {
234
+ readonly fill: string;
235
+ readonly stroke: string;
236
+ readonly lineWidth: 1.5;
237
+ };
238
+ readonly lean: {
239
+ readonly fill: string;
240
+ readonly stroke: string;
241
+ readonly lineWidth: 1.5;
242
+ };
243
+ readonly sixSigma: {
244
+ readonly fill: string;
245
+ readonly stroke: string;
246
+ readonly lineWidth: 1.5;
247
+ };
248
+ readonly pioneers: {
249
+ readonly fill: string;
250
+ readonly stroke: string;
251
+ readonly lineWidth: 2;
252
+ };
253
+ readonly settlers: {
254
+ readonly fill: string;
255
+ readonly stroke: string;
256
+ readonly lineWidth: 2;
257
+ };
258
+ readonly townPlanners: {
259
+ readonly fill: string;
260
+ readonly stroke: string;
261
+ readonly lineWidth: 2;
262
+ };
263
+ };
132
264
  /**
133
265
  * Porter's forces (composite): a large white circle carrying ONE letter, pushed
134
266
  * on from the four cardinal directions by solid red arrows.
@@ -116,7 +116,10 @@ export const ECOSYSTEM_LABEL = 'Ecosystem';
116
116
  * Method node: a component inscribed in a slightly larger outer circle whose
117
117
  * FILL color encodes the chosen method (editable via the toolbar). Glyph = the
118
118
  * outer circle (colored fill + border, the base ellipse) + a white component
119
- * circle drawn at its center. Default fill is a neutral grey.
119
+ * circle drawn at its center. Default fill is a neutral grey, still written at
120
+ * creation, but no longer what paints the ring: the ring is the method's
121
+ * prefabricated style, `make` when no `wardley:method` tag names one
122
+ * (`METHOD_STYLE`, ADR 0046).
120
123
  */
121
124
  export const METHOD_SIZE = 35;
122
125
  export const METHOD_FILL = '#d9d9d9';
@@ -124,6 +127,221 @@ export const METHOD = {
124
127
  centerRatio: 0.5, // inner white component radius / R
125
128
  };
126
129
  export const METHOD_LABEL = 'Component';
130
+ /*
131
+ * The Wardley swatch table, moved here word for word from
132
+ * `../toolbar/node-config.ts` (only `export` added, ADR 0046): the method
133
+ * rings below are painted from Peace and War, and the toolbar module already
134
+ * imports this file, so the ring styles could not import the table back from
135
+ * there without an import loop — nor pull toolbar code into the node painter
136
+ * and the headless entry. The colour picker still builds its list from it.
137
+ */
138
+ /**
139
+ * The nine Wardley swatches' own names, resolved by `resolvePaletteLabel`
140
+ * (`@labre/affine-components/color-picker`) through each swatch's own
141
+ * {@link NamedPalette.labelWording} — the framework-agnostic mechanism a
142
+ * gfx module uses to name a palette without touching `Palette`
143
+ * (`packages/affine/model`, RED ZONE) or importing another framework.
144
+ *
145
+ * Exported so `../translations.ts` can list them in the manifest under
146
+ * source `chrome`, the same treatment `WARDLEY_TOOLBAR_WORDINGS` already
147
+ * gets — a swatch's name is toolbar chrome, re-rendered on every locale
148
+ * switch, never seeded into a document.
149
+ */
150
+ export const WARDLEY_PALETTE_WORDING_WONDER = [
151
+ 'com.labre.wardley.palette.wonder',
152
+ 'Wonder',
153
+ ];
154
+ export const WARDLEY_PALETTE_WORDING_PEACE = [
155
+ 'com.labre.wardley.palette.peace',
156
+ 'Peace',
157
+ ];
158
+ export const WARDLEY_PALETTE_WORDING_WAR = [
159
+ 'com.labre.wardley.palette.war',
160
+ 'War',
161
+ ];
162
+ export const WARDLEY_PALETTE_WORDING_WONDER_LIGHT = [
163
+ 'com.labre.wardley.palette.wonder-light',
164
+ 'Wonder light',
165
+ ];
166
+ export const WARDLEY_PALETTE_WORDING_PEACE_LIGHT = [
167
+ 'com.labre.wardley.palette.peace-light',
168
+ 'Peace light',
169
+ ];
170
+ export const WARDLEY_PALETTE_WORDING_WAR_LIGHT = [
171
+ 'com.labre.wardley.palette.war-light',
172
+ 'War light',
173
+ ];
174
+ export const WARDLEY_PALETTE_WORDING_RED = [
175
+ 'com.labre.wardley.palette.wardley-red',
176
+ 'Wardley red',
177
+ ];
178
+ export const WARDLEY_PALETTE_WORDING_INERTIA = [
179
+ 'com.labre.wardley.palette.inertia',
180
+ 'Inertia',
181
+ ];
182
+ export const WARDLEY_PALETTE_WORDING_METHOD_GREY = [
183
+ 'com.labre.wardley.palette.method-grey',
184
+ 'Method grey',
185
+ ];
186
+ export const WARDLEY_PALETTE_WORDINGS = [
187
+ WARDLEY_PALETTE_WORDING_WONDER,
188
+ WARDLEY_PALETTE_WORDING_PEACE,
189
+ WARDLEY_PALETTE_WORDING_WAR,
190
+ WARDLEY_PALETTE_WORDING_WONDER_LIGHT,
191
+ WARDLEY_PALETTE_WORDING_PEACE_LIGHT,
192
+ WARDLEY_PALETTE_WORDING_WAR_LIGHT,
193
+ WARDLEY_PALETTE_WORDING_RED,
194
+ WARDLEY_PALETTE_WORDING_INERTIA,
195
+ WARDLEY_PALETTE_WORDING_METHOD_GREY,
196
+ ];
197
+ /**
198
+ * The Wardley **evolution cycle**, surfaced as ready-made swatches in the node
199
+ * colour picker: Wonder / Peace / War, the three climatic phases a component
200
+ * travels through in Simon Wardley's pattern — saturated first, then the light
201
+ * shades for a fill under a label — followed by the three colours the notation
202
+ * itself already uses (the evolution arrow's red, the inertia bar, a method's
203
+ * neutral fill).
204
+ *
205
+ * They are SHORTCUTS, never constraints: nothing in the map reads a colour, so
206
+ * an author is free to ignore them, and the custom picker stays one click away.
207
+ */
208
+ export const WARDLEY_PALETTES = [
209
+ {
210
+ key: 'Wonder',
211
+ value: '#3ec9f2',
212
+ labelWording: WARDLEY_PALETTE_WORDING_WONDER,
213
+ },
214
+ {
215
+ key: 'Peace',
216
+ value: '#5b9cf6',
217
+ labelWording: WARDLEY_PALETTE_WORDING_PEACE,
218
+ },
219
+ { key: 'War', value: '#9d6df0', labelWording: WARDLEY_PALETTE_WORDING_WAR },
220
+ {
221
+ key: 'Wonder light',
222
+ value: '#b9e9fa',
223
+ labelWording: WARDLEY_PALETTE_WORDING_WONDER_LIGHT,
224
+ },
225
+ {
226
+ key: 'Peace light',
227
+ value: '#c6dbfc',
228
+ labelWording: WARDLEY_PALETTE_WORDING_PEACE_LIGHT,
229
+ },
230
+ {
231
+ key: 'War light',
232
+ value: '#d9c9fa',
233
+ labelWording: WARDLEY_PALETTE_WORDING_WAR_LIGHT,
234
+ },
235
+ {
236
+ key: 'Wardley red',
237
+ value: WARDLEY_RED,
238
+ labelWording: WARDLEY_PALETTE_WORDING_RED,
239
+ },
240
+ {
241
+ key: 'Inertia',
242
+ value: INERTIA_COLOR,
243
+ labelWording: WARDLEY_PALETTE_WORDING_INERTIA,
244
+ },
245
+ {
246
+ key: 'Method grey',
247
+ value: METHOD_FILL,
248
+ labelWording: WARDLEY_PALETTE_WORDING_METHOD_GREY,
249
+ },
250
+ ];
251
+ /**
252
+ * The two greys of the buying-policy ramp below Method grey (`METHOD_FILL`,
253
+ * which is "make"): the three steps of Wardley's own make / buy / outsource
254
+ * shading, light to dark, about 85 %, 55 % and 25 % of white. The darkest is
255
+ * one notch off OWM's `#444444` on purpose: that literal is C4's stencil grey,
256
+ * the dark tables are one table across frameworks, and Wardley's ramp must
257
+ * not take its dark value from another framework's (`notation-dark-palettes`).
258
+ * Both have a dark entry in `../dark-palette.ts` that keeps the ramp's order
259
+ * on the dark card.
260
+ */
261
+ export const METHOD_BUY_FILL = '#8c8c8c';
262
+ export const METHOD_OUTSOURCE_FILL = '#404040';
263
+ /**
264
+ * The swatch keys the method rings are made of. `swatch` accepts these and
265
+ * no other, so a key used below is always one the guard test checks.
266
+ */
267
+ export const METHOD_SWATCH_KEYS = [
268
+ 'Peace',
269
+ 'Peace light',
270
+ 'War',
271
+ 'War light',
272
+ ];
273
+ /**
274
+ * A Wardley swatch's colour, by the key the colour picker names it with.
275
+ * Throws on a key the table does not hold: a renamed swatch must fail the
276
+ * build (`method.unit.spec.ts` pins every key used here), never paint a ring
277
+ * in no colour.
278
+ */
279
+ function swatch(key) {
280
+ const value = WARDLEY_PALETTES.find(palette => palette.key === key)?.value;
281
+ if (typeof value !== 'string') {
282
+ throw new Error(`No Wardley swatch "${key}" with a plain colour.`);
283
+ }
284
+ return value;
285
+ }
286
+ /** `'#rrggbb'` at `alpha` (two hex digits), as a stored 8-digit hex. */
287
+ const withAlpha = (hex, alpha) => `${hex}${alpha}`;
288
+ /**
289
+ * The nine methods' rings, PO decision of 2026-10-28: in Labre the colour
290
+ * defines the method, family by family (ADR 0046).
291
+ *
292
+ * - **Buying policy** — Wardley's own style: the ink border, thick, around a
293
+ * grey that darkens with the phase (make, buy, outsource).
294
+ * - **Project management** — intensity of grey: the ink itself at 15 %, 35 %
295
+ * and 55 %, with no border of another colour. Ink with an alpha tail, so the
296
+ * dark card paints the same intensities in its own light ink (a stored
297
+ * `#rrggbbaa` keeps its alpha through the tint).
298
+ * - **Attitudes** — the Wardley palette's own swatches, a "transparent" fill
299
+ * being the light variant of the border's swatch (PO decision,
300
+ * 2026-10-28): pioneers Peace light in a Peace border, settlers solid
301
+ * Peace, town planners War light in a War border.
302
+ *
303
+ * Told apart family by family by the border (ink, none, hue) and within a
304
+ * family by the fill, and none of them is a plain component's white disc.
305
+ *
306
+ * A method node with no method tag paints `make`, the buying-policy family's
307
+ * first value: the default style of the node IS that family (PO decision,
308
+ * 2026-10-28), so its stored fill no longer shows on any method node.
309
+ */
310
+ export const METHOD_STYLE = {
311
+ make: { fill: METHOD_FILL, stroke: NODE_STROKE, lineWidth: 2 },
312
+ buy: { fill: METHOD_BUY_FILL, stroke: NODE_STROKE, lineWidth: 2 },
313
+ outsource: { fill: METHOD_OUTSOURCE_FILL, stroke: NODE_STROKE, lineWidth: 2 },
314
+ agile: {
315
+ fill: withAlpha(NODE_STROKE, '26'),
316
+ stroke: withAlpha(NODE_STROKE, '26'),
317
+ lineWidth: 1.5,
318
+ },
319
+ lean: {
320
+ fill: withAlpha(NODE_STROKE, '59'),
321
+ stroke: withAlpha(NODE_STROKE, '59'),
322
+ lineWidth: 1.5,
323
+ },
324
+ sixSigma: {
325
+ fill: withAlpha(NODE_STROKE, '8c'),
326
+ stroke: withAlpha(NODE_STROKE, '8c'),
327
+ lineWidth: 1.5,
328
+ },
329
+ pioneers: {
330
+ fill: swatch('Peace light'),
331
+ stroke: swatch('Peace'),
332
+ lineWidth: 2,
333
+ },
334
+ settlers: {
335
+ fill: swatch('Peace'),
336
+ stroke: swatch('Peace'),
337
+ lineWidth: 2,
338
+ },
339
+ townPlanners: {
340
+ fill: swatch('War light'),
341
+ stroke: swatch('War'),
342
+ lineWidth: 2,
343
+ },
344
+ };
127
345
  /**
128
346
  * Porter's forces (composite): a large white circle carrying ONE letter, pushed
129
347
  * on from the four cardinal directions by solid red arrows.
@@ -11,6 +11,6 @@ import { type WardleyNodeElementModel } from '@formicoidea/labre-core/model';
11
11
  * the hollow center come from the base fill, so colors stay editable.
12
12
  */
13
13
  export declare const wardleyNode: ElementRenderer<WardleyNodeElementModel>;
14
- export declare const WardleyNodeRendererExtension: import("@formicoidea/labre-core/_pkgs/store").ExtensionType & {
15
- identifier: import("@formicoidea/labre-core/_pkgs/global/di").ServiceIdentifier<ElementRenderer<WardleyNodeElementModel>>;
14
+ export declare const WardleyNodeRendererExtension: import("@formicoidea/labre-core/_pkgs/store/index.js").ExtensionType & {
15
+ identifier: import("@formicoidea/labre-core/_pkgs/global/di/types.js").ServiceIdentifier<ElementRenderer<WardleyNodeElementModel>>;
16
16
  };
@@ -1,7 +1,47 @@
1
1
  import { ElementRendererExtension, notationColor, } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { shape as shapeRenderer } from '@formicoidea/labre-core/gfx/shape';
3
3
  import { DefaultTheme, } from '@formicoidea/labre-core/model';
4
- import { ANCHOR, ECOSYSTEM, METHOD, NODE_FILL } from './consts.js';
4
+ import { elementTagValues } from '@formicoidea/labre-core/std/gfx';
5
+ import { wardleyMethodStyle } from '../method.js';
6
+ import { WARDLEY_METHOD_TAG_ID } from '../natures.js';
7
+ import { ANCHOR, ECOSYSTEM, METHOD, METHOD_STYLE, NODE_FILL } from './consts.js';
8
+ /**
9
+ * The outer ring of a method node, painted with its method's prefabricated
10
+ * style (`METHOD_STYLE`, ADR 0046): the one its `wardley:method` tag names,
11
+ * and `make` — the buying-policy family's first value, the node's default
12
+ * style (PO decision, 2026-10-28) — when it carries no tag or a value no pack
13
+ * of this version names.
14
+ *
15
+ * Derived at paint time and never written back. The stored fill and stroke
16
+ * the "colour = phase" palette wrote stay in the document untouched; they no
17
+ * longer reach the ring, which is the method's on every method node.
18
+ *
19
+ * Painted OVER the native ellipse, on its exact path (the shape renderer's
20
+ * own inset of half a stroke, both radii, so a node stretched into an oval is
21
+ * covered too). The paper goes down first, line included, so a wash or a
22
+ * borderless grey reads the same over any stored fill and no dark edge of the
23
+ * native rim survives under it; then the style's fill and line. Every colour
24
+ * goes through the notation tint, so a grey of the ramp and the ink at an
25
+ * alpha take their dark-card value (ADR 0039) and the hues stay.
26
+ */
27
+ function paintMethodRing(model, ctx, renderer, w, h, strokeWidth) {
28
+ const style = wardleyMethodStyle(elementTagValues(model, WARDLEY_METHOD_TAG_ID)[0]) ??
29
+ METHOD_STYLE.make;
30
+ // Both radii are positive: the caller has already returned on a node too
31
+ // small for its own stroke.
32
+ ctx.beginPath();
33
+ ctx.ellipse(w / 2, h / 2, w / 2 - strokeWidth / 2, h / 2 - strokeWidth / 2, 0, 0, Math.PI * 2);
34
+ const paper = notationColor(renderer, NODE_FILL, 'fill');
35
+ ctx.lineWidth = Math.max(strokeWidth, style.lineWidth);
36
+ ctx.fillStyle = paper;
37
+ ctx.fill();
38
+ ctx.strokeStyle = paper;
39
+ ctx.stroke();
40
+ ctx.fillStyle = notationColor(renderer, style.fill, 'fill');
41
+ ctx.fill();
42
+ ctx.strokeStyle = notationColor(renderer, style.stroke, 'ink');
43
+ ctx.stroke();
44
+ }
5
45
  /**
6
46
  * Renderer for a Wardley node. The circle is drawn by REUSING the native shape
7
47
  * renderer (so stroke width, colors and theme behave exactly like a native
@@ -71,6 +111,7 @@ export const wardleyNode = (model, ctx, matrix, renderer, rc, bound) => {
71
111
  }
72
112
  // ── Method: a white component inscribed in the colored outer circle ──
73
113
  if (model.kind === 'method') {
114
+ paintMethodRing(model, ctx, renderer, w, h, strokeWidth);
74
115
  const rInner = R * METHOD.centerRatio;
75
116
  ctx.fillStyle = notationColor(renderer, NODE_FILL, 'fill');
76
117
  ctx.lineWidth = strokeWidth;