@formicoidea/labre-framework-wardley 0.31.0 → 0.33.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 (58) hide show
  1. package/dist/actions.d.ts +63 -20
  2. package/dist/actions.js +190 -57
  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 +9 -0
  8. package/dist/commands.js +352 -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/element-renderer.d.ts +8 -5
  14. package/dist/element-renderer.js +12 -147
  15. package/dist/element-view.d.ts +8 -4
  16. package/dist/element-view.js +30 -22
  17. package/dist/export.d.ts +211 -0
  18. package/dist/export.js +655 -0
  19. package/dist/gradient.d.ts +6 -11
  20. package/dist/gradient.js +59 -48
  21. package/dist/import.d.ts +116 -0
  22. package/dist/import.js +905 -0
  23. package/dist/index.d.ts +27 -1
  24. package/dist/index.js +31 -1
  25. package/dist/interchange.d.ts +80 -0
  26. package/dist/interchange.js +138 -0
  27. package/dist/legend.js +8 -0
  28. package/dist/natures.d.ts +50 -0
  29. package/dist/natures.js +93 -0
  30. package/dist/node/node-renderer.js +1 -1
  31. package/dist/nudges.d.ts +41 -0
  32. package/dist/nudges.js +69 -0
  33. package/dist/profiles.d.ts +2 -0
  34. package/dist/profiles.js +87 -0
  35. package/dist/reading.d.ts +3 -0
  36. package/dist/reading.js +129 -0
  37. package/dist/roles.d.ts +50 -0
  38. package/dist/roles.js +132 -0
  39. package/dist/rules.d.ts +2 -0
  40. package/dist/rules.js +286 -0
  41. package/dist/templates/index.js +66 -10
  42. package/dist/templates/maps.js +146 -22
  43. package/dist/toolbar/config.js +3 -1
  44. package/dist/toolbar/icons.d.ts +20 -0
  45. package/dist/toolbar/icons.js +34 -0
  46. package/dist/toolbar/senior-tool.js +1 -0
  47. package/dist/toolbar/wardley-menu.d.ts +8 -15
  48. package/dist/toolbar/wardley-menu.js +8 -136
  49. package/dist/toolbar/wardley-senior-button.js +12 -6
  50. package/dist/translations.d.ts +16 -0
  51. package/dist/translations.js +24 -0
  52. package/dist/view.d.ts +17 -0
  53. package/dist/view.js +127 -13
  54. package/package.json +2 -2
  55. package/dist/label-layout.d.ts +0 -20
  56. package/dist/label-layout.js +0 -72
  57. package/dist/shortcuts.d.ts +0 -2
  58. package/dist/shortcuts.js +0 -37
@@ -1,8 +1,15 @@
1
1
  import { makeTemplateSnapshot, surfaceText, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextAlign, } from '@formicoidea/labre-core/model';
3
3
  import { REF_WIDTH } from '../consts.js';
4
+ import { WARDLEY_ROLE } from '../roles.js';
4
5
  import { wardleyMaps } from './maps.js';
5
6
  import { ECOSYSTEM_SIZE, HANDLE_SIZE, INERTIA_COLOR, INERTIA_SIZE, LABEL_FONT_SIZE, LINK_GREY, LINK_STROKE_WIDTH, MARKET_DOT_RING, MARKET_DOT_SIZE, MARKET_DOT_STROKE_WIDTH, MARKET_LINK_COLOR, MARKET_LINK_WIDTH, MARKET_SIZE, METHOD_FILL, METHOD_SIZE, NODE_FILL, NODE_SIZE, NODE_STROKE, NODE_STROKE_WIDTH, PIPELINE_FILL, PIPELINE_HEIGHT, PIPELINE_WIDTH, WARDLEY_RED, } from '../node/consts.js';
7
+ /**
8
+ * Verbatim copy of `BACKGROUND_VARIANT_DEFAULTS` in `../actions.ts` — see the
9
+ * TODO there: both copies write English prose into the document for the two
10
+ * value-chain variants, and both go away together when the variant becomes
11
+ * part of the declaration.
12
+ */
6
13
  const VARIANT_DEFAULTS = {
7
14
  classic: {},
8
15
  opportunity: {
@@ -20,13 +27,24 @@ const VARIANT_DEFAULTS = {
20
27
  };
21
28
  const bg = (variant, w = REF_WIDTH) => {
22
29
  const h = Math.round((w * 9) / 16);
23
- return { type: 'wardley', variant, ...VARIANT_DEFAULTS[variant], xywh: `[0,0,${w},${h}]` };
30
+ return {
31
+ type: 'wardley',
32
+ variant,
33
+ ...VARIANT_DEFAULTS[variant],
34
+ xywh: `[0,0,${w},${h}]`,
35
+ };
24
36
  };
25
37
  /** A wardley node ellipse positioned by top-left. */
26
- function node(kind, x, y, d = NODE_SIZE, fill = NODE_FILL, strokeWidth = NODE_STROKE_WIDTH) {
38
+ function node(kind, x, y, d = NODE_SIZE, fill = NODE_FILL, strokeWidth = NODE_STROKE_WIDTH,
39
+ // Neutral for the market's inner dots, exactly as `createWardleyMarket` does:
40
+ // glyph wiring, not artefacts.
41
+ neutral = false) {
27
42
  return {
28
43
  type: 'wardleyNode',
29
44
  kind,
45
+ // A template must produce the same typed artefacts as the toolbox, so a
46
+ // map started from a preset validates like a hand-drawn one.
47
+ role: neutral ? undefined : WARDLEY_ROLE[kind],
30
48
  shapeType: 'ellipse',
31
49
  filled: true,
32
50
  fillColor: fill,
@@ -41,6 +59,7 @@ function label(x, y, str, align = 'left') {
41
59
  return {
42
60
  type: 'text',
43
61
  text: surfaceText(str),
62
+ role: WARDLEY_ROLE.label,
44
63
  color: NODE_STROKE,
45
64
  fontFamily: FontFamily.Inter,
46
65
  fontSize: LABEL_FONT_SIZE,
@@ -48,15 +67,35 @@ function label(x, y, str, align = 'left') {
48
67
  xywh: `[${x},${y},140,26]`,
49
68
  };
50
69
  }
70
+ /**
71
+ * A Wardley connector for the palette samples.
72
+ *
73
+ * `evolution` is the ONE predicate that decides what the stroke means, here and
74
+ * in `maps.ts` alike — `docs/adr/0010` § Compatibility. The two kits used to
75
+ * test different things (`opts.red` here, `o.arrow` there), which was a style
76
+ * inconsistency until W4 started reading these edges and became a SEMANTIC one:
77
+ * what a rule governs must not depend on which authoring helper a template
78
+ * borrowed. Colour is a consequence of the meaning, never its source.
79
+ *
80
+ * `typed: false` drops the role altogether — for a sample that makes no claim
81
+ * about anything (see the "Link" swatch below).
82
+ */
51
83
  function connect(source, target, opts = {}) {
84
+ const role = opts.evolution
85
+ ? WARDLEY_ROLE.changeArrow
86
+ : WARDLEY_ROLE.dependency;
52
87
  return {
53
88
  type: 'connector',
54
89
  mode: ConnectorMode.Straight,
55
- stroke: opts.red ? WARDLEY_RED : LINK_GREY,
56
- strokeStyle: opts.red ? StrokeStyle.Dash : StrokeStyle.Solid,
90
+ // A template must produce the same typed artefacts as the toolbox, or a map
91
+ // started from a preset would validate differently from a hand-drawn one.
92
+ // `undefined` writes nothing: a neutral stroke keeps no `role` key.
93
+ role: opts.typed === false ? undefined : role,
94
+ stroke: opts.evolution ? WARDLEY_RED : LINK_GREY,
95
+ strokeStyle: opts.evolution ? StrokeStyle.Dash : StrokeStyle.Solid,
57
96
  strokeWidth: LINK_STROKE_WIDTH,
58
97
  frontEndpointStyle: PointStyle.None,
59
- rearEndpointStyle: opts.red ? PointStyle.Triangle : PointStyle.None,
98
+ rearEndpointStyle: opts.evolution ? PointStyle.Triangle : PointStyle.None,
60
99
  source,
61
100
  target,
62
101
  };
@@ -64,6 +103,7 @@ function connect(source, target, opts = {}) {
64
103
  const inertia = (x = 0, y = 0) => ({
65
104
  type: 'shape',
66
105
  shapeType: 'rect',
106
+ role: WARDLEY_ROLE.inertia,
67
107
  filled: true,
68
108
  fillColor: INERTIA_COLOR,
69
109
  strokeColor: INERTIA_COLOR,
@@ -80,6 +120,7 @@ function pipeline() {
80
120
  body: {
81
121
  type: 'wardleyNode',
82
122
  kind: 'pipeline',
123
+ role: WARDLEY_ROLE.pipeline,
83
124
  shapeType: 'rect',
84
125
  filled: true,
85
126
  fillColor: PIPELINE_FILL,
@@ -93,6 +134,7 @@ function pipeline() {
93
134
  handle: {
94
135
  type: 'wardleyNode',
95
136
  kind: 'handle',
137
+ role: WARDLEY_ROLE.handle,
96
138
  shapeType: 'rect',
97
139
  filled: true,
98
140
  fillColor: NODE_FILL,
@@ -116,7 +158,9 @@ function market() {
116
158
  [rho * sin60, rho / 2],
117
159
  [-rho * sin60, rho / 2],
118
160
  ];
119
- const dotAt = (vx, vy) => node('component', c + vx - MARKET_DOT_SIZE / 2, c + vy - MARKET_DOT_SIZE / 2, MARKET_DOT_SIZE, NODE_FILL, MARKET_DOT_STROKE_WIDTH);
161
+ const dotAt = (vx, vy) => node('component', c + vx - MARKET_DOT_SIZE / 2, c + vy - MARKET_DOT_SIZE / 2, MARKET_DOT_SIZE, NODE_FILL, MARKET_DOT_STROKE_WIDTH, true);
162
+ // Neutral on purpose: the triangle is the market glyph's own wiring, not a
163
+ // dependency the user drew (same rule as `createWardleyMarket`).
120
164
  const tri = (a, b) => ({
121
165
  type: 'connector',
122
166
  mode: ConnectorMode.Straight,
@@ -139,7 +183,9 @@ function market() {
139
183
  label: label(MARKET_SIZE + 8, 2, 'Market'),
140
184
  };
141
185
  }
142
- const single = (el) => ({ a: el });
186
+ const single = (el) => ({
187
+ a: el,
188
+ });
143
189
  const nodeWithLabel = (kind, d, fill, name) => ({
144
190
  n: node(kind, 0, 0, d, fill),
145
191
  l: label(d + 8, d / 2 - 13, name),
@@ -148,7 +194,12 @@ const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www
148
194
  const bgPreview = (extra = '') => `<svg ${ATTRS} fill="none"><path d="M22 12 V64 H120" stroke="#3b3d42" stroke-width="2"/><path d="M44 12 V64 M68 12 V64 M94 12 V64" stroke="#9aa0a6" stroke-width="0.8"/>${extra}</svg>`;
149
195
  const dotPreview = (fill, sw = 2) => `<svg ${ATTRS} fill="none"><circle cx="67" cy="40" r="13" fill="${fill}" stroke="#1f2328" stroke-width="${sw}"/></svg>`;
150
196
  function tpl(name, preview, elements) {
151
- return { name, type: 'template', preview, content: makeTemplateSnapshot(elements, name) };
197
+ return {
198
+ name,
199
+ type: 'template',
200
+ preview,
201
+ content: makeTemplateSnapshot(elements, name),
202
+ };
152
203
  }
153
204
  export const wardleyTemplateCategory = {
154
205
  name: 'Wardley',
@@ -165,7 +216,12 @@ export const wardleyTemplateCategory = {
165
216
  tpl('Pipeline', `<svg ${ATTRS} fill="none"><rect x="34" y="40" width="66" height="14" fill="#fff" stroke="#1f2328"/><rect x="60" y="33" width="14" height="14" fill="#fff" stroke="#1f2328"/></svg>`, pipeline()),
166
217
  tpl('Market', `<svg ${ATTRS} fill="none"><circle cx="67" cy="40" r="16" fill="#fff" stroke="#1f2328"/><circle cx="67" cy="30" r="3.5" fill="#fff" stroke="#1f2328" stroke-width="1.5"/><circle cx="75" cy="46" r="3.5" fill="#fff" stroke="#1f2328" stroke-width="1.5"/><circle cx="59" cy="46" r="3.5" fill="#fff" stroke="#1f2328" stroke-width="1.5"/><path d="M67 30 L75 46 L59 46 Z" stroke="#1f2328" stroke-width="0.8" fill="none"/></svg>`, market()),
167
218
  tpl('Inertia', `<svg ${ATTRS} fill="none"><rect x="63" y="22" width="8" height="36" fill="#1f2328"/></svg>`, single(inertia())),
168
- tpl('Link', `<svg ${ATTRS} fill="none"><path d="M24 40 H110" stroke="#666" stroke-width="2.4"/></svg>`, single(connect({ position: [0, 0] }, { position: [160, 0] }))),
169
- tpl('Evolution arrow', `<svg ${ATTRS} fill="none"><path d="M24 40 H100" stroke="#d6455d" stroke-width="2.4" stroke-dasharray="6 4"/><path d="M98 33 L112 40 L98 47 Z" fill="#d6455d"/></svg>`, single(connect({ position: [0, 0] }, { position: [160, 0] }, { red: true }))),
219
+ // NEUTRAL on purpose (`docs/adr/0010` § Compatibility). This is a horizontal
220
+ // stroke bound to nothing a sample of a STYLE, in a palette. It was typed
221
+ // `wardley:dependency` only because the helper defaults to that role, and a
222
+ // sample of a stroke makes no claim about who depends on whom. Same call the
223
+ // market glyph's own wiring already makes.
224
+ tpl('Link', `<svg ${ATTRS} fill="none"><path d="M24 40 H110" stroke="#666" stroke-width="2.4"/></svg>`, single(connect({ position: [0, 0] }, { position: [160, 0] }, { typed: false }))),
225
+ tpl('Evolution arrow', `<svg ${ATTRS} fill="none"><path d="M24 40 H100" stroke="#d6455d" stroke-width="2.4" stroke-dasharray="6 4"/><path d="M98 33 L112 40 L98 47 Z" fill="#d6455d"/></svg>`, single(connect({ position: [0, 0] }, { position: [160, 0] }, { evolution: true, typed: false }))),
170
226
  ],
171
227
  };
@@ -1,6 +1,9 @@
1
1
  import { makeTemplateSnapshot, surfaceText, } from '@formicoidea/labre-core/gfx/template';
2
2
  import { ConnectorMode, FontFamily, PointStyle, ShapeStyle, StrokeStyle, TextAlign, } from '@formicoidea/labre-core/model';
3
3
  import { INERTIA_COLOR, LABEL_FONT_SIZE, LINK_GREY, LINK_STROKE_WIDTH, NODE_FILL, NODE_SIZE, NODE_STROKE, WARDLEY_RED, } from '../node/consts.js';
4
+ import { backgroundPlot, backgroundZoneBoundaries, } from '@formicoidea/labre-core/blocks/surface';
5
+ import { WARDLEY_BACKGROUND } from '../background.js';
6
+ import { WARDLEY_ROLE } from '../roles.js';
4
7
  /**
5
8
  * Authoring kit for canonical Wardley maps. Positions are given as
6
9
  * (evolution 0..1, value 0..1) and mapped into the plot interior of a fixed
@@ -11,19 +14,69 @@ import { INERTIA_COLOR, LABEL_FONT_SIZE, LINK_GREY, LINK_STROKE_WIDTH, NODE_FILL
11
14
  * arrow is the red dashed connector. Legends are produced by the editor's
12
15
  * auto-legend action rather than baked into the template.
13
16
  */
14
- const W = 1600;
15
- const H = 900;
16
- const PL = { x: 70, y: 56, w: 1470, h: 786 };
17
+ const W = WARDLEY_BACKGROUND.geometry.width;
18
+ const H = WARDLEY_BACKGROUND.geometry.height;
19
+ /**
20
+ * The plot these templates lay their nodes out in — THE DECLARATION'S, read off
21
+ * `geometry.margin` rather than copied.
22
+ *
23
+ * It used to be a hand-written `{x:70, y:56, w:1470, h:786}`, inset further than
24
+ * the drawn plot (`x 40 → 1570, y 30 → 862`), with a comment calling the drift
25
+ * "harmless (everything lands inside the map) but not the same number".
26
+ *
27
+ * It stopped being harmless the moment a RULE measured against the plot. W2
28
+ * asks whether an inertia bar sits on a declared phase transition, and a
29
+ * transition is a ratio OF THE PLOT: two plots means an evolution of `0.7` in a
30
+ * template lands 25 units away from the `0.7` the background draws. The
31
+ * templates were laid out in one frame of reference and judged in another.
32
+ *
33
+ * Deriving it removes the possibility. The nodes of every preset move by a few
34
+ * units, which is a visual change to FACTORY CONTENT — acceptable, and the
35
+ * reason this could be fixed at the source rather than worked around: no user
36
+ * document is touched, because a template is data we ship, not data they wrote.
37
+ */
38
+ const PLOT = backgroundPlot(WARDLEY_BACKGROUND, W, H);
39
+ const PL = { x: PLOT.x0, y: PLOT.y0, w: PLOT.width, h: PLOT.height };
17
40
  const ex = (e) => PL.x + e * PL.w;
18
41
  const vy = (v) => PL.y + (1 - v) * PL.h;
19
42
  const D = NODE_SIZE; // 18
20
- const bg = (variant = 'classic') => ({ type: 'wardley', variant, xywh: `[0,0,${W},${H}]` });
43
+ /** The evolution transitions, as plot ratios, straight from the declaration. */
44
+ const PHASES = backgroundZoneBoundaries(WARDLEY_BACKGROUND).x;
45
+ /**
46
+ * Where the segment `from → to` crosses the evolution transition at `PHASES[i]`,
47
+ * in (evolution, value) coordinates.
48
+ *
49
+ * This is where an inertia bar belongs: astride the boundary the thing refuses
50
+ * to cross — which is all W2 asks since the PO spelled it out (02/08/2026) — and
51
+ * on the dependency that would have to move, which the rule no longer demands
52
+ * and a well-drawn map still shows. Computed rather than eyeballed, so the
53
+ * symbol stays on both whatever the declaration says either of them is.
54
+ */
55
+ function crossing(from, to, i) {
56
+ const at = PHASES[i];
57
+ const t = (at - from[0]) / (to[0] - from[0]);
58
+ return [at, from[1] + t * (to[1] - from[1])];
59
+ }
60
+ // The map carries `wardley:map`: rules position artefacts against the ROLE, so
61
+ // a templated map is a first-class frame like a hand-drawn one — and since
62
+ // PF13.4 every artefact these presets lay on it (nodes, labels, links, change
63
+ // arrows, inertia bars) carries the same role the toolbox writes.
64
+ const bg = (variant = 'classic') => ({
65
+ type: WARDLEY_BACKGROUND.type,
66
+ role: WARDLEY_BACKGROUND.role,
67
+ resizeEnabled: WARDLEY_BACKGROUND.geometry.resizable,
68
+ variant,
69
+ xywh: `[0,0,${W},${H}]`,
70
+ });
21
71
  function dot(e, v, sw, stroke = NODE_STROKE, fill = NODE_FILL) {
22
72
  const cx = ex(e);
23
73
  const cy = vy(v);
24
74
  return {
25
75
  type: 'wardleyNode',
26
76
  kind: 'component',
77
+ // Templates carry the same semantic roles as the toolbox, so a map started
78
+ // from a preset validates exactly like a hand-drawn one.
79
+ role: WARDLEY_ROLE.component,
27
80
  shapeType: 'ellipse',
28
81
  filled: true,
29
82
  fillColor: fill,
@@ -43,6 +96,7 @@ function stake(e, v) {
43
96
  return {
44
97
  type: 'wardleyNode',
45
98
  kind: 'anchor',
99
+ role: WARDLEY_ROLE.anchor,
46
100
  shapeType: 'ellipse',
47
101
  filled: true,
48
102
  fillColor: NODE_FILL,
@@ -64,22 +118,53 @@ function lbl(e, v, text, o = {}) {
64
118
  return {
65
119
  type: 'text',
66
120
  text: surfaceText(text),
121
+ // The NAME of an artefact, so it carries the label role W3 is written on.
122
+ // The free texts these presets also use for notes and legends stay neutral:
123
+ // they name nothing and nothing measures them.
124
+ role: WARDLEY_ROLE.label,
67
125
  color: o.color ?? NODE_STROKE,
68
126
  fontFamily: FontFamily.Inter,
69
127
  fontSize: o.size ?? LABEL_FONT_SIZE,
70
- textAlign: align === 'right' ? TextAlign.Right : align === 'center' ? TextAlign.Center : TextAlign.Left,
128
+ textAlign: align === 'right'
129
+ ? TextAlign.Right
130
+ : align === 'center'
131
+ ? TextAlign.Center
132
+ : TextAlign.Left,
71
133
  xywh: `[${x},${cy + dy},${w},26]`,
72
134
  };
73
135
  }
136
+ /**
137
+ * A link between two nodes of a shipped map.
138
+ *
139
+ * `evolution` is the ONE predicate that decides what the stroke MEANS — the
140
+ * same flag `templates/index.ts` reads, aligned by `docs/adr/0010`
141
+ * § Compatibility. It used to be `arrow` here and `red` there, so a red SOLID
142
+ * Kodak link was a typed dependency while a red sample in the palette was not:
143
+ * two answers to "is this a dependency?" in one framework, in neighbouring
144
+ * files. Harmless as a style inconsistency, semantic the moment W4 reads these
145
+ * edges.
146
+ *
147
+ * `red` stays, and stays orthogonal: it colours a DEPENDENCY red (Kodak's
148
+ * future chain) without changing what it is. Colour is never what decides a
149
+ * relation's type.
150
+ *
151
+ * The direction is meaning, not decoration: `a` is the consumer, `b` is what it
152
+ * needs, and every one of the twelve links these presets ship respects it
153
+ * (a corpus test walks them and fails loudly the day one does not).
154
+ */
74
155
  function link(a, b, o = {}) {
75
156
  return {
76
157
  type: 'connector',
77
158
  mode: ConnectorMode.Straight,
78
- stroke: o.red ? WARDLEY_RED : LINK_GREY,
79
- strokeStyle: o.arrow ? StrokeStyle.Dash : StrokeStyle.Solid,
159
+ // An evolution arrow is a movement annotation, not a dependency — same
160
+ // split as the two Wardley connector tools, and since PF13.4 both sides of
161
+ // that split carry a role.
162
+ role: o.evolution ? WARDLEY_ROLE.changeArrow : WARDLEY_ROLE.dependency,
163
+ stroke: o.red || o.evolution ? WARDLEY_RED : LINK_GREY,
164
+ strokeStyle: o.evolution ? StrokeStyle.Dash : StrokeStyle.Solid,
80
165
  strokeWidth: LINK_STROKE_WIDTH,
81
166
  frontEndpointStyle: PointStyle.None,
82
- rearEndpointStyle: o.arrow ? PointStyle.Triangle : PointStyle.None,
167
+ rearEndpointStyle: o.evolution ? PointStyle.Triangle : PointStyle.None,
83
168
  source: { id: a },
84
169
  target: { id: b },
85
170
  };
@@ -90,6 +175,7 @@ function inertia(e, v) {
90
175
  return {
91
176
  type: 'shape',
92
177
  shapeType: 'rect',
178
+ role: WARDLEY_ROLE.inertia,
93
179
  filled: true,
94
180
  fillColor: INERTIA_COLOR,
95
181
  strokeColor: INERTIA_COLOR,
@@ -140,7 +226,12 @@ function title(str) {
140
226
  const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
141
227
  const mapPreview = (extra) => `<svg ${ATTRS} fill="none"><path d="M22 12 V64 H120" stroke="#3b3d42" stroke-width="2"/>${extra}</svg>`;
142
228
  function tpl(name, preview, elements) {
143
- return { name, type: 'template', preview, content: makeTemplateSnapshot(elements, name) };
229
+ return {
230
+ name,
231
+ type: 'template',
232
+ preview,
233
+ content: makeTemplateSnapshot(elements, name),
234
+ };
144
235
  }
145
236
  function ann(e, v) {
146
237
  const cx = ex(e);
@@ -178,7 +269,11 @@ function teaShop() {
178
269
  annBox: panel(120, 200, 420, 64),
179
270
  annText: freeText(132, 208, 400, 'Annotations:\n1. Standardising power lets kettles evolve faster\n2. Hot water is obvious and well known', 13),
180
271
  business: stake(0.62, 0.93),
181
- businessL: lbl(0.62, 0.93, 'Business', { align: 'center', dy: -28, w: 120 }),
272
+ businessL: lbl(0.62, 0.93, 'Business', {
273
+ align: 'center',
274
+ dy: -28,
275
+ w: 120,
276
+ }),
182
277
  public: stake(0.78, 0.93),
183
278
  publicL: lbl(0.78, 0.93, 'Public', { align: 'center', dy: -28, w: 120 }),
184
279
  cupOfTea: comp(0.62, 0.74),
@@ -199,9 +294,19 @@ function teaShop() {
199
294
  powerL: lbl(0.7, 0.1, 'Power', { align: 'right', dy: 6 }),
200
295
  powerFut: future(0.88, 0.1),
201
296
  powerFutL: lbl(0.88, 0.1, 'Power'),
202
- limitedBy: lbl(0.56, 0.43, 'limited by', { align: 'center', w: 120, size: 13 }),
203
- ann1a: ann(0.5, 0.385), ann1t: annTxt(0.5, 0.385, '1'),
204
- ann2a: ann(0.84, 0.45), ann2t: annTxt(0.84, 0.45, '2'),
297
+ // ABOVE the link it annotates, not across it. Written on the line it reads
298
+ // as a label nobody can read — which is the finding W3 raises, and it was
299
+ // raising it on the map that ships as the canonical example.
300
+ limitedBy: lbl(0.56, 0.43, 'limited by', {
301
+ align: 'center',
302
+ w: 120,
303
+ size: 13,
304
+ dy: -34,
305
+ }),
306
+ ann1a: ann(0.5, 0.385),
307
+ ann1t: annTxt(0.5, 0.385, '1'),
308
+ ann2a: ann(0.84, 0.45),
309
+ ann2t: annTxt(0.84, 0.45, '2'),
205
310
  l1: link('business', 'cupOfTea'),
206
311
  l2: link('public', 'cupOfTea'),
207
312
  l3: link('cupOfTea', 'cup'),
@@ -210,34 +315,53 @@ function teaShop() {
210
315
  l6: link('hotWater', 'water'),
211
316
  l7: link('hotWater', 'kettle'),
212
317
  l8: link('kettle', 'power'),
213
- a1: link('kettle', 'electric', { red: true, arrow: true }),
214
- a2: link('power', 'powerFut', { red: true, arrow: true }),
318
+ a1: link('kettle', 'electric', { evolution: true }),
319
+ a2: link('power', 'powerFut', { evolution: true }),
215
320
  };
216
321
  }
217
322
  // ── Kodak inertia (2005) ──────────────────────────────────────────────
218
323
  function kodak() {
324
+ // The future dependency `capture → storage` is the movement Kodak resisted,
325
+ // and the inertia bar belongs where that dependency crosses into commodity —
326
+ // the boundary the capability refused to cross. It used to sit 105 units away
327
+ // from any transition: the template named after inertia was the
328
+ // counter-example to the inertia rule. Computed from the two node positions
329
+ // and the declared transitions, so it cannot drift again — and the bar comes
330
+ // out centred on the divider, which is exactly what W2 asks of it now that
331
+ // "astride the transition" is the whole of the rule.
332
+ const CAPTURE = [0.53, 0.8];
333
+ const STORAGE = [0.84, 0.4];
334
+ const [barE, barV] = crossing(CAPTURE, STORAGE, 2);
219
335
  return {
220
336
  bg: bg(),
221
337
  title: title("Wardley map of Kodak's 2005 inertia to digital"),
222
338
  user: stake(0.54, 0.92),
223
339
  userL: lbl(0.54, 0.92, 'User'),
224
- capture: dot(0.53, 0.8, 3),
225
- captureL: lbl(0.53, 0.8, 'Capture a moment'),
340
+ capture: dot(CAPTURE[0], CAPTURE[1], 3),
341
+ // To the LEFT, like the other two capability names: to the right of this
342
+ // node runs the future dependency towards digital storage, and a name
343
+ // written across the line that carries the whole argument is exactly the
344
+ // case W3 exists for.
345
+ captureL: lbl(CAPTURE[0], CAPTURE[1], 'Capture a moment', {
346
+ align: 'right',
347
+ }),
226
348
  film: comp(0.52, 0.62),
227
349
  filmL: lbl(0.52, 0.62, 'Film camera', { align: 'right' }),
228
350
  digital: future(0.74, 0.62),
229
351
  digitalL: lbl(0.74, 0.62, 'Digital camera', { color: WARDLEY_RED }),
230
352
  roll: comp(0.52, 0.4),
231
353
  rollL: lbl(0.52, 0.4, 'Photographic film', { align: 'right' }),
232
- storage: future(0.84, 0.4),
233
- storageL: lbl(0.84, 0.4, 'Digital storage', { color: WARDLEY_RED }),
234
- inertiaBar: inertia(0.78, 0.4),
354
+ storage: future(STORAGE[0], STORAGE[1]),
355
+ storageL: lbl(STORAGE[0], STORAGE[1], 'Digital storage', {
356
+ color: WARDLEY_RED,
357
+ }),
358
+ inertiaBar: inertia(barE, barV),
235
359
  l1: link('user', 'capture'),
236
360
  l2: link('capture', 'film'),
237
361
  l3: link('film', 'roll'),
238
362
  r1: link('capture', 'storage', { red: true }),
239
- a1: link('film', 'digital', { red: true, arrow: true }),
240
- a2: link('roll', 'storage', { red: true, arrow: true }),
363
+ a1: link('film', 'digital', { evolution: true }),
364
+ a2: link('roll', 'storage', { evolution: true }),
241
365
  };
242
366
  }
243
367
  export const wardleyMaps = [
@@ -187,7 +187,9 @@ export const wardleyToolbarConfig = {
187
187
  if (!bg)
188
188
  return;
189
189
  createWardleyLegend(ctx.std, bg);
190
- ctx.std.getOptional(TelemetryProvider)?.track('FrameworkLegendCreated', {
190
+ ctx.std
191
+ .getOptional(TelemetryProvider)
192
+ ?.track('FrameworkLegendCreated', {
191
193
  framework: 'wardley',
192
194
  element: 'legend',
193
195
  page: 'whiteboard editor',
@@ -24,7 +24,27 @@ export declare const wardleyMethodIcon: import("lit-html").TemplateResult<2>;
24
24
  export declare const wardleyLegendIcon: import("lit-html").TemplateResult<2>;
25
25
  /** Opportunity gradient background: axes + green differential hump + red operational bump. */
26
26
  export declare const wardleyOpportunityIcon: import("lit-html").TemplateResult<2>;
27
+ /**
28
+ * Import an SVG sketch — a framed picture with an arrow coming IN.
29
+ *
30
+ * Deliberately the same glyph BPMN gives the same command: the two wrap ONE
31
+ * parser and make one promise, and a reader who has met one should recognise
32
+ * the other without reading its label.
33
+ */
34
+ export declare const wardleyImportSvgIcon: import("lit-html").TemplateResult<2>;
27
35
  /** Benefit/Investment gradient background: axes + zero line + green J-curve. */
28
36
  export declare const wardleyBenefitIcon: import("lit-html").TemplateResult<2>;
29
37
  /** Evolution-gradient background (Wardley's S-curve presentation): grey at both edges fading to white center. */
30
38
  export declare const wardleyEvolutionGradientIcon: import("lit-html").TemplateResult<2>;
39
+ /**
40
+ * Export the map as an OWM document — a page with the map's own axes drawn on
41
+ * it, and an arrow going OUT.
42
+ *
43
+ * The page and the arrow are BPMN's, deliberately: interchange is a platform
44
+ * capability, and a reader who has met one framework's pair should recognise
45
+ * the next without reading its label. What differs is what is drawn ON the page
46
+ * — the value-chain corner, which is what makes it a map rather than a process.
47
+ */
48
+ export declare const wardleyExportOwmIcon: import("lit-html").TemplateResult<2>;
49
+ /** Import an OWM document — the same page and the same arrow, mirrored back IN. */
50
+ export declare const wardleyImportOwmIcon: import("lit-html").TemplateResult<2>;
@@ -114,6 +114,18 @@ export const wardleyOpportunityIcon = svg `<svg width="24" height="24" viewBox="
114
114
  <path d="M5 12 C6 7, 8.5 7, 9.5 11 C11 17, 14 19, 18 20" stroke="${GREEN}" stroke-width="1.7" fill="none" stroke-linecap="round"/>
115
115
  <path d="M14.5 19.5 C15.5 16.5, 17 16.5, 18 19" stroke="${RED}" stroke-width="1.7" fill="none" stroke-linecap="round"/>
116
116
  </svg>`;
117
+ /**
118
+ * Import an SVG sketch — a framed picture with an arrow coming IN.
119
+ *
120
+ * Deliberately the same glyph BPMN gives the same command: the two wrap ONE
121
+ * parser and make one promise, and a reader who has met one should recognise
122
+ * the other without reading its label.
123
+ */
124
+ export const wardleyImportSvgIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
125
+ <rect x="3" y="5" width="14" height="11" rx="1.5" stroke="currentColor" stroke-width="1.5"/>
126
+ <path d="M3.5 13 7 9.5l3 3 2-2 3.5 3.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
127
+ <path d="M19 14.5v6M16.5 17l2.5-2.5 2.5 2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
128
+ </svg>`;
117
129
  /** Benefit/Investment gradient background: axes + zero line + green J-curve. */
118
130
  export const wardleyBenefitIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
119
131
  <path d="M4 20V4M4 20h16" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
@@ -132,3 +144,25 @@ export const wardleyEvolutionGradientIcon = svg `<svg width="24" height="24" vie
132
144
  </defs>
133
145
  <rect x="4" y="4.5" width="16" height="15" rx="2" fill="url(#wardleyGreyU)" stroke="currentColor" stroke-width="1.2"/>
134
146
  </svg>`;
147
+ /**
148
+ * Export the map as an OWM document — a page with the map's own axes drawn on
149
+ * it, and an arrow going OUT.
150
+ *
151
+ * The page and the arrow are BPMN's, deliberately: interchange is a platform
152
+ * capability, and a reader who has met one framework's pair should recognise
153
+ * the next without reading its label. What differs is what is drawn ON the page
154
+ * — the value-chain corner, which is what makes it a map rather than a process.
155
+ */
156
+ export const wardleyExportOwmIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
157
+ <path d="M13.5 3.5H7a1.5 1.5 0 0 0-1.5 1.5v14A1.5 1.5 0 0 0 7 20.5h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
158
+ <path d="M13.5 3.5 18.5 8.5V12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
159
+ <path d="M8.5 7v6.5H13" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" opacity="0.6"/>
160
+ <path d="M16 20.5v-6M13.5 18l2.5 2.5 2.5-2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
161
+ </svg>`;
162
+ /** Import an OWM document — the same page and the same arrow, mirrored back IN. */
163
+ export const wardleyImportOwmIcon = svg `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
164
+ <path d="M13.5 3.5H7a1.5 1.5 0 0 0-1.5 1.5v14A1.5 1.5 0 0 0 7 20.5h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
165
+ <path d="M13.5 3.5 18.5 8.5V12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
166
+ <path d="M8.5 7v6.5H13" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" opacity="0.6"/>
167
+ <path d="M16 14.5v6M13.5 17l2.5-2.5 2.5 2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
168
+ </svg>`;
@@ -3,6 +3,7 @@ import { html } from 'lit';
3
3
  export const wardleySeniorTool = SeniorToolExtension('wardley', ({ block }) => {
4
4
  return {
5
5
  name: 'Wardley map',
6
+ labelKey: 'com.labre.framework.wardley',
6
7
  content: html `<edgeless-wardley-senior-button
7
8
  .edgeless=${block}
8
9
  ></edgeless-wardley-senior-button>`,
@@ -1,20 +1,13 @@
1
1
  import { EmptyTool } from '@formicoidea/labre-core/gfx/pointer';
2
- import { LitElement } from 'lit';
3
- declare const EdgelessWardleyMenu_base: typeof LitElement & import("@formicoidea/labre-core/_pkgs/global/utils").Constructor<import("@formicoidea/labre-core/_pkgs/affine-widget-edgeless-toolbar").EdgelessToolbarToolClass>;
2
+ import { EdgelessCommandMenu } from '@formicoidea/labre-core/widgets/edgeless-toolbar';
4
3
  /**
5
- * The popover that opens above the toolbar for the Wardley toolbox. Each item
6
- * creates a pre-formatted Wardley object (see `../actions.ts` — the same
7
- * actions back the wardley keyboard shortcuts).
4
+ * The popover that opens above the toolbar for the Wardley toolbox.
5
+ *
6
+ * Since PF3 it holds no artefact list of its own: {@link EdgelessCommandMenu}
7
+ * renders whatever `wardleyCommands` declares for the `senior-menu` surface, so
8
+ * the menu and the shortcut manifest can no longer drift (they did: 13 vs 7).
8
9
  */
9
- export declare class EdgelessWardleyMenu extends EdgelessWardleyMenu_base {
10
- static styles: import("lit").CSSResult;
10
+ export declare class EdgelessWardleyMenu extends EdgelessCommandMenu {
11
+ protected owner: "wardley";
11
12
  type: typeof EmptyTool;
12
- private _createBackground;
13
- private _createNode;
14
- private readonly _createInertia;
15
- private readonly _createPipeline;
16
- private readonly _createMarket;
17
- private _activateConnector;
18
- render(): import("lit-html").TemplateResult<1>;
19
13
  }
20
- export {};