@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/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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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'
|
|
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
|
|
5
|
+
import { EditPropsStore } from '@formicoidea/labre-core/shared/services';
|
|
6
6
|
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
7
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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'
|
|
130
|
+
export function createWardleyBackground(gfx, variant = 'classic') {
|
|
109
131
|
if (!gfx.surface)
|
|
110
132
|
return;
|
|
111
|
-
|
|
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
|
-
|
|
140
|
+
atLeastWidth = Math.max(atLeastWidth, ew);
|
|
141
|
+
atLeastHeight = Math.max(atLeastHeight, eh);
|
|
115
142
|
}
|
|
116
|
-
const height = (
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
286
|
-
|
|
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;
|