@formicoidea/labre-framework-wardley 0.30.1 → 0.32.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.
Files changed (51) hide show
  1. package/dist/actions.d.ts +14 -18
  2. package/dist/actions.js +80 -39
  3. package/dist/audit-criteria.d.ts +31 -0
  4. package/dist/audit-criteria.js +90 -0
  5. package/dist/background.d.ts +14 -0
  6. package/dist/background.js +338 -0
  7. package/dist/commands.d.ts +5 -0
  8. package/dist/commands.js +190 -0
  9. package/dist/consts.d.ts +10 -4
  10. package/dist/consts.js +10 -9
  11. package/dist/descriptor.d.ts +8 -3
  12. package/dist/descriptor.js +6 -3
  13. package/dist/effects.d.ts +2 -2
  14. package/dist/effects.js +2 -2
  15. package/dist/element-renderer.d.ts +8 -5
  16. package/dist/element-renderer.js +12 -147
  17. package/dist/element-view.d.ts +8 -4
  18. package/dist/element-view.js +30 -22
  19. package/dist/gradient.d.ts +6 -11
  20. package/dist/gradient.js +58 -47
  21. package/dist/index.d.ts +9 -1
  22. package/dist/index.js +9 -1
  23. package/dist/legend.js +10 -2
  24. package/dist/natures.d.ts +50 -0
  25. package/dist/natures.js +93 -0
  26. package/dist/node/node-renderer.js +1 -1
  27. package/dist/nudges.d.ts +41 -0
  28. package/dist/nudges.js +69 -0
  29. package/dist/profiles.d.ts +2 -0
  30. package/dist/profiles.js +87 -0
  31. package/dist/reading.d.ts +3 -0
  32. package/dist/reading.js +129 -0
  33. package/dist/roles.d.ts +50 -0
  34. package/dist/roles.js +132 -0
  35. package/dist/rules.d.ts +2 -0
  36. package/dist/rules.js +270 -0
  37. package/dist/templates/index.js +54 -10
  38. package/dist/templates/maps.js +122 -18
  39. package/dist/toolbar/config.js +2 -2
  40. package/dist/toolbar/wardley-menu.d.ts +8 -15
  41. package/dist/toolbar/wardley-menu.js +8 -136
  42. package/dist/toolbar/wardley-senior-button.js +1 -1
  43. package/dist/translations.d.ts +16 -0
  44. package/dist/translations.js +24 -0
  45. package/dist/view.d.ts +17 -0
  46. package/dist/view.js +125 -22
  47. package/package.json +2 -2
  48. package/dist/label-layout.d.ts +0 -20
  49. package/dist/label-layout.js +0 -72
  50. package/dist/shortcuts.d.ts +0 -2
  51. package/dist/shortcuts.js +0 -37
package/dist/actions.d.ts CHANGED
@@ -1,16 +1,5 @@
1
1
  import { type WardleyBgVariant } from '@formicoidea/labre-core/model';
2
2
  import type { GfxController } from '@formicoidea/labre-core/std/gfx';
3
- /**
4
- * Standalone creation/activation actions for the Wardley toolbox. They are
5
- * invoked from both the toolbar menu ({@link EdgelessWardleyMenu}) and the
6
- * wardley keyboard shortcuts, so they only depend on the {@link GfxController}.
7
- */
8
- /** Where the action was triggered from — feeds the telemetry payload. */
9
- export interface WardleyActionSource {
10
- segment: string;
11
- module: string;
12
- }
13
- export declare const WARDLEY_SHORTCUT_SOURCE: WardleyActionSource;
14
3
  /**
15
4
  * The single-circle node flavours: one connectable ellipse + a label to its
16
5
  * right, grouped. The glyph itself (anchor silhouette, ecosystem hatching,
@@ -38,17 +27,24 @@ declare const NODE_PRESETS: {
38
27
  readonly label: "Component";
39
28
  };
40
29
  };
41
- export type WardleyNodeKind = keyof typeof NODE_PRESETS;
30
+ /**
31
+ * The subset of {@link WardleyNodeKind} that the single-circle presets above
32
+ * cover. Renamed away from `WardleyNodeKind` on purpose: the model declares a
33
+ * type of that name with SEVEN values, and two homonyms of different
34
+ * cardinality — one of them now the source of the semantic vocabulary — is a
35
+ * trap. `WARDLEY_ROLE[kind]` below only compiles while this stays a subset.
36
+ */
37
+ export type WardleySingleCircleKind = keyof typeof NODE_PRESETS;
42
38
  /** Create a wardley map background of the given variant, viewport-centered. */
43
- export declare function createWardleyBackground(gfx: GfxController, variant?: WardleyBgVariant, source?: WardleyActionSource): void;
39
+ export declare function createWardleyBackground(gfx: GfxController, variant?: WardleyBgVariant): void;
44
40
  /**
45
41
  * Create a single-circle node (component / anchor / ecosystem / method):
46
42
  * one connectable native ellipse + a label to its right, grouped so they
47
43
  * move together (enter the group to reposition / edit the label).
48
44
  */
49
- export declare function createWardleyNode(gfx: GfxController, kind: WardleyNodeKind, source?: WardleyActionSource): void;
45
+ export declare function createWardleyNode(gfx: GfxController, kind: WardleySingleCircleKind): void;
50
46
  /** Create an inertia bar (filled black rect). */
51
- export declare function createWardleyInertia(gfx: GfxController, source?: WardleyActionSource): void;
47
+ export declare function createWardleyInertia(gfx: GfxController): void;
52
48
  /**
53
49
  * Create a pipeline: a wide thin native rect body (white semi-transparent,
54
50
  * NON-connectable) + a node-sized square handle straddling its top edge (the
@@ -56,7 +52,7 @@ export declare function createWardleyInertia(gfx: GfxController, source?: Wardle
56
52
  * label are grouped, then grouped again with the body so the whole pipeline
57
53
  * moves as one. Pure composition of native elements — no custom type / view.
58
54
  */
59
- export declare function createWardleyPipeline(gfx: GfxController, source?: WardleyActionSource): void;
55
+ export declare function createWardleyPipeline(gfx: GfxController): void;
60
56
  /**
61
57
  * Create a market: a large thin-bordered circle (the connectable market node)
62
58
  * containing 3 small thick-bordered component nodes wired into a triangle by
@@ -64,11 +60,11 @@ export declare function createWardleyPipeline(gfx: GfxController, source?: Wardl
64
60
  * the node centers and follow on move/resize). A label sits to the right and
65
61
  * everything is grouped into one object.
66
62
  */
67
- export declare function createWardleyMarket(gfx: GfxController, source?: WardleyActionSource): void;
63
+ export declare function createWardleyMarket(gfx: GfxController): void;
68
64
  /**
69
65
  * Activate the native connector tool, pre-styled for a Wardley link (grey,
70
66
  * solid, no arrow) or evolution arrow (red, dashed, FILLED triangle). The
71
67
  * user then draws from one node to another (endpoints attach to centers).
72
68
  */
73
- export declare function activateWardleyConnector(gfx: GfxController, kind: 'link' | 'arrow', source?: WardleyActionSource): void;
69
+ export declare function activateWardleyConnector(gfx: GfxController, kind: 'link' | 'arrow'): void;
74
70
  export {};
package/dist/actions.js CHANGED
@@ -1,23 +1,40 @@
1
- import { DefaultTool } from '@formicoidea/labre-core/blocks/surface';
1
+ import { backgroundSize, DefaultTool } from '@formicoidea/labre-core/blocks/surface';
2
2
  import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
3
3
  import { createGroupCommand } from '@formicoidea/labre-core/gfx/group';
4
4
  import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextFitMode, } from '@formicoidea/labre-core/model';
5
- import { EditPropsStore, TelemetryProvider, } from '@formicoidea/labre-core/shared/services';
5
+ import { EditPropsStore } from '@formicoidea/labre-core/shared/services';
6
6
  import { Bound } from '@formicoidea/labre-core/global/gfx';
7
- import { REF_WIDTH } from './consts';
8
- import { ECOSYSTEM_LABEL, ECOSYSTEM_SIZE, HANDLE_SIZE, INERTIA_COLOR, INERTIA_SIZE, LABEL_DEFAULT, LABEL_FONT_SIZE, LABEL_GAP, LINK_GREY, LINK_STROKE_WIDTH, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LABEL, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, METHOD_FILL, METHOD_LABEL, METHOD_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, PIPELINE_LABEL, PIPELINE_WIDTH, WARDLEY_RED, } from './node/consts';
9
- const TOOLBOX_SOURCE = {
10
- segment: 'wardley toolbox',
11
- module: 'wardley menu',
12
- };
13
- export const WARDLEY_SHORTCUT_SOURCE = {
14
- segment: 'wardley toolbox',
15
- module: 'keyboard shortcut',
16
- };
7
+ import { WARDLEY_BACKGROUND } from './background.js';
8
+ import { ECOSYSTEM_LABEL, ECOSYSTEM_SIZE, HANDLE_SIZE, INERTIA_COLOR, INERTIA_SIZE, LABEL_DEFAULT, LABEL_FONT_SIZE, LABEL_GAP, LINK_GREY, LINK_STROKE_WIDTH, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LABEL, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, METHOD_FILL, METHOD_LABEL, METHOD_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, PIPELINE_LABEL, PIPELINE_WIDTH, WARDLEY_RED, } from './node/consts.js';
9
+ import { WARDLEY_ROLE } from './roles.js';
10
+ /**
11
+ * Standalone creation/activation actions for the Wardley toolbox — the
12
+ * BEHAVIOUR layer, shared by every surface. They only depend on the
13
+ * {@link GfxController}.
14
+ *
15
+ * They no longer emit telemetry: since PF3 the single emission point is the
16
+ * command registry's `runCommand` (`docs/adr/0008`), which is the only function
17
+ * every surface goes through. `WardleyActionSource` is gone with it — the
18
+ * segment/module discrimination it carried is now `CommandInvocation.surface`.
19
+ */
17
20
  /**
18
21
  * Per-variant default label overrides applied at creation (all remain editable
19
22
  * afterwards via the inline editor / toggles). The gradient itself is driven by
20
23
  * `variant` in the renderer.
24
+ *
25
+ * TODO(PF2 follow-up): these write ENGLISH PROSE into the document. Since PF2
26
+ * the ten label props default to `undefined` precisely so the declaration's
27
+ * i18n keys are reachable — but a map created as `opportunity` or `benefit`
28
+ * lands with "Opportunity" / "Benefit" / "Investment" already persisted as if
29
+ * the user had typed them, so those three are un-localisable for the life of
30
+ * the document. Only `classic` and `evolution-gradient` are fully localisable
31
+ * today.
32
+ *
33
+ * The fix is to make the variant part of the declaration — one axis/end-label
34
+ * set per variant, each naming its own `labelKey` — rather than a bag of prop
35
+ * overrides applied at creation. Out of scope here (it changes what a variant
36
+ * IS); duplicated verbatim in `templates/index.ts`, and both copies go away
37
+ * together.
21
38
  */
22
39
  const BACKGROUND_VARIANT_DEFAULTS = {
23
40
  classic: {},
@@ -55,14 +72,6 @@ const NODE_PRESETS = {
55
72
  // Method: the FILL color encodes the chosen method (editable).
56
73
  method: { d: METHOD_SIZE, fill: METHOD_FILL, label: METHOD_LABEL },
57
74
  };
58
- function track(gfx, source, event, element) {
59
- gfx.std.getOptional(TelemetryProvider)?.track(event, {
60
- framework: 'wardley',
61
- element,
62
- page: 'whiteboard editor',
63
- ...source,
64
- });
65
- }
66
75
  function finish(gfx, id) {
67
76
  gfx.doc.captureSync();
68
77
  gfx.tool.setTool(DefaultTool);
@@ -78,10 +87,18 @@ function group(gfx, ids) {
78
87
  return result.groupId || ids[0];
79
88
  }
80
89
  /** Add a native ellipse wardley node centred on (cx, cy). */
81
- function addEllipseNode(surface, kind, cx, cy, d, fillColor, strokeWidth = NODE_STROKE_WIDTH) {
90
+ function addEllipseNode(surface, kind, cx, cy, d, fillColor, strokeWidth = NODE_STROKE_WIDTH,
91
+ // The market's three inner dots are the GLYPH's own wiring, not artefacts the
92
+ // user placed — the same reason its triangle connectors carry no role. They
93
+ // sit inside the market circle by construction, so a role would make every
94
+ // market composite report an overlap with itself (W3).
95
+ neutral = false) {
82
96
  return surface.addElement({
83
97
  type: 'wardleyNode',
84
98
  kind,
99
+ // Semantic identity (PF1): posted next to `kind`, which stays untouched
100
+ // and keeps driving the rendering.
101
+ role: neutral ? undefined : WARDLEY_ROLE[kind],
85
102
  shapeType: 'ellipse',
86
103
  filled: true,
87
104
  fillColor,
@@ -97,6 +114,11 @@ function addLabel(surface, text, x, y, textAlign = 'left') {
97
114
  return surface.addElement({
98
115
  type: 'text',
99
116
  text,
117
+ // Semantic identity (PF1, revised in PF13.4): a Wardley label is a free
118
+ // text element like any other, so its ROLE is the only thing that tells W3
119
+ // it must not land on top of a node. A free text the user typed elsewhere
120
+ // stays neutral and is never evaluated.
121
+ role: WARDLEY_ROLE.label,
100
122
  fontFamily: FontFamily.Inter,
101
123
  fontSize: LABEL_FONT_SIZE,
102
124
  color: NODE_STROKE,
@@ -105,23 +127,34 @@ function addLabel(surface, text, x, y, textAlign = 'left') {
105
127
  });
106
128
  }
107
129
  /** Create a wardley map background of the given variant, viewport-centered. */
108
- export function createWardleyBackground(gfx, variant = 'classic', source = TOOLBOX_SOURCE) {
130
+ export function createWardleyBackground(gfx, variant = 'classic') {
109
131
  if (!gfx.surface)
110
132
  return;
111
- let width = REF_WIDTH;
133
+ // A second map matches the biggest one already on the board rather than
134
+ // shrinking beside it. The reference size and the locked 16:9 proportion are
135
+ // the declaration's (`geometry`), not this function's.
136
+ let atLeastWidth = 0;
137
+ let atLeastHeight = 0;
112
138
  for (const el of gfx.surface.getElementsByType('wardley')) {
113
139
  const [, , ew, eh] = el.deserializedXYWH;
114
- width = Math.max(width, ew, (eh * 16) / 9);
140
+ atLeastWidth = Math.max(atLeastWidth, ew);
141
+ atLeastHeight = Math.max(atLeastHeight, eh);
115
142
  }
116
- const height = (width * 9) / 16;
143
+ const { width, height } = backgroundSize(WARDLEY_BACKGROUND, atLeastWidth, atLeastHeight);
117
144
  const { centerX, centerY } = gfx.viewport;
118
145
  const id = gfx.surface.addElement({
119
- type: 'wardley',
146
+ type: WARDLEY_BACKGROUND.type,
147
+ // The map is a first-class role: validation rules position artefacts
148
+ // against `wardley:map`, never against the `wardley` element type. The
149
+ // declaration owns it, so a templated map and a hand-drawn one agree.
150
+ role: WARDLEY_BACKGROUND.role,
151
+ // A map is a frame you place things on, not a shape you nudge — the
152
+ // declaration decides, and the toolbar toggle takes over from there.
153
+ resizeEnabled: WARDLEY_BACKGROUND.geometry.resizable,
120
154
  variant,
121
155
  ...BACKGROUND_VARIANT_DEFAULTS[variant],
122
156
  xywh: new Bound(centerX - width / 2, centerY - height / 2, width, height).serialize(),
123
157
  });
124
- track(gfx, source, 'FrameworkElementAdded', `background:${variant}`);
125
158
  finish(gfx, id);
126
159
  }
127
160
  /**
@@ -129,7 +162,7 @@ export function createWardleyBackground(gfx, variant = 'classic', source = TOOLB
129
162
  * one connectable native ellipse + a label to its right, grouped so they
130
163
  * move together (enter the group to reposition / edit the label).
131
164
  */
132
- export function createWardleyNode(gfx, kind, source = TOOLBOX_SOURCE) {
165
+ export function createWardleyNode(gfx, kind) {
133
166
  const surface = gfx.surface;
134
167
  if (!surface)
135
168
  return;
@@ -137,11 +170,10 @@ export function createWardleyNode(gfx, kind, source = TOOLBOX_SOURCE) {
137
170
  const { centerX: cx, centerY: cy } = gfx.viewport;
138
171
  const nodeId = addEllipseNode(surface, kind, cx, cy, d, fill);
139
172
  const labelId = addLabel(surface, label, cx + d / 2 + LABEL_GAP, cy - LABEL_H / 2);
140
- track(gfx, source, 'FrameworkElementAdded', `node:${kind}`);
141
173
  finish(gfx, group(gfx, [nodeId, labelId]));
142
174
  }
143
175
  /** Create an inertia bar (filled black rect). */
144
- export function createWardleyInertia(gfx, source = TOOLBOX_SOURCE) {
176
+ export function createWardleyInertia(gfx) {
145
177
  if (!gfx.surface)
146
178
  return;
147
179
  const { w, h } = INERTIA_SIZE;
@@ -149,6 +181,9 @@ export function createWardleyInertia(gfx, source = TOOLBOX_SOURCE) {
149
181
  const id = gfx.surface.addElement({
150
182
  type: 'shape',
151
183
  shapeType: 'rect',
184
+ // The inertia bar has no element type of its own — it IS a plain filled
185
+ // rect — so the role is the whole of its semantics (PF13.5).
186
+ role: WARDLEY_ROLE.inertia,
152
187
  filled: true,
153
188
  fillColor: INERTIA_COLOR,
154
189
  strokeColor: INERTIA_COLOR,
@@ -160,7 +195,6 @@ export function createWardleyInertia(gfx, source = TOOLBOX_SOURCE) {
160
195
  textFitMode: TextFitMode.Overflow,
161
196
  xywh: new Bound(centerX - w / 2, centerY - h / 2, w, h).serialize(),
162
197
  });
163
- track(gfx, source, 'FrameworkElementAdded', 'node:inertia');
164
198
  finish(gfx, id);
165
199
  }
166
200
  /**
@@ -170,7 +204,7 @@ export function createWardleyInertia(gfx, source = TOOLBOX_SOURCE) {
170
204
  * label are grouped, then grouped again with the body so the whole pipeline
171
205
  * moves as one. Pure composition of native elements — no custom type / view.
172
206
  */
173
- export function createWardleyPipeline(gfx, source = TOOLBOX_SOURCE) {
207
+ export function createWardleyPipeline(gfx) {
174
208
  if (!gfx.surface)
175
209
  return;
176
210
  const { centerX: cx, centerY: cy } = gfx.viewport;
@@ -182,6 +216,7 @@ export function createWardleyPipeline(gfx, source = TOOLBOX_SOURCE) {
182
216
  const bodyId = gfx.surface.addElement({
183
217
  type: 'wardleyNode',
184
218
  kind: 'pipeline',
219
+ role: WARDLEY_ROLE.pipeline,
185
220
  shapeType: 'rect',
186
221
  filled: true,
187
222
  fillColor: PIPELINE_FILL,
@@ -197,6 +232,7 @@ export function createWardleyPipeline(gfx, source = TOOLBOX_SOURCE) {
197
232
  const handleId = gfx.surface.addElement({
198
233
  type: 'wardleyNode',
199
234
  kind: 'handle',
235
+ role: WARDLEY_ROLE.handle,
200
236
  shapeType: 'rect',
201
237
  filled: true,
202
238
  fillColor: NODE_FILL,
@@ -211,7 +247,6 @@ export function createWardleyPipeline(gfx, source = TOOLBOX_SOURCE) {
211
247
  const labelId = addLabel(gfx.surface, PIPELINE_LABEL, cx - 60, top - d / 2 - LABEL_H - LABEL_GAP, 'center');
212
248
  // Nested groups: (handle + label), then (body + that group).
213
249
  const innerId = group(gfx, [handleId, labelId]);
214
- track(gfx, source, 'FrameworkElementAdded', 'node:pipeline');
215
250
  finish(gfx, group(gfx, [bodyId, innerId]));
216
251
  }
217
252
  /**
@@ -221,7 +256,7 @@ export function createWardleyPipeline(gfx, source = TOOLBOX_SOURCE) {
221
256
  * the node centers and follow on move/resize). A label sits to the right and
222
257
  * everything is grouped into one object.
223
258
  */
224
- export function createWardleyMarket(gfx, source = TOOLBOX_SOURCE) {
259
+ export function createWardleyMarket(gfx) {
225
260
  const surface = gfx.surface;
226
261
  if (!surface)
227
262
  return;
@@ -237,7 +272,7 @@ export function createWardleyMarket(gfx, source = TOOLBOX_SOURCE) {
237
272
  [rho * sin60, rho / 2],
238
273
  [-rho * sin60, rho / 2],
239
274
  ];
240
- const dotIds = verts.map(([vx, vy]) => addEllipseNode(surface, 'component', cx + vx, cy + vy, MARKET_DOT_SIZE, NODE_FILL, MARKET_DOT_STROKE_WIDTH));
275
+ const dotIds = verts.map(([vx, vy]) => addEllipseNode(surface, 'component', cx + vx, cy + vy, MARKET_DOT_SIZE, NODE_FILL, MARKET_DOT_STROKE_WIDTH, true));
241
276
  // Triangle: 3 attached connectors (auto-route center-to-center, clipped).
242
277
  const connIds = [
243
278
  [dotIds[0], dotIds[1]],
@@ -255,7 +290,6 @@ export function createWardleyMarket(gfx, source = TOOLBOX_SOURCE) {
255
290
  rearEndpointStyle: PointStyle.None,
256
291
  }));
257
292
  const labelId = addLabel(surface, MARKET_LABEL, cx + R + LABEL_GAP, cy - LABEL_H / 2);
258
- track(gfx, source, 'FrameworkElementAdded', 'node:market');
259
293
  finish(gfx, group(gfx, [circleId, ...dotIds, ...connIds, labelId]));
260
294
  }
261
295
  /**
@@ -263,7 +297,7 @@ export function createWardleyMarket(gfx, source = TOOLBOX_SOURCE) {
263
297
  * solid, no arrow) or evolution arrow (red, dashed, FILLED triangle). The
264
298
  * user then draws from one node to another (endpoints attach to centers).
265
299
  */
266
- export function activateWardleyConnector(gfx, kind, source = TOOLBOX_SOURCE) {
300
+ export function activateWardleyConnector(gfx, kind) {
267
301
  const props = kind === 'arrow'
268
302
  ? {
269
303
  mode: ConnectorMode.Straight,
@@ -282,8 +316,15 @@ export function activateWardleyConnector(gfx, kind, source = TOOLBOX_SOURCE) {
282
316
  rearEndpointStyle: PointStyle.None,
283
317
  };
284
318
  gfx.std.get(EditPropsStore).recordLastProps('connector', props);
285
- track(gfx, source, 'FrameworkToolPicked', `connector:${kind}`);
286
- gfx.tool.setTool(ConnectorTool, { mode: ConnectorMode.Straight });
319
+ gfx.tool.setTool(ConnectorTool, {
320
+ mode: ConnectorMode.Straight,
321
+ // The value-chain link IS the "depends on" edge of a Wardley map; the
322
+ // change arrow is a movement annotation and gets a role OF ITS OWN
323
+ // (PF13.4, reversing #71 — see `roles.ts`). Two roles, never one
324
+ // specialising the other: W1 is about where an arrow points and must never
325
+ // fall on a dependency.
326
+ role: kind === 'link' ? WARDLEY_ROLE.dependency : WARDLEY_ROLE.changeArrow,
327
+ });
287
328
  // The wardley palette stays open (native sub-menu behaviour): it only
288
329
  // closes on re-click of the senior button, another senior tool, or Escape.
289
330
  }
@@ -0,0 +1,31 @@
1
+ import type { AuditCriterion } from '@formicoidea/labre-core/shared/services';
2
+ /**
3
+ * The three Wardley audit criteria, A1–A3, as DATA (PF14.1).
4
+ *
5
+ * ## Why these three are criteria and not rules
6
+ *
7
+ * A validation rule is decided by geometry: an element is inside a frame or it
8
+ * is not, an arrow runs with an axis or against it. The engine can settle it in
9
+ * microseconds and be right every time, so it does — that is levels 1 and 2.
10
+ *
11
+ * These three cannot be settled that way, and no amount of engineering will
12
+ * change that. "Is this component positioned for the right reason" is a
13
+ * question about the author's argument; "does this map do its job" is a question
14
+ * about a conversation. They are the questions a Wardley coach asks in a review,
15
+ * and they are exactly what an assistant with the map's facts in hand can have
16
+ * an opinion about — an OPINION, hence `audit` severity, hence invisible on the
17
+ * canvas and never a wall.
18
+ *
19
+ * They are versioned like a rule and shipped like one, because the day a
20
+ * criterion's wording changes is a day the answers before and after are no
21
+ * longer comparable, and a host pinning behaviour needs to be able to say so.
22
+ *
23
+ * ## The prompts
24
+ *
25
+ * The prompt is the criterion, in words, addressed to the assistant and never
26
+ * rendered — see {@link AuditCriterion.prompt}. They are written as review
27
+ * instructions: what to look at, and what would make the map fail. They name no
28
+ * model, no vendor and no output format: the assistant owns how it answers, the
29
+ * library owns what is asked.
30
+ */
31
+ export declare const WARDLEY_AUDIT_CRITERIA: readonly AuditCriterion[];
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The three Wardley audit criteria, A1–A3, as DATA (PF14.1).
3
+ *
4
+ * ## Why these three are criteria and not rules
5
+ *
6
+ * A validation rule is decided by geometry: an element is inside a frame or it
7
+ * is not, an arrow runs with an axis or against it. The engine can settle it in
8
+ * microseconds and be right every time, so it does — that is levels 1 and 2.
9
+ *
10
+ * These three cannot be settled that way, and no amount of engineering will
11
+ * change that. "Is this component positioned for the right reason" is a
12
+ * question about the author's argument; "does this map do its job" is a question
13
+ * about a conversation. They are the questions a Wardley coach asks in a review,
14
+ * and they are exactly what an assistant with the map's facts in hand can have
15
+ * an opinion about — an OPINION, hence `audit` severity, hence invisible on the
16
+ * canvas and never a wall.
17
+ *
18
+ * They are versioned like a rule and shipped like one, because the day a
19
+ * criterion's wording changes is a day the answers before and after are no
20
+ * longer comparable, and a host pinning behaviour needs to be able to say so.
21
+ *
22
+ * ## The prompts
23
+ *
24
+ * The prompt is the criterion, in words, addressed to the assistant and never
25
+ * rendered — see {@link AuditCriterion.prompt}. They are written as review
26
+ * instructions: what to look at, and what would make the map fail. They name no
27
+ * model, no vendor and no output format: the assistant owns how it answers, the
28
+ * library owns what is asked.
29
+ */
30
+ export const WARDLEY_AUDIT_CRITERIA = [
31
+ {
32
+ id: 'wardley.A1',
33
+ framework: 'wardley',
34
+ labelKey: 'com.labre.wardley.audit.A1',
35
+ fallback: 'Positioning is justified',
36
+ version: 1,
37
+ prompt: [
38
+ 'Assess whether each component is placed on the evolution axis for the',
39
+ 'right reason. Evolution measures how CONTEXTUALLY UBIQUITOUS and',
40
+ 'well-understood a component is to the users of THIS value chain — not',
41
+ 'how modern, sophisticated or technically mature the technology behind',
42
+ 'it is. A component built on cutting-edge technology can be a commodity',
43
+ 'to its users; a component built on decades-old technology can still be',
44
+ 'in genesis for this organisation. Flag components whose position looks',
45
+ 'argued from technical maturity, vendor age, or how recently the team',
46
+ 'adopted them, rather than from how their users perceive and consume',
47
+ 'them. Use the zone each component sits in, the roles it carries and the',
48
+ 'components it depends on. Say which component, in which zone, and what',
49
+ 'about its position does not follow from the value chain around it.',
50
+ ].join(' '),
51
+ },
52
+ {
53
+ id: 'wardley.A2',
54
+ framework: 'wardley',
55
+ labelKey: 'com.labre.wardley.audit.A2',
56
+ fallback: 'The value chain is legible',
57
+ version: 1,
58
+ prompt: [
59
+ 'Assess whether the value chain can actually be read. The map exists to',
60
+ 'be discussed, agreed on and decided from, by people who did not draw',
61
+ 'it. Follow the dependency edges from the anchor (the user and their',
62
+ 'need) down through the chain: does every component have a reason to be',
63
+ 'there that the chain itself states? Look for anchors that are missing or',
64
+ 'that name a system rather than a user need, components that hang off no',
65
+ 'chain, chains that fork into detail nobody would decide from, and',
66
+ 'dependencies whose direction says the opposite of what the layout',
67
+ 'implies. Say what a reader would fail to understand, and where.',
68
+ ].join(' '),
69
+ },
70
+ {
71
+ id: 'wardley.A3',
72
+ framework: 'wardley',
73
+ labelKey: 'com.labre.wardley.audit.A3',
74
+ fallback: 'The model applies here',
75
+ version: 1,
76
+ prompt: [
77
+ 'Assess whether Wardley mapping is the right instrument for what this map',
78
+ 'describes. The model assumes a COMPETITIVE landscape: evolution is',
79
+ 'driven by supply and demand competition, and the strategic play is',
80
+ 'against other actors who could serve the same need. It says little about',
81
+ 'a domain with no competitive pressure — an internal process nobody else',
82
+ 'could supply, a regulatory obligation with a single possible',
83
+ 'implementation, an organisational chart. Judge from the anchor, the user',
84
+ 'need it names, and the nature of the components in the chain. If the',
85
+ 'subject is not a competitive landscape, say so plainly and say which',
86
+ 'other instrument would fit — that is more useful than three careful',
87
+ 'observations about a map that should not exist.',
88
+ ].join(' '),
89
+ },
90
+ ];
@@ -0,0 +1,14 @@
1
+ import type { FrameworkBackgroundDef } from '@formicoidea/labre-core/blocks/surface';
2
+ export declare const WARDLEY_BACKGROUND: FrameworkBackgroundDef;
3
+ /**
4
+ * The props the in-place label editor is allowed to write.
5
+ *
6
+ * A closed list, not `string`: the hit test reports whatever `prop` a
7
+ * declaration names, and since #73 an element preserves keys it does not
8
+ * declare — so a typo in the declaration would happily persist a junk key onto
9
+ * every map it was double-clicked on. This is the gate between "the
10
+ * declaration says so" and "the document gets it".
11
+ */
12
+ export declare const WARDLEY_LABEL_PROPS: readonly ["xAxisTitle", "yAxisTitle", "evolutionStart", "evolutionEnd", "visibilityHigh", "visibilityLow", "phase0", "phase1", "phase2", "phase3"];
13
+ export type WardleyLabelProp = (typeof WARDLEY_LABEL_PROPS)[number];
14
+ export declare function isWardleyLabelProp(prop: string): prop is WardleyLabelProp;