@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.
- package/dist/actions.d.ts +14 -18
- package/dist/actions.js +80 -39
- package/dist/audit-criteria.d.ts +31 -0
- package/dist/audit-criteria.js +90 -0
- package/dist/background.d.ts +14 -0
- package/dist/background.js +338 -0
- package/dist/commands.d.ts +5 -0
- package/dist/commands.js +190 -0
- package/dist/consts.d.ts +10 -4
- package/dist/consts.js +10 -9
- package/dist/descriptor.d.ts +8 -3
- package/dist/descriptor.js +6 -3
- package/dist/effects.d.ts +2 -2
- package/dist/effects.js +2 -2
- package/dist/element-renderer.d.ts +8 -5
- package/dist/element-renderer.js +12 -147
- package/dist/element-view.d.ts +8 -4
- package/dist/element-view.js +30 -22
- package/dist/gradient.d.ts +6 -11
- package/dist/gradient.js +58 -47
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/legend.js +10 -2
- package/dist/natures.d.ts +50 -0
- package/dist/natures.js +93 -0
- package/dist/node/node-renderer.js +1 -1
- package/dist/nudges.d.ts +41 -0
- package/dist/nudges.js +69 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +87 -0
- package/dist/reading.d.ts +3 -0
- package/dist/reading.js +129 -0
- package/dist/roles.d.ts +50 -0
- package/dist/roles.js +132 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +270 -0
- package/dist/templates/index.js +54 -10
- package/dist/templates/maps.js +122 -18
- package/dist/toolbar/config.js +2 -2
- package/dist/toolbar/wardley-menu.d.ts +8 -15
- package/dist/toolbar/wardley-menu.js +8 -136
- package/dist/toolbar/wardley-senior-button.js +1 -1
- package/dist/translations.d.ts +16 -0
- package/dist/translations.js +24 -0
- package/dist/view.d.ts +17 -0
- package/dist/view.js +125 -22
- package/package.json +2 -2
- package/dist/label-layout.d.ts +0 -20
- package/dist/label-layout.js +0 -72
- package/dist/shortcuts.d.ts +0 -2
- package/dist/shortcuts.js +0 -37
package/dist/rules.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { WARDLEY_BACKGROUND } from './background.js';
|
|
2
|
+
import { WARDLEY_ROLE, WARDLEY_ROLES } from './roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wardley validation rules (PF13.4 / PF13.5 / PF13.6).
|
|
5
|
+
*
|
|
6
|
+
* DATA owned by the framework, versioned per rule: the engine
|
|
7
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
8
|
+
* concrete rule. Adding a Wardley rule is adding an entry to this array.
|
|
9
|
+
*
|
|
10
|
+
* Registered from the flag-gated `WardleyViewExtension`, so switching the
|
|
11
|
+
* Wardley flag off removes the rules with the rest of the tooling — already
|
|
12
|
+
* drawn maps keep rendering, they simply stop being checked (see
|
|
13
|
+
* `docs/adr/0009`).
|
|
14
|
+
*
|
|
15
|
+
* ## The pilot rule is gone
|
|
16
|
+
*
|
|
17
|
+
* `wardley.component-outside-map` was the tracer bullet's rule: it existed to
|
|
18
|
+
* prove the engine, the affordance, the exceptions and the profiles could work
|
|
19
|
+
* end to end, never because a Wardley practitioner asked for it. Parking a node
|
|
20
|
+
* in the margin while you think is normal work, and PO decision of 01/08/2026
|
|
21
|
+
* removed it the moment there were real rules for the machinery to carry.
|
|
22
|
+
* Nothing was left behind: every test that exercised the pipework through it
|
|
23
|
+
* now exercises it through W1, W2 or W3.
|
|
24
|
+
*
|
|
25
|
+
* ## Severity, and a hole that is named rather than papered over
|
|
26
|
+
*
|
|
27
|
+
* W1 and W2 are the two the architect would put at `blocking-overridable`. They
|
|
28
|
+
* are declared `warning`, because NOTHING downstream implements a blocking
|
|
29
|
+
* level: no gesture is refused anywhere in this library, so shipping the value
|
|
30
|
+
* would be data claiming an effect that does not exist. The severity moves to
|
|
31
|
+
* `blocking-overridable` in the profiles, in one line each, the day the gesture
|
|
32
|
+
* refusal lands. Building it was explicitly out of this slice.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* **W1** — a change arrow may not point against the sense of evolution.
|
|
36
|
+
*
|
|
37
|
+
* The whole grammar of a Wardley map is that things move left to right: a red
|
|
38
|
+
* arrow pointing back towards Genesis says the opposite of what the map means.
|
|
39
|
+
* Written on `wardley:change-arrow`, the role PF13.4 puts on the arrow at
|
|
40
|
+
* creation — value-chain links are a different role and are never touched.
|
|
41
|
+
*
|
|
42
|
+
* The 30° dead zone is deliberate. An arrow drawn straight up a value chain is
|
|
43
|
+
* neither with evolution nor against it, and one drawn slightly up-and-back is
|
|
44
|
+
* a hand that slipped, not a claim about the world. Only an arrow genuinely
|
|
45
|
+
* heading back — more than 120° off the axis' forward sense — is one.
|
|
46
|
+
*/
|
|
47
|
+
const changeArrowAgainstEvolution = {
|
|
48
|
+
id: 'wardley.change-arrow-against-evolution',
|
|
49
|
+
framework: 'wardley',
|
|
50
|
+
family: 'orientation-against-axis',
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
appliesTo: WARDLEY_ROLE.changeArrow,
|
|
53
|
+
roles: WARDLEY_ROLES,
|
|
54
|
+
messageKey: 'com.labre.wardley.validation.change-arrow-against-evolution',
|
|
55
|
+
messageFallback: 'This change arrow points against evolution.',
|
|
56
|
+
suggestionKey: 'com.labre.wardley.validation.change-arrow-against-evolution.suggestion',
|
|
57
|
+
suggestionFallback: 'Evolution runs left to right — turn the arrow towards the commodity end, or draw a dependency instead.',
|
|
58
|
+
version: 1,
|
|
59
|
+
backgroundRole: WARDLEY_ROLE.map,
|
|
60
|
+
// The frame's own declaration, carried as data exactly like `roles` is: it is
|
|
61
|
+
// where "which way does evolution run" is written, and the engine reads it
|
|
62
|
+
// rather than knowing anything about Wardley.
|
|
63
|
+
background: WARDLEY_BACKGROUND,
|
|
64
|
+
against: { axis: 'evolution', toleranceDeg: 30 },
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* **W2** — the inertia bar straddles a phase transition.
|
|
68
|
+
*
|
|
69
|
+
* > "The horizontal position of an inertia bar is only valid if it is ASTRIDE
|
|
70
|
+
* > two evolution phases, that is, superimposed on a dashed vertical axis."
|
|
71
|
+
* > — the PO, spelling the rule out on the recette of 02/08/2026
|
|
72
|
+
*
|
|
73
|
+
* Inertia is resistance to crossing a frontier. The bar is the frontier being
|
|
74
|
+
* refused, so it is drawn ON the divider; a bar parked in the middle of a phase
|
|
75
|
+
* marks nothing at all and becomes a black rectangle.
|
|
76
|
+
*
|
|
77
|
+
* ## What this rule used to ask, and no longer does
|
|
78
|
+
*
|
|
79
|
+
* Until this version it ALSO demanded a dependency under the bar, and reported
|
|
80
|
+
* that half first ("This inertia bar is not drawn on a dependency."). That was
|
|
81
|
+
* our reading of inertia, not the PO's rule, and it was wrong in both
|
|
82
|
+
* directions: a bar alone on a divider — a perfectly ordinary way to say "this
|
|
83
|
+
* whole column is stuck" — was flagged, while nothing in the sentence pointed at
|
|
84
|
+
* the position that actually decides the verdict. The carrier condition is gone
|
|
85
|
+
* entirely, with the second message that existed only to tell the two halves
|
|
86
|
+
* apart. One condition, one sentence.
|
|
87
|
+
*
|
|
88
|
+
* ## "Astride", as geometry
|
|
89
|
+
*
|
|
90
|
+
* The engine takes the bar's own horizontal EXTENT and asks whether it
|
|
91
|
+
* intersects the transition band — the divider widened by the map's declared
|
|
92
|
+
* `transitionBandWidth` (see {@link AttachmentDef.boundaryAxis}). That single
|
|
93
|
+
* overlap test says both halves of "superimposed on the axis": a bar wide enough
|
|
94
|
+
* to cover the divider genuinely has the line running through it, and a bar too
|
|
95
|
+
* thin to cover anything (the toolbox draws it eight units wide) is accepted
|
|
96
|
+
* inside the band the map itself declares around the frontier — Wardley's zone
|
|
97
|
+
* of punctuated equilibrium, where inertia lives.
|
|
98
|
+
*
|
|
99
|
+
* Measured on the extent rather than on the centre because "superimposed on the
|
|
100
|
+
* line" is a statement about ink; measured against a band declared as a RATIO of
|
|
101
|
+
* the plot because the same gesture must get the same verdict on a map somebody
|
|
102
|
+
* resized (the lesson of the 01/08/2026 recette).
|
|
103
|
+
*/
|
|
104
|
+
const inertiaOffTransition = {
|
|
105
|
+
id: 'wardley.inertia-off-transition',
|
|
106
|
+
framework: 'wardley',
|
|
107
|
+
family: 'attachment',
|
|
108
|
+
severity: 'warning',
|
|
109
|
+
appliesTo: WARDLEY_ROLE.inertia,
|
|
110
|
+
roles: WARDLEY_ROLES,
|
|
111
|
+
// ONE sentence, because there is now one condition. The key is the rule's own
|
|
112
|
+
// id: the two keys it replaces named halves of a rule that no longer has any.
|
|
113
|
+
messageKey: 'com.labre.wardley.validation.inertia-off-transition',
|
|
114
|
+
messageFallback: 'This inertia bar sits inside a phase, not astride a phase transition.',
|
|
115
|
+
suggestionKey: 'com.labre.wardley.validation.inertia-off-transition.suggestion',
|
|
116
|
+
suggestionFallback: 'Inertia bites at a frontier — slide the bar sideways until it sits astride the dashed line between two evolution phases.',
|
|
117
|
+
// 3: the carrier condition is gone and the position is judged on the bar's
|
|
118
|
+
// extent — a different verdict on the same map, so a new version.
|
|
119
|
+
version: 3,
|
|
120
|
+
backgroundRole: WARDLEY_ROLE.map,
|
|
121
|
+
background: WARDLEY_BACKGROUND,
|
|
122
|
+
attachment: {
|
|
123
|
+
boundaryAxis: 'evolution',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* **W3** — nodes and labels must not sit on top of each other.
|
|
128
|
+
*
|
|
129
|
+
* A readability rule, not a semantic one, which is why it is the softest of the
|
|
130
|
+
* three: a map you cannot read is useless, but a momentary overlap while you
|
|
131
|
+
* drag things into place is not a mistake.
|
|
132
|
+
*
|
|
133
|
+
* The four declared combinations are the ones that actually make a map
|
|
134
|
+
* illegible. Node/node hides an artefact outright; label/label and label/node
|
|
135
|
+
* make a name unreadable or attach it to the wrong thing; label/link is the one
|
|
136
|
+
* everybody hits, a name crossed out by the dependency running under it.
|
|
137
|
+
*
|
|
138
|
+
* Link/link is deliberately ABSENT: dependencies cross all the time on a real
|
|
139
|
+
* value chain, and that is the map working, not the map broken.
|
|
140
|
+
*
|
|
141
|
+
* ## Calibration (PO acceptance, 01/08/2026)
|
|
142
|
+
*
|
|
143
|
+
* Shipped, the rule was noisy in two ways the corpus could not see, because its
|
|
144
|
+
* fixtures were boxes rather than words:
|
|
145
|
+
*
|
|
146
|
+
* 1. a link crossing the EMPTY MARGIN of a label raised label/link. A label is
|
|
147
|
+
* created 120–200 units wide whatever it says, so a short name left-aligned
|
|
148
|
+
* in one leaves most of its box blank. Fixed where the mistake was: the
|
|
149
|
+
* label role is `kind: 'text'`, so the engine measures the ink and not the
|
|
150
|
+
* box (`textInkBound`).
|
|
151
|
+
* 2. two labels whose WORDS are nowhere near each other raised label/label,
|
|
152
|
+
* for the same reason and with the same fix.
|
|
153
|
+
*
|
|
154
|
+
* `minPenetration` is what remains: not every touch is a collision. **4 model
|
|
155
|
+
* units** — under a quarter of the 18-unit node, a sixth of the 26-unit label
|
|
156
|
+
* line, and about the width of a dependency's own stroke. A link grazing the
|
|
157
|
+
* top of a name and two names whose last and first letter share a hair are
|
|
158
|
+
* silent; a link through the middle of a name scores half its line height (13)
|
|
159
|
+
* and a name written across a node scores the height of the letters, so both
|
|
160
|
+
* are still reported with room to spare.
|
|
161
|
+
*
|
|
162
|
+
* It also absorbs the declared imprecision of the width approximation, which is
|
|
163
|
+
* on the same scale — measured against the real renderer over 28 names, the
|
|
164
|
+
* engine's per-character table reads between 11 % narrow and dead on, never
|
|
165
|
+
* wide — and points the same way, towards silence.
|
|
166
|
+
*
|
|
167
|
+
* IN RESERVE, if label/link is still noisy after this (PO, 01/08/2026): an
|
|
168
|
+
* ANGLE criterion — only a link crossing a name roughly PERPENDICULARLY strikes
|
|
169
|
+
* it out, one running along it at a shallow angle mostly runs beside it. It
|
|
170
|
+
* would be declared here as data, exactly like the threshold, and evaluated by
|
|
171
|
+
* the family. Not built: nobody has yet seen the noise it would remove.
|
|
172
|
+
*/
|
|
173
|
+
const overlappingArtefacts = {
|
|
174
|
+
id: 'wardley.overlapping-artefacts',
|
|
175
|
+
framework: 'wardley',
|
|
176
|
+
family: 'no-overlap',
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
// No `appliesTo`: this rule has no single subject role — the subjects are the
|
|
179
|
+
// pairs below, and naming one of them here would be data that lies.
|
|
180
|
+
roles: WARDLEY_ROLES,
|
|
181
|
+
messageKey: 'com.labre.wardley.validation.overlapping-artefacts',
|
|
182
|
+
messageFallback: 'These two overlap and make the map harder to read.',
|
|
183
|
+
suggestionKey: 'com.labre.wardley.validation.overlapping-artefacts.suggestion',
|
|
184
|
+
suggestionFallback: 'Move one of them aside.',
|
|
185
|
+
// 2: measured on the ink of a label rather than on its creation box, and
|
|
186
|
+
// calibrated with a penetration threshold — the same mistakes, fewer of the
|
|
187
|
+
// things that were never mistakes.
|
|
188
|
+
version: 2,
|
|
189
|
+
// Not a frame the rule measures against — an overlap is an overlap wherever
|
|
190
|
+
// it happens — but the map a finding is ATTRIBUTED to, so the arbitration
|
|
191
|
+
// "ignore this rule on the whole map" has one map to be written on.
|
|
192
|
+
backgroundRole: WARDLEY_ROLE.map,
|
|
193
|
+
overlap: [
|
|
194
|
+
[WARDLEY_ROLE.component, WARDLEY_ROLE.component],
|
|
195
|
+
[WARDLEY_ROLE.label, WARDLEY_ROLE.label],
|
|
196
|
+
[WARDLEY_ROLE.label, WARDLEY_ROLE.component],
|
|
197
|
+
[WARDLEY_ROLE.label, WARDLEY_ROLE.dependency],
|
|
198
|
+
],
|
|
199
|
+
// How deep a collision has to be before it is one. See the header.
|
|
200
|
+
minPenetration: 4,
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* **W4** — a provider may not be positioned higher than its consumer.
|
|
204
|
+
*
|
|
205
|
+
* The value chain is the whole grammar of the map: the user sits at the top,
|
|
206
|
+
* each component rests on the components below it, and value flows back up. A
|
|
207
|
+
* dependency drawn from a component to something ABOVE it says the opposite of
|
|
208
|
+
* what the map means — either the link was drawn the wrong way round, or one of
|
|
209
|
+
* the two nodes is in the wrong place.
|
|
210
|
+
*
|
|
211
|
+
* ## The rule this one could not have been before `docs/adr/0010`
|
|
212
|
+
*
|
|
213
|
+
* W4 reads the persisted `source → target` pair of the edge, and that pair only
|
|
214
|
+
* became a STATEMENT the day the three mechanisms of that ADR landed: the link
|
|
215
|
+
* tool announces which way to drag (M1), a typed edge shows its orientation on
|
|
216
|
+
* hover and selection (M2), and the user can reverse it in one gesture (M3).
|
|
217
|
+
* Before them the direction was a by-product of which end the finger landed on
|
|
218
|
+
* first, and a rule on top of it would have spent the validation platform's
|
|
219
|
+
* credibility on its first false positive.
|
|
220
|
+
*
|
|
221
|
+
* The alternative — deriving the direction from the y coordinates — is not a
|
|
222
|
+
* cheaper W4, it is no W4 at all: the rule would compare the layout against
|
|
223
|
+
* itself and could never fire.
|
|
224
|
+
*
|
|
225
|
+
* ## The violation IS the affordance
|
|
226
|
+
*
|
|
227
|
+
* Nothing normalises the direction at creation, deliberately. An edge drawn
|
|
228
|
+
* upside-down raises this finding on the spot, and the user resolves it their
|
|
229
|
+
* way: drag the node, or reverse the relation. Either resolution is theirs.
|
|
230
|
+
*
|
|
231
|
+
* ## Tolerance
|
|
232
|
+
*
|
|
233
|
+
* 2% of the map's height, as a ratio and never as a number of units (the lesson
|
|
234
|
+
* of the 01/08/2026 recette, already learned by the equilibrium zone). Two
|
|
235
|
+
* components drawn level are not a mistake — a chain gets lined up before it
|
|
236
|
+
* gets spread out — so the rule only speaks when one is genuinely under the
|
|
237
|
+
* other. On the 900-high reference map that is 18 units, about the diameter of
|
|
238
|
+
* a component node.
|
|
239
|
+
*/
|
|
240
|
+
const providerAboveConsumer = {
|
|
241
|
+
id: 'wardley.provider-above-consumer',
|
|
242
|
+
framework: 'wardley',
|
|
243
|
+
family: 'relative-order-along-axis',
|
|
244
|
+
severity: 'warning',
|
|
245
|
+
// No `appliesTo`: the subject of this rule is a RELATION, and the role that
|
|
246
|
+
// names it is declared where the family reads it — naming one of the three
|
|
247
|
+
// indicted elements here would be data that lies.
|
|
248
|
+
roles: WARDLEY_ROLES,
|
|
249
|
+
messageKey: 'com.labre.wardley.validation.provider-above-consumer',
|
|
250
|
+
messageFallback: 'This component sits above the one that depends on it.',
|
|
251
|
+
suggestionKey: 'com.labre.wardley.validation.provider-above-consumer.suggestion',
|
|
252
|
+
suggestionFallback: 'Needs run downwards on a Wardley map: move the provider below its consumer — or, if the link was drawn the wrong way round, reverse it.',
|
|
253
|
+
version: 1,
|
|
254
|
+
backgroundRole: WARDLEY_ROLE.map,
|
|
255
|
+
background: WARDLEY_BACKGROUND,
|
|
256
|
+
relativeOrder: {
|
|
257
|
+
edgeRole: WARDLEY_ROLE.dependency,
|
|
258
|
+
axis: 'value-chain',
|
|
259
|
+
// Tier 2 of ADR 0010: the verb of `wardley:dependency` is "depends on", so
|
|
260
|
+
// its source is the CONSUMER and sits higher on the visibility axis.
|
|
261
|
+
expect: 'source-ahead',
|
|
262
|
+
toleranceRatio: 0.02,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
export const WARDLEY_RULES = [
|
|
266
|
+
changeArrowAgainstEvolution,
|
|
267
|
+
inertiaOffTransition,
|
|
268
|
+
overlappingArtefacts,
|
|
269
|
+
providerAboveConsumer,
|
|
270
|
+
];
|
package/dist/templates/index.js
CHANGED
|
@@ -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
|
-
import { REF_WIDTH } from '../consts';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { REF_WIDTH } from '../consts.js';
|
|
4
|
+
import { WARDLEY_ROLE } from '../roles.js';
|
|
5
|
+
import { wardleyMaps } from './maps.js';
|
|
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: {
|
|
@@ -23,10 +30,16 @@ const bg = (variant, w = REF_WIDTH) => {
|
|
|
23
30
|
return { type: 'wardley', variant, ...VARIANT_DEFAULTS[variant], xywh: `[0,0,${w},${h}]` };
|
|
24
31
|
};
|
|
25
32
|
/** A wardley node ellipse positioned by top-left. */
|
|
26
|
-
function node(kind, x, y, d = NODE_SIZE, fill = NODE_FILL, strokeWidth = NODE_STROKE_WIDTH
|
|
33
|
+
function node(kind, x, y, d = NODE_SIZE, fill = NODE_FILL, strokeWidth = NODE_STROKE_WIDTH,
|
|
34
|
+
// Neutral for the market's inner dots, exactly as `createWardleyMarket` does:
|
|
35
|
+
// glyph wiring, not artefacts.
|
|
36
|
+
neutral = false) {
|
|
27
37
|
return {
|
|
28
38
|
type: 'wardleyNode',
|
|
29
39
|
kind,
|
|
40
|
+
// A template must produce the same typed artefacts as the toolbox, so a
|
|
41
|
+
// map started from a preset validates like a hand-drawn one.
|
|
42
|
+
role: neutral ? undefined : WARDLEY_ROLE[kind],
|
|
30
43
|
shapeType: 'ellipse',
|
|
31
44
|
filled: true,
|
|
32
45
|
fillColor: fill,
|
|
@@ -41,6 +54,7 @@ function label(x, y, str, align = 'left') {
|
|
|
41
54
|
return {
|
|
42
55
|
type: 'text',
|
|
43
56
|
text: surfaceText(str),
|
|
57
|
+
role: WARDLEY_ROLE.label,
|
|
44
58
|
color: NODE_STROKE,
|
|
45
59
|
fontFamily: FontFamily.Inter,
|
|
46
60
|
fontSize: LABEL_FONT_SIZE,
|
|
@@ -48,15 +62,35 @@ function label(x, y, str, align = 'left') {
|
|
|
48
62
|
xywh: `[${x},${y},140,26]`,
|
|
49
63
|
};
|
|
50
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* A Wardley connector for the palette samples.
|
|
67
|
+
*
|
|
68
|
+
* `evolution` is the ONE predicate that decides what the stroke means, here and
|
|
69
|
+
* in `maps.ts` alike — `docs/adr/0010` § Compatibility. The two kits used to
|
|
70
|
+
* test different things (`opts.red` here, `o.arrow` there), which was a style
|
|
71
|
+
* inconsistency until W4 started reading these edges and became a SEMANTIC one:
|
|
72
|
+
* what a rule governs must not depend on which authoring helper a template
|
|
73
|
+
* borrowed. Colour is a consequence of the meaning, never its source.
|
|
74
|
+
*
|
|
75
|
+
* `typed: false` drops the role altogether — for a sample that makes no claim
|
|
76
|
+
* about anything (see the "Link" swatch below).
|
|
77
|
+
*/
|
|
51
78
|
function connect(source, target, opts = {}) {
|
|
79
|
+
const role = opts.evolution
|
|
80
|
+
? WARDLEY_ROLE.changeArrow
|
|
81
|
+
: WARDLEY_ROLE.dependency;
|
|
52
82
|
return {
|
|
53
83
|
type: 'connector',
|
|
54
84
|
mode: ConnectorMode.Straight,
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
// A template must produce the same typed artefacts as the toolbox, or a map
|
|
86
|
+
// started from a preset would validate differently from a hand-drawn one.
|
|
87
|
+
// `undefined` writes nothing: a neutral stroke keeps no `role` key.
|
|
88
|
+
role: opts.typed === false ? undefined : role,
|
|
89
|
+
stroke: opts.evolution ? WARDLEY_RED : LINK_GREY,
|
|
90
|
+
strokeStyle: opts.evolution ? StrokeStyle.Dash : StrokeStyle.Solid,
|
|
57
91
|
strokeWidth: LINK_STROKE_WIDTH,
|
|
58
92
|
frontEndpointStyle: PointStyle.None,
|
|
59
|
-
rearEndpointStyle: opts.
|
|
93
|
+
rearEndpointStyle: opts.evolution ? PointStyle.Triangle : PointStyle.None,
|
|
60
94
|
source,
|
|
61
95
|
target,
|
|
62
96
|
};
|
|
@@ -64,6 +98,7 @@ function connect(source, target, opts = {}) {
|
|
|
64
98
|
const inertia = (x = 0, y = 0) => ({
|
|
65
99
|
type: 'shape',
|
|
66
100
|
shapeType: 'rect',
|
|
101
|
+
role: WARDLEY_ROLE.inertia,
|
|
67
102
|
filled: true,
|
|
68
103
|
fillColor: INERTIA_COLOR,
|
|
69
104
|
strokeColor: INERTIA_COLOR,
|
|
@@ -80,6 +115,7 @@ function pipeline() {
|
|
|
80
115
|
body: {
|
|
81
116
|
type: 'wardleyNode',
|
|
82
117
|
kind: 'pipeline',
|
|
118
|
+
role: WARDLEY_ROLE.pipeline,
|
|
83
119
|
shapeType: 'rect',
|
|
84
120
|
filled: true,
|
|
85
121
|
fillColor: PIPELINE_FILL,
|
|
@@ -93,6 +129,7 @@ function pipeline() {
|
|
|
93
129
|
handle: {
|
|
94
130
|
type: 'wardleyNode',
|
|
95
131
|
kind: 'handle',
|
|
132
|
+
role: WARDLEY_ROLE.handle,
|
|
96
133
|
shapeType: 'rect',
|
|
97
134
|
filled: true,
|
|
98
135
|
fillColor: NODE_FILL,
|
|
@@ -116,7 +153,9 @@ function market() {
|
|
|
116
153
|
[rho * sin60, rho / 2],
|
|
117
154
|
[-rho * sin60, rho / 2],
|
|
118
155
|
];
|
|
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);
|
|
156
|
+
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);
|
|
157
|
+
// Neutral on purpose: the triangle is the market glyph's own wiring, not a
|
|
158
|
+
// dependency the user drew (same rule as `createWardleyMarket`).
|
|
120
159
|
const tri = (a, b) => ({
|
|
121
160
|
type: 'connector',
|
|
122
161
|
mode: ConnectorMode.Straight,
|
|
@@ -165,7 +204,12 @@ export const wardleyTemplateCategory = {
|
|
|
165
204
|
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
205
|
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
206
|
tpl('Inertia', `<svg ${ATTRS} fill="none"><rect x="63" y="22" width="8" height="36" fill="#1f2328"/></svg>`, single(inertia())),
|
|
168
|
-
|
|
169
|
-
|
|
207
|
+
// NEUTRAL on purpose (`docs/adr/0010` § Compatibility). This is a horizontal
|
|
208
|
+
// stroke bound to nothing — a sample of a STYLE, in a palette. It was typed
|
|
209
|
+
// `wardley:dependency` only because the helper defaults to that role, and a
|
|
210
|
+
// sample of a stroke makes no claim about who depends on whom. Same call the
|
|
211
|
+
// market glyph's own wiring already makes.
|
|
212
|
+
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 }))),
|
|
213
|
+
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
214
|
],
|
|
171
215
|
};
|
package/dist/templates/maps.js
CHANGED
|
@@ -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
|
-
import { INERTIA_COLOR, LABEL_FONT_SIZE, LINK_GREY, LINK_STROKE_WIDTH, NODE_FILL, NODE_SIZE, NODE_STROKE, WARDLEY_RED, } from '../node/consts';
|
|
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 =
|
|
15
|
-
const H =
|
|
16
|
-
|
|
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
|
-
|
|
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,6 +118,10 @@ 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,
|
|
@@ -71,15 +129,38 @@ function lbl(e, v, text, o = {}) {
|
|
|
71
129
|
xywh: `[${x},${cy + dy},${w},26]`,
|
|
72
130
|
};
|
|
73
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* A link between two nodes of a shipped map.
|
|
134
|
+
*
|
|
135
|
+
* `evolution` is the ONE predicate that decides what the stroke MEANS — the
|
|
136
|
+
* same flag `templates/index.ts` reads, aligned by `docs/adr/0010`
|
|
137
|
+
* § Compatibility. It used to be `arrow` here and `red` there, so a red SOLID
|
|
138
|
+
* Kodak link was a typed dependency while a red sample in the palette was not:
|
|
139
|
+
* two answers to "is this a dependency?" in one framework, in neighbouring
|
|
140
|
+
* files. Harmless as a style inconsistency, semantic the moment W4 reads these
|
|
141
|
+
* edges.
|
|
142
|
+
*
|
|
143
|
+
* `red` stays, and stays orthogonal: it colours a DEPENDENCY red (Kodak's
|
|
144
|
+
* future chain) without changing what it is. Colour is never what decides a
|
|
145
|
+
* relation's type.
|
|
146
|
+
*
|
|
147
|
+
* The direction is meaning, not decoration: `a` is the consumer, `b` is what it
|
|
148
|
+
* needs, and every one of the twelve links these presets ship respects it
|
|
149
|
+
* (a corpus test walks them and fails loudly the day one does not).
|
|
150
|
+
*/
|
|
74
151
|
function link(a, b, o = {}) {
|
|
75
152
|
return {
|
|
76
153
|
type: 'connector',
|
|
77
154
|
mode: ConnectorMode.Straight,
|
|
78
|
-
|
|
79
|
-
|
|
155
|
+
// An evolution arrow is a movement annotation, not a dependency — same
|
|
156
|
+
// split as the two Wardley connector tools, and since PF13.4 both sides of
|
|
157
|
+
// that split carry a role.
|
|
158
|
+
role: o.evolution ? WARDLEY_ROLE.changeArrow : WARDLEY_ROLE.dependency,
|
|
159
|
+
stroke: o.red || o.evolution ? WARDLEY_RED : LINK_GREY,
|
|
160
|
+
strokeStyle: o.evolution ? StrokeStyle.Dash : StrokeStyle.Solid,
|
|
80
161
|
strokeWidth: LINK_STROKE_WIDTH,
|
|
81
162
|
frontEndpointStyle: PointStyle.None,
|
|
82
|
-
rearEndpointStyle: o.
|
|
163
|
+
rearEndpointStyle: o.evolution ? PointStyle.Triangle : PointStyle.None,
|
|
83
164
|
source: { id: a },
|
|
84
165
|
target: { id: b },
|
|
85
166
|
};
|
|
@@ -90,6 +171,7 @@ function inertia(e, v) {
|
|
|
90
171
|
return {
|
|
91
172
|
type: 'shape',
|
|
92
173
|
shapeType: 'rect',
|
|
174
|
+
role: WARDLEY_ROLE.inertia,
|
|
93
175
|
filled: true,
|
|
94
176
|
fillColor: INERTIA_COLOR,
|
|
95
177
|
strokeColor: INERTIA_COLOR,
|
|
@@ -199,7 +281,10 @@ function teaShop() {
|
|
|
199
281
|
powerL: lbl(0.7, 0.1, 'Power', { align: 'right', dy: 6 }),
|
|
200
282
|
powerFut: future(0.88, 0.1),
|
|
201
283
|
powerFutL: lbl(0.88, 0.1, 'Power'),
|
|
202
|
-
|
|
284
|
+
// ABOVE the link it annotates, not across it. Written on the line it reads
|
|
285
|
+
// as a label nobody can read — which is the finding W3 raises, and it was
|
|
286
|
+
// raising it on the map that ships as the canonical example.
|
|
287
|
+
limitedBy: lbl(0.56, 0.43, 'limited by', { align: 'center', w: 120, size: 13, dy: -34 }),
|
|
203
288
|
ann1a: ann(0.5, 0.385), ann1t: annTxt(0.5, 0.385, '1'),
|
|
204
289
|
ann2a: ann(0.84, 0.45), ann2t: annTxt(0.84, 0.45, '2'),
|
|
205
290
|
l1: link('business', 'cupOfTea'),
|
|
@@ -210,34 +295,53 @@ function teaShop() {
|
|
|
210
295
|
l6: link('hotWater', 'water'),
|
|
211
296
|
l7: link('hotWater', 'kettle'),
|
|
212
297
|
l8: link('kettle', 'power'),
|
|
213
|
-
a1: link('kettle', 'electric', {
|
|
214
|
-
a2: link('power', 'powerFut', {
|
|
298
|
+
a1: link('kettle', 'electric', { evolution: true }),
|
|
299
|
+
a2: link('power', 'powerFut', { evolution: true }),
|
|
215
300
|
};
|
|
216
301
|
}
|
|
217
302
|
// ── Kodak inertia (2005) ──────────────────────────────────────────────
|
|
218
303
|
function kodak() {
|
|
304
|
+
// The future dependency `capture → storage` is the movement Kodak resisted,
|
|
305
|
+
// and the inertia bar belongs where that dependency crosses into commodity —
|
|
306
|
+
// the boundary the capability refused to cross. It used to sit 105 units away
|
|
307
|
+
// from any transition: the template named after inertia was the
|
|
308
|
+
// counter-example to the inertia rule. Computed from the two node positions
|
|
309
|
+
// and the declared transitions, so it cannot drift again — and the bar comes
|
|
310
|
+
// out centred on the divider, which is exactly what W2 asks of it now that
|
|
311
|
+
// "astride the transition" is the whole of the rule.
|
|
312
|
+
const CAPTURE = [0.53, 0.8];
|
|
313
|
+
const STORAGE = [0.84, 0.4];
|
|
314
|
+
const [barE, barV] = crossing(CAPTURE, STORAGE, 2);
|
|
219
315
|
return {
|
|
220
316
|
bg: bg(),
|
|
221
317
|
title: title("Wardley map of Kodak's 2005 inertia to digital"),
|
|
222
318
|
user: stake(0.54, 0.92),
|
|
223
319
|
userL: lbl(0.54, 0.92, 'User'),
|
|
224
|
-
capture: dot(0
|
|
225
|
-
|
|
320
|
+
capture: dot(CAPTURE[0], CAPTURE[1], 3),
|
|
321
|
+
// To the LEFT, like the other two capability names: to the right of this
|
|
322
|
+
// node runs the future dependency towards digital storage, and a name
|
|
323
|
+
// written across the line that carries the whole argument is exactly the
|
|
324
|
+
// case W3 exists for.
|
|
325
|
+
captureL: lbl(CAPTURE[0], CAPTURE[1], 'Capture a moment', {
|
|
326
|
+
align: 'right',
|
|
327
|
+
}),
|
|
226
328
|
film: comp(0.52, 0.62),
|
|
227
329
|
filmL: lbl(0.52, 0.62, 'Film camera', { align: 'right' }),
|
|
228
330
|
digital: future(0.74, 0.62),
|
|
229
331
|
digitalL: lbl(0.74, 0.62, 'Digital camera', { color: WARDLEY_RED }),
|
|
230
332
|
roll: comp(0.52, 0.4),
|
|
231
333
|
rollL: lbl(0.52, 0.4, 'Photographic film', { align: 'right' }),
|
|
232
|
-
storage: future(0
|
|
233
|
-
storageL: lbl(0
|
|
234
|
-
|
|
334
|
+
storage: future(STORAGE[0], STORAGE[1]),
|
|
335
|
+
storageL: lbl(STORAGE[0], STORAGE[1], 'Digital storage', {
|
|
336
|
+
color: WARDLEY_RED,
|
|
337
|
+
}),
|
|
338
|
+
inertiaBar: inertia(barE, barV),
|
|
235
339
|
l1: link('user', 'capture'),
|
|
236
340
|
l2: link('capture', 'film'),
|
|
237
341
|
l3: link('film', 'roll'),
|
|
238
342
|
r1: link('capture', 'storage', { red: true }),
|
|
239
|
-
a1: link('film', 'digital', {
|
|
240
|
-
a2: link('roll', 'storage', {
|
|
343
|
+
a1: link('film', 'digital', { evolution: true }),
|
|
344
|
+
a2: link('roll', 'storage', { evolution: true }),
|
|
241
345
|
};
|
|
242
346
|
}
|
|
243
347
|
export const wardleyMaps = [
|
package/dist/toolbar/config.js
CHANGED
|
@@ -3,8 +3,8 @@ import { WardleyBackgroundElementModel } from '@formicoidea/labre-core/model';
|
|
|
3
3
|
import { TelemetryProvider, ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
|
|
4
4
|
import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
|
|
5
5
|
import { html } from 'lit';
|
|
6
|
-
import { createWardleyLegend } from '../legend';
|
|
7
|
-
import { wardleyLegendIcon } from './icons';
|
|
6
|
+
import { createWardleyLegend } from '../legend.js';
|
|
7
|
+
import { wardleyLegendIcon } from './icons.js';
|
|
8
8
|
const ResizeIcon = html `<svg
|
|
9
9
|
width="24"
|
|
10
10
|
height="24"
|