@formicoidea/labre-framework-wardley 0.31.0 → 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 +79 -38
- 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/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 +8 -0
- package/dist/natures.d.ts +50 -0
- package/dist/natures.js +93 -0
- 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 +51 -7
- package/dist/templates/maps.js +121 -17
- package/dist/toolbar/wardley-menu.d.ts +8 -15
- package/dist/toolbar/wardley-menu.js +8 -136
- package/dist/translations.d.ts +16 -0
- package/dist/translations.js +24 -0
- package/dist/view.d.ts +17 -0
- package/dist/view.js +116 -13
- 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/element-renderer.js
CHANGED
|
@@ -1,150 +1,15 @@
|
|
|
1
|
-
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import {
|
|
3
|
-
import { paintGradientBackground } from './gradient.js';
|
|
4
|
-
function roundRectPath(ctx, x, y, w, h, r) {
|
|
5
|
-
const rr = Math.min(r, w / 2, h / 2);
|
|
6
|
-
ctx.beginPath();
|
|
7
|
-
ctx.moveTo(x + rr, y);
|
|
8
|
-
ctx.arcTo(x + w, y, x + w, y + h, rr);
|
|
9
|
-
ctx.arcTo(x + w, y + h, x, y + h, rr);
|
|
10
|
-
ctx.arcTo(x, y + h, x, y, rr);
|
|
11
|
-
ctx.arcTo(x, y, x + w, y, rr);
|
|
12
|
-
ctx.closePath();
|
|
13
|
-
}
|
|
1
|
+
import { createFrameworkBackgroundRenderer, ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { WARDLEY_BACKGROUND } from './background.js';
|
|
14
3
|
/**
|
|
15
|
-
* Canvas renderer for the Wardley map background
|
|
16
|
-
* an L-shaped axes frame (no top/right border), dashed evolution dividers and
|
|
17
|
-
* the symmetric axis labels.
|
|
4
|
+
* Canvas renderer for the Wardley map background.
|
|
18
5
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
6
|
+
* There is no Wardley drawing code any more (PF2.12): the map is an
|
|
7
|
+
* INSTANTIATION of the framework-background primitive, configured by the
|
|
8
|
+
* `WARDLEY_BACKGROUND` declaration. What used to be two hundred lines of
|
|
9
|
+
* `ctx.fillText` is now a declaration any other framework can write for itself.
|
|
10
|
+
*
|
|
11
|
+
* Exported as a function as well as an extension because the non-regression
|
|
12
|
+
* suite drives it directly with a canvas stub.
|
|
21
13
|
*/
|
|
22
|
-
export const wardley = (
|
|
23
|
-
|
|
24
|
-
const cx = w / 2;
|
|
25
|
-
const cy = h / 2;
|
|
26
|
-
ctx.setTransform(matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy));
|
|
27
|
-
const px0 = MARGIN.left;
|
|
28
|
-
const px1 = w - MARGIN.right;
|
|
29
|
-
const py0 = MARGIN.top;
|
|
30
|
-
const py1 = h - MARGIN.bottom;
|
|
31
|
-
const pw = px1 - px0;
|
|
32
|
-
const ph = py1 - py0;
|
|
33
|
-
const ex = (r) => px0 + r * pw;
|
|
34
|
-
const line = (x1, y1, x2, y2) => {
|
|
35
|
-
ctx.beginPath();
|
|
36
|
-
ctx.moveTo(x1, y1);
|
|
37
|
-
ctx.lineTo(x2, y2);
|
|
38
|
-
ctx.stroke();
|
|
39
|
-
};
|
|
40
|
-
const vtext = (text, x, y, fontSize, color) => {
|
|
41
|
-
ctx.save();
|
|
42
|
-
ctx.translate(x, y);
|
|
43
|
-
ctx.rotate(-Math.PI / 2);
|
|
44
|
-
ctx.font = `${fontSize}px ${FONT_FAMILY}`;
|
|
45
|
-
ctx.fillStyle = color;
|
|
46
|
-
ctx.textAlign = 'center';
|
|
47
|
-
ctx.textBaseline = 'alphabetic';
|
|
48
|
-
ctx.fillText(text, 0, 0);
|
|
49
|
-
ctx.restore();
|
|
50
|
-
};
|
|
51
|
-
// ── Card (element bounds) ───────────────────────────────────────────
|
|
52
|
-
const inset = LINE.card / 2;
|
|
53
|
-
roundRectPath(ctx, inset, inset, w - inset * 2, h - inset * 2, CARD_RADIUS);
|
|
54
|
-
ctx.fillStyle = COLORS.card;
|
|
55
|
-
ctx.fill();
|
|
56
|
-
ctx.strokeStyle = COLORS.cardBorder;
|
|
57
|
-
ctx.lineWidth = LINE.card;
|
|
58
|
-
ctx.stroke();
|
|
59
|
-
// ── Curve-driven gradient variants (inscribed in the frame) ─────────
|
|
60
|
-
// Hidden when `showGradient` is false → plain white background.
|
|
61
|
-
if (model.variant !== 'classic' && model.showGradient) {
|
|
62
|
-
paintGradientBackground(ctx, model.variant, px0, px1, py0, py1);
|
|
63
|
-
}
|
|
64
|
-
// ── Optional evolution band tints ───────────────────────────────────
|
|
65
|
-
if (model.banded) {
|
|
66
|
-
const starts = [0, 0.175, 0.4, 0.7];
|
|
67
|
-
const ends = [0.175, 0.4, 0.7, 1];
|
|
68
|
-
for (let i = 0; i < 4; i++) {
|
|
69
|
-
ctx.fillStyle = COLORS.band[i];
|
|
70
|
-
ctx.fillRect(ex(starts[i]), py0, ex(ends[i]) - ex(starts[i]), ph);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// ── Evolution phase dividers (dashed) ───────────────────────────────
|
|
74
|
-
if (model.showColumnDividers) {
|
|
75
|
-
ctx.strokeStyle = COLORS.divider;
|
|
76
|
-
ctx.lineWidth = LINE.divider;
|
|
77
|
-
ctx.setLineDash([5, 5]);
|
|
78
|
-
for (const r of EVOLUTION_BOUNDARIES) {
|
|
79
|
-
line(ex(r), py0, ex(r), py1);
|
|
80
|
-
}
|
|
81
|
-
ctx.setLineDash([]);
|
|
82
|
-
}
|
|
83
|
-
// ── Axes (L shape) + arrowheads ─────────────────────────────────────
|
|
84
|
-
// X and Y axes are independently toggleable. Each line stops at the base of
|
|
85
|
-
// its arrowhead (1px overlap, hidden under the triangle) so the line never
|
|
86
|
-
// pokes past the tip on zoom.
|
|
87
|
-
ctx.strokeStyle = COLORS.axis;
|
|
88
|
-
ctx.lineWidth = LINE.axis;
|
|
89
|
-
ctx.fillStyle = COLORS.axis;
|
|
90
|
-
if (model.showXAxis) {
|
|
91
|
-
line(px0, py1, px1 - ARROW + 1, py1); // X axis (arrow tip at px1)
|
|
92
|
-
ctx.beginPath(); // X arrow (points right)
|
|
93
|
-
ctx.moveTo(px1, py1);
|
|
94
|
-
ctx.lineTo(px1 - ARROW, py1 - ARROW / 2);
|
|
95
|
-
ctx.lineTo(px1 - ARROW, py1 + ARROW / 2);
|
|
96
|
-
ctx.closePath();
|
|
97
|
-
ctx.fill();
|
|
98
|
-
}
|
|
99
|
-
if (model.showYAxis) {
|
|
100
|
-
line(px0, py1, px0, py0 + ARROW - 1); // Y axis (arrow tip at py0)
|
|
101
|
-
ctx.beginPath(); // Y arrow (points up)
|
|
102
|
-
ctx.moveTo(px0, py0);
|
|
103
|
-
ctx.lineTo(px0 - ARROW / 2, py0 + ARROW);
|
|
104
|
-
ctx.lineTo(px0 + ARROW / 2, py0 + ARROW);
|
|
105
|
-
ctx.closePath();
|
|
106
|
-
ctx.fill();
|
|
107
|
-
}
|
|
108
|
-
// ── Horizontal labels ───────────────────────────────────────────────
|
|
109
|
-
ctx.textBaseline = 'alphabetic';
|
|
110
|
-
// Phase (column) labels (left-aligned at each zone start)
|
|
111
|
-
if (model.showColumnLabels) {
|
|
112
|
-
ctx.font = `${FONTS.phase}px ${FONT_FAMILY}`;
|
|
113
|
-
ctx.fillStyle = COLORS.label;
|
|
114
|
-
ctx.textAlign = 'left';
|
|
115
|
-
const phases = [
|
|
116
|
-
[model.phase0, 0],
|
|
117
|
-
[model.phase1, 0.175],
|
|
118
|
-
[model.phase2, 0.4],
|
|
119
|
-
[model.phase3, 0.7],
|
|
120
|
-
];
|
|
121
|
-
for (const [label, start] of phases) {
|
|
122
|
-
ctx.fillText(label, ex(start) + OFFSETS.phasePad, py1 + OFFSETS.phaseBaseline);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
// "Evolution" title near the X arrow (tied to the X axis)
|
|
126
|
-
if (model.showXAxis) {
|
|
127
|
-
ctx.font = `${FONTS.axis}px ${FONT_FAMILY}`;
|
|
128
|
-
ctx.fillStyle = COLORS.axis;
|
|
129
|
-
ctx.textAlign = 'right';
|
|
130
|
-
ctx.fillText(model.xAxisTitle, px1 - OFFSETS.evolutionPadRight, py1 + OFFSETS.phaseBaseline);
|
|
131
|
-
}
|
|
132
|
-
// Direction indicators (Uncharted / Industrialized, top corners)
|
|
133
|
-
if (model.showCornerLabels) {
|
|
134
|
-
ctx.font = `${FONTS.direction}px ${FONT_FAMILY}`;
|
|
135
|
-
ctx.fillStyle = COLORS.label;
|
|
136
|
-
ctx.textAlign = 'left';
|
|
137
|
-
ctx.fillText(model.evolutionStart, px0 + OFFSETS.directionPadLeft, py0 + OFFSETS.directionTop);
|
|
138
|
-
ctx.textAlign = 'right';
|
|
139
|
-
ctx.fillText(model.evolutionEnd, px1 - OFFSETS.directionPadRight, py0 + OFFSETS.directionTop);
|
|
140
|
-
}
|
|
141
|
-
// ── Rotated Y labels (hugging the axis, symmetric with the X labels) ─
|
|
142
|
-
if (model.showYAxis) {
|
|
143
|
-
vtext(model.yAxisTitle, px0 - OFFSETS.yHug, (py0 + py1) / 2, FONTS.axis, COLORS.axis);
|
|
144
|
-
}
|
|
145
|
-
if (model.showVisibilityLabels) {
|
|
146
|
-
vtext(model.visibilityHigh, px0 - OFFSETS.yHug, py0 + OFFSETS.visibleTop, FONTS.visibility, COLORS.label);
|
|
147
|
-
vtext(model.visibilityLow, px0 - OFFSETS.yHug, py1 - OFFSETS.invisibleBottom, FONTS.visibility, COLORS.label);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
export const WardleyElementRendererExtension = ElementRendererExtension('wardley', wardley);
|
|
14
|
+
export const wardley = createFrameworkBackgroundRenderer(WARDLEY_BACKGROUND);
|
|
15
|
+
export const WardleyElementRendererExtension = ElementRendererExtension(WARDLEY_BACKGROUND.type, wardley);
|
package/dist/element-view.d.ts
CHANGED
|
@@ -8,13 +8,17 @@ export declare class WardleyView extends GfxElementModelView<WardleyBackgroundEl
|
|
|
8
8
|
onDestroyed(): void;
|
|
9
9
|
/** Double-click on a label → edit its text in place. */
|
|
10
10
|
private _onDblClick;
|
|
11
|
+
/**
|
|
12
|
+
* @param current the words currently DRAWN — which is the vocabulary, not
|
|
13
|
+
* `model[field]`, for a label the user has never renamed. Opening on the raw
|
|
14
|
+
* prop would show an empty box for a label that plainly reads "Evolution".
|
|
15
|
+
*/
|
|
11
16
|
private _openLabelEditor;
|
|
12
17
|
private _closeLabelEditor;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
|
-
* Resize gating: the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* handles reactively. Moving/selecting stays available throughout.
|
|
20
|
+
* Resize gating, from the primitive: the handles stay hidden until
|
|
21
|
+
* `resizeEnabled` is true — the runtime half of the declaration's
|
|
22
|
+
* `geometry.resizable`.
|
|
19
23
|
*/
|
|
20
24
|
export declare const WardleyInteraction: import("@formicoidea/labre-core/store").ExtensionType;
|
package/dist/element-view.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
1
|
+
import { backgroundLabelHits, EdgelessCRUDIdentifier, FrameworkBackgroundInteractionExtension, hitTestBackgroundLabel, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { TranslationProvider } from '@formicoidea/labre-core/shared/services';
|
|
2
3
|
import { rotatePoint } from '@formicoidea/labre-core/global/gfx';
|
|
3
|
-
import { GfxElementModelView
|
|
4
|
-
import {
|
|
4
|
+
import { GfxElementModelView } from '@formicoidea/labre-core/std/gfx';
|
|
5
|
+
import { isWardleyLabelProp, WARDLEY_BACKGROUND, } from './background.js';
|
|
5
6
|
export class WardleyView extends GfxElementModelView {
|
|
6
7
|
constructor() {
|
|
7
8
|
super(...arguments);
|
|
@@ -34,15 +35,27 @@ export class WardleyView extends GfxElementModelView {
|
|
|
34
35
|
lx = ux - bx;
|
|
35
36
|
ly = uy - by;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
// Which labels exist, where they sit, what they SAY and which are editable
|
|
39
|
+
// all come from the declaration the renderer paints — one source, resolved
|
|
40
|
+
// through the same catalogue, so a label can never be drawn in one place
|
|
41
|
+
// and clicked in another, nor read one thing and open on another.
|
|
42
|
+
const hit = hitTestBackgroundLabel(backgroundLabelHits(WARDLEY_BACKGROUND, this.model, w, h, this.gfx.std.getOptional(TranslationProvider)), lx, ly);
|
|
38
43
|
if (!hit)
|
|
39
44
|
return;
|
|
40
|
-
this.
|
|
45
|
+
// The declaration names the prop; this decides whether it may be written.
|
|
46
|
+
if (!isWardleyLabelProp(hit.prop))
|
|
47
|
+
return;
|
|
48
|
+
this._openLabelEditor(hit.prop, hit.text, e);
|
|
41
49
|
}
|
|
42
|
-
|
|
50
|
+
/**
|
|
51
|
+
* @param current the words currently DRAWN — which is the vocabulary, not
|
|
52
|
+
* `model[field]`, for a label the user has never renamed. Opening on the raw
|
|
53
|
+
* prop would show an empty box for a label that plainly reads "Evolution".
|
|
54
|
+
*/
|
|
55
|
+
_openLabelEditor(field, current, e) {
|
|
43
56
|
this._closeLabelEditor();
|
|
44
57
|
const input = document.createElement('input');
|
|
45
|
-
input.value =
|
|
58
|
+
input.value = current;
|
|
46
59
|
Object.assign(input.style, {
|
|
47
60
|
position: 'fixed',
|
|
48
61
|
left: `${e.raw.clientX}px`,
|
|
@@ -73,6 +86,12 @@ export class WardleyView extends GfxElementModelView {
|
|
|
73
86
|
return;
|
|
74
87
|
const value = input.value;
|
|
75
88
|
this._closeLabelEditor();
|
|
89
|
+
// Opening an editor is not renaming. Writing back an untouched value
|
|
90
|
+
// would persist the resolved VOCABULARY as the user's own text, freezing
|
|
91
|
+
// the label in whatever language it was read in and putting it beyond
|
|
92
|
+
// any catalogue for good — and it would push an empty entry onto undo.
|
|
93
|
+
if (value === current)
|
|
94
|
+
return;
|
|
76
95
|
this.gfx.std.store.captureSync();
|
|
77
96
|
this.gfx.std
|
|
78
97
|
.get(EdgelessCRUDIdentifier)
|
|
@@ -103,19 +122,8 @@ export class WardleyView extends GfxElementModelView {
|
|
|
103
122
|
}
|
|
104
123
|
}
|
|
105
124
|
/**
|
|
106
|
-
* Resize gating: the
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* handles reactively. Moving/selecting stays available throughout.
|
|
125
|
+
* Resize gating, from the primitive: the handles stay hidden until
|
|
126
|
+
* `resizeEnabled` is true — the runtime half of the declaration's
|
|
127
|
+
* `geometry.resizable`.
|
|
110
128
|
*/
|
|
111
|
-
export const WardleyInteraction =
|
|
112
|
-
handleResize({ model }) {
|
|
113
|
-
return {
|
|
114
|
-
beforeResize({ set }) {
|
|
115
|
-
if (!model.resizeEnabled) {
|
|
116
|
-
set({ allowedHandlers: [] });
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
});
|
|
129
|
+
export const WardleyInteraction = FrameworkBackgroundInteractionExtension(WARDLEY_BACKGROUND);
|
package/dist/gradient.d.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
* Curve-driven gradient backgrounds (Slice C). Each analytic background is a
|
|
3
|
-
* smooth mathematical curve (piecewise asymmetric Gaussian bells); the gradient
|
|
4
|
-
* opacity at each evolution position X follows that curve, normalised between
|
|
5
|
-
* its own min and max — i.e. the gradient is strongest where the curve peaks and
|
|
6
|
-
* fades to nothing at its minimum. Validated against the reference images at
|
|
7
|
-
* `../wardley-mockups/gradient-backgrounds.html`.
|
|
8
|
-
*/
|
|
1
|
+
import type { BackgroundWashDef } from '@formicoidea/labre-core/blocks/surface';
|
|
9
2
|
export declare const GRADIENT_GREEN = "#1f9e4d";
|
|
10
3
|
export declare const GRADIENT_RED = "#d6455d";
|
|
11
4
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* The washes the Wardley declaration ships, in painting order. Only those whose
|
|
6
|
+
* `variants` name the background's current `variant` are painted, and only
|
|
7
|
+
* while `showGradient` is on — so `classic` paints none of them and the frame
|
|
8
|
+
* stays plain white, exactly as before.
|
|
14
9
|
*/
|
|
15
|
-
export declare
|
|
10
|
+
export declare const WARDLEY_WASHES: readonly BackgroundWashDef[];
|
package/dist/gradient.js
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* its own min and max — i.e. the gradient is strongest where the curve peaks and
|
|
6
6
|
* fades to nothing at its minimum. Validated against the reference images at
|
|
7
7
|
* `../wardley-mockups/gradient-backgrounds.html`.
|
|
8
|
+
*
|
|
9
|
+
* The curves are TABULATED ONCE, here, at module load: what the declaration
|
|
10
|
+
* ships — and what the primitive paints — is a table of `[offset, alpha]`
|
|
11
|
+
* stops, not a function. Nothing is evaluated at paint time, and the wash is
|
|
12
|
+
* data like the rest of the declaration (PF2.1).
|
|
8
13
|
*/
|
|
9
14
|
const bell = (x, mu, s) => Math.exp(-0.5 * ((x - mu) / s) ** 2);
|
|
10
15
|
const asym = (x, mu, sL, sR) => Math.exp(-0.5 * ((x - mu) / (x < mu ? sL : sR)) ** 2);
|
|
@@ -48,62 +53,68 @@ const GRADIENT_GREY = '#7c8389';
|
|
|
48
53
|
const GRADIENT_MAX_OPACITY = 0.45;
|
|
49
54
|
/** Peak opacity for the grey evolution-gradient variant. */
|
|
50
55
|
const GREY_MAX_OPACITY = 0.38;
|
|
51
|
-
function rgba(hex, alpha) {
|
|
52
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
53
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
54
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
55
|
-
return `rgba(${r},${g},${b},${alpha})`;
|
|
56
|
-
}
|
|
57
56
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* Tabulate one opacity profile as gradient stops spanning the plot width:
|
|
58
|
+
* 49 samples inside [x0, x1], bracketed by a zero stop wherever the profile
|
|
59
|
+
* does not reach the edge of the plot.
|
|
60
60
|
*/
|
|
61
|
-
function
|
|
61
|
+
function stopTable(opacityFn, x0, x1, maxOp = GRADIENT_MAX_OPACITY) {
|
|
62
62
|
const eps = 0.001;
|
|
63
|
+
const stops = [];
|
|
63
64
|
if (x0 > eps)
|
|
64
|
-
|
|
65
|
+
stops.push([Math.max(0, x0 - eps), 0]);
|
|
65
66
|
const N = 48;
|
|
66
67
|
for (let i = 0; i <= N; i++) {
|
|
67
68
|
const x = x0 + ((x1 - x0) * i) / N;
|
|
68
|
-
|
|
69
|
+
stops.push([clamp01(x), clamp01(opacityFn(x)) * maxOp]);
|
|
69
70
|
}
|
|
70
71
|
if (x1 < 1 - eps)
|
|
71
|
-
|
|
72
|
+
stops.push([Math.min(1, x1 + eps), 0]);
|
|
73
|
+
return stops;
|
|
72
74
|
}
|
|
75
|
+
// benefit: green where the curve is positive, red where negative.
|
|
76
|
+
const BEN_MAX_POS = RB.hi;
|
|
77
|
+
const BEN_MAX_NEG = -RB.lo;
|
|
73
78
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
79
|
+
* The washes the Wardley declaration ships, in painting order. Only those whose
|
|
80
|
+
* `variants` name the background's current `variant` are painted, and only
|
|
81
|
+
* while `showGradient` is on — so `classic` paints none of them and the frame
|
|
82
|
+
* stays plain white, exactly as before.
|
|
76
83
|
*/
|
|
77
|
-
export
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
84
|
+
export const WARDLEY_WASHES = [
|
|
85
|
+
{
|
|
86
|
+
id: 'evolution-grey',
|
|
87
|
+
variants: ['evolution-gradient'],
|
|
88
|
+
visibleProp: 'showGradient',
|
|
89
|
+
color: GRADIENT_GREY,
|
|
90
|
+
stops: stopTable(fGrey, 0, 1, GREY_MAX_OPACITY),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'opportunity-differential',
|
|
94
|
+
variants: ['opportunity'],
|
|
95
|
+
visibleProp: 'showGradient',
|
|
96
|
+
color: GRADIENT_GREEN,
|
|
97
|
+
stops: stopTable(x => norm(fDiff(x), RG.lo, RG.hi), DIFF_DOM[0], DIFF_DOM[1]),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'opportunity-operational',
|
|
101
|
+
variants: ['opportunity'],
|
|
102
|
+
visibleProp: 'showGradient',
|
|
103
|
+
color: GRADIENT_RED,
|
|
104
|
+
stops: stopTable(x => norm(fOper(x), RR.lo, RR.hi), OPER_DOM[0], OPER_DOM[1]),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'benefit-positive',
|
|
108
|
+
variants: ['benefit'],
|
|
109
|
+
visibleProp: 'showGradient',
|
|
110
|
+
color: GRADIENT_GREEN,
|
|
111
|
+
stops: stopTable(x => Math.max(0, fBen(x)) / BEN_MAX_POS, 0, 1),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'benefit-investment',
|
|
115
|
+
variants: ['benefit'],
|
|
116
|
+
visibleProp: 'showGradient',
|
|
117
|
+
color: GRADIENT_RED,
|
|
118
|
+
stops: stopTable(x => Math.max(0, -fBen(x)) / BEN_MAX_NEG, 0, 1),
|
|
119
|
+
},
|
|
120
|
+
];
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { WARDLEY_ROLE, WARDLEY_ROLES, type WardleyRole, type WardleyRoleId, } from './roles.js';
|
|
2
|
+
export { WARDLEY_AUDIT_CRITERIA } from './audit-criteria.js';
|
|
3
|
+
export { wardleyCommandIcons, wardleyCommands } from './commands.js';
|
|
4
|
+
export { wardleyTranslationEntries } from './translations.js';
|
|
5
|
+
export { WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
|
|
6
|
+
export { WARDLEY_PROFILES } from './profiles.js';
|
|
7
|
+
export { WARDLEY_NUDGES } from './nudges.js';
|
|
8
|
+
export { WARDLEY_NAMING_CONVENTIONS, WARDLEY_READING } from './reading.js';
|
|
9
|
+
export { WARDLEY_RULES } from './rules.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { WARDLEY_ROLE, WARDLEY_ROLES, } from './roles.js';
|
|
2
|
+
export { WARDLEY_AUDIT_CRITERIA } from './audit-criteria.js';
|
|
3
|
+
export { wardleyCommandIcons, wardleyCommands } from './commands.js';
|
|
4
|
+
export { wardleyTranslationEntries } from './translations.js';
|
|
5
|
+
export { WARDLEY_NATURE, WARDLEY_NATURE_TAG_ID, WARDLEY_TAG_DEFS, } from './natures.js';
|
|
6
|
+
export { WARDLEY_PROFILES } from './profiles.js';
|
|
7
|
+
export { WARDLEY_NUDGES } from './nudges.js';
|
|
8
|
+
export { WARDLEY_NAMING_CONVENTIONS, WARDLEY_READING } from './reading.js';
|
|
9
|
+
export { WARDLEY_RULES } from './rules.js';
|
package/dist/legend.js
CHANGED
|
@@ -110,6 +110,14 @@ export function createWardleyLegend(std, bg) {
|
|
|
110
110
|
xywh: new Bound(x, y, w, h).serialize(),
|
|
111
111
|
});
|
|
112
112
|
// ── glyph builders (real, editable elements), centred on (cx, cy) ─────
|
|
113
|
+
//
|
|
114
|
+
// DELIBERATELY ROLE-LESS. These are real `wardleyNode` elements, but a
|
|
115
|
+
// legend documents the map — it is not part of it. Giving its glyphs
|
|
116
|
+
// `wardley:component` & co. would make every legend entry count as an
|
|
117
|
+
// artefact and skew any rule written against roles (a legend would add a
|
|
118
|
+
// phantom component, anchor, market…). Neutral is the semantics we want;
|
|
119
|
+
// `kind` still drives their rendering. Frozen by a test in
|
|
120
|
+
// `__tests__/roles.unit.spec.ts`.
|
|
113
121
|
const ellipse = (kind, d, fill, sw, cx, cy) => surface.addElement({
|
|
114
122
|
type: 'wardleyNode',
|
|
115
123
|
kind,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { UniverseTagDefs } from '@formicoidea/labre-core/shared/services';
|
|
2
|
+
/**
|
|
3
|
+
* The Wardley **nature** tag — the framework's type-3 contextual qualification
|
|
4
|
+
* (PRD level 3, ADR 0007).
|
|
5
|
+
*
|
|
6
|
+
* A component's nature answers "what KIND of thing is this?", which is a
|
|
7
|
+
* different question from its role ("what is it, on a map?") and from its
|
|
8
|
+
* position ("how evolved is it?"). Simon Wardley's four are activity, data,
|
|
9
|
+
* practice and knowledge, and mapping practitioners routinely draw all four on
|
|
10
|
+
* one map — the same circle, the same axes, four different things.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this ships as DATA, on the host's own mechanism
|
|
13
|
+
*
|
|
14
|
+
* The library fixes the FORMAT of tag definitions; the application seeds them.
|
|
15
|
+
* Nothing here is privileged: this pack is registered through the same
|
|
16
|
+
* `UniverseTagDefsExtension` a host uses for its own taxonomy, and a client's
|
|
17
|
+
* private extension of Wardley — say a `criticality` tag — is a second pack
|
|
18
|
+
* with a different `packId` that merges with this one, with no library release.
|
|
19
|
+
* Shipping one real pack is what keeps that mechanism honest.
|
|
20
|
+
*
|
|
21
|
+
* ## Why it applies to `wardley:component` and not to `'*'`
|
|
22
|
+
*
|
|
23
|
+
* A nature qualifies a component. `market` and `ecosystem` specialise
|
|
24
|
+
* `wardley:component`, so they get it for free through `roleIsA` — that is the
|
|
25
|
+
* entire reason role hierarchy is data. The `anchor` (a user / need) is
|
|
26
|
+
* deliberately NOT a child of `component` and is deliberately NOT qualified
|
|
27
|
+
* here: a need has no nature, it has a demand. The map itself, the change
|
|
28
|
+
* arrow, the inertia bar and the labels are chrome or annotations and are not
|
|
29
|
+
* candidates either.
|
|
30
|
+
*
|
|
31
|
+
* ## Why `cardinality: 'single'`
|
|
32
|
+
*
|
|
33
|
+
* A component is one of the four, not several. Where practitioners disagree —
|
|
34
|
+
* "is a data pipeline data or an activity?" — the disagreement is the finding,
|
|
35
|
+
* and forcing one answer is what makes the finding visible. A multi-valued
|
|
36
|
+
* nature would let the ambiguity hide inside the element.
|
|
37
|
+
*
|
|
38
|
+
* Labels are English fallbacks: the host localizes them. The library never
|
|
39
|
+
* pretends a def's `label` is already translated for someone else's locale, and
|
|
40
|
+
* a pack shipped as a `.json` asset carries whatever the host put in it.
|
|
41
|
+
*/
|
|
42
|
+
export declare const WARDLEY_NATURE_TAG_ID = "wardley:nature";
|
|
43
|
+
/** The four natures, as value ids. Ids are forever; a def is only deprecated. */
|
|
44
|
+
export declare const WARDLEY_NATURE: {
|
|
45
|
+
readonly activity: "wardley:nature/activity";
|
|
46
|
+
readonly data: "wardley:nature/data";
|
|
47
|
+
readonly practice: "wardley:nature/practice";
|
|
48
|
+
readonly knowledge: "wardley:nature/knowledge";
|
|
49
|
+
};
|
|
50
|
+
export declare const WARDLEY_TAG_DEFS: UniverseTagDefs;
|
package/dist/natures.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { WARDLEY_ROLE } from './roles.js';
|
|
2
|
+
/**
|
|
3
|
+
* The Wardley **nature** tag — the framework's type-3 contextual qualification
|
|
4
|
+
* (PRD level 3, ADR 0007).
|
|
5
|
+
*
|
|
6
|
+
* A component's nature answers "what KIND of thing is this?", which is a
|
|
7
|
+
* different question from its role ("what is it, on a map?") and from its
|
|
8
|
+
* position ("how evolved is it?"). Simon Wardley's four are activity, data,
|
|
9
|
+
* practice and knowledge, and mapping practitioners routinely draw all four on
|
|
10
|
+
* one map — the same circle, the same axes, four different things.
|
|
11
|
+
*
|
|
12
|
+
* ## Why this ships as DATA, on the host's own mechanism
|
|
13
|
+
*
|
|
14
|
+
* The library fixes the FORMAT of tag definitions; the application seeds them.
|
|
15
|
+
* Nothing here is privileged: this pack is registered through the same
|
|
16
|
+
* `UniverseTagDefsExtension` a host uses for its own taxonomy, and a client's
|
|
17
|
+
* private extension of Wardley — say a `criticality` tag — is a second pack
|
|
18
|
+
* with a different `packId` that merges with this one, with no library release.
|
|
19
|
+
* Shipping one real pack is what keeps that mechanism honest.
|
|
20
|
+
*
|
|
21
|
+
* ## Why it applies to `wardley:component` and not to `'*'`
|
|
22
|
+
*
|
|
23
|
+
* A nature qualifies a component. `market` and `ecosystem` specialise
|
|
24
|
+
* `wardley:component`, so they get it for free through `roleIsA` — that is the
|
|
25
|
+
* entire reason role hierarchy is data. The `anchor` (a user / need) is
|
|
26
|
+
* deliberately NOT a child of `component` and is deliberately NOT qualified
|
|
27
|
+
* here: a need has no nature, it has a demand. The map itself, the change
|
|
28
|
+
* arrow, the inertia bar and the labels are chrome or annotations and are not
|
|
29
|
+
* candidates either.
|
|
30
|
+
*
|
|
31
|
+
* ## Why `cardinality: 'single'`
|
|
32
|
+
*
|
|
33
|
+
* A component is one of the four, not several. Where practitioners disagree —
|
|
34
|
+
* "is a data pipeline data or an activity?" — the disagreement is the finding,
|
|
35
|
+
* and forcing one answer is what makes the finding visible. A multi-valued
|
|
36
|
+
* nature would let the ambiguity hide inside the element.
|
|
37
|
+
*
|
|
38
|
+
* Labels are English fallbacks: the host localizes them. The library never
|
|
39
|
+
* pretends a def's `label` is already translated for someone else's locale, and
|
|
40
|
+
* a pack shipped as a `.json` asset carries whatever the host put in it.
|
|
41
|
+
*/
|
|
42
|
+
export const WARDLEY_NATURE_TAG_ID = 'wardley:nature';
|
|
43
|
+
/** The four natures, as value ids. Ids are forever; a def is only deprecated. */
|
|
44
|
+
export const WARDLEY_NATURE = {
|
|
45
|
+
activity: `${WARDLEY_NATURE_TAG_ID}/activity`,
|
|
46
|
+
data: `${WARDLEY_NATURE_TAG_ID}/data`,
|
|
47
|
+
practice: `${WARDLEY_NATURE_TAG_ID}/practice`,
|
|
48
|
+
knowledge: `${WARDLEY_NATURE_TAG_ID}/knowledge`,
|
|
49
|
+
};
|
|
50
|
+
export const WARDLEY_TAG_DEFS = {
|
|
51
|
+
formatVersion: 1,
|
|
52
|
+
// The id of this PACK, not of the framework: several packs may extend
|
|
53
|
+
// Wardley, and re-registering this one replaces it rather than duplicating
|
|
54
|
+
// it.
|
|
55
|
+
packId: 'wardley-core',
|
|
56
|
+
framework: 'wardley',
|
|
57
|
+
label: 'Wardley',
|
|
58
|
+
tags: [
|
|
59
|
+
{
|
|
60
|
+
id: WARDLEY_NATURE_TAG_ID,
|
|
61
|
+
label: 'Nature',
|
|
62
|
+
description: 'What kind of thing this component is: an activity, data, a practice or knowledge.',
|
|
63
|
+
cardinality: 'single',
|
|
64
|
+
appliesTo: [WARDLEY_ROLE.component],
|
|
65
|
+
// No `order`: it is the only tag this pack declares, and an absent order
|
|
66
|
+
// sorts by SEED order, which is what puts the library's pack ahead of a
|
|
67
|
+
// client's extension without either having to know about the other. A
|
|
68
|
+
// number here would be a claim about packs that do not exist yet.
|
|
69
|
+
values: [
|
|
70
|
+
{
|
|
71
|
+
id: WARDLEY_NATURE.activity,
|
|
72
|
+
label: 'Activity',
|
|
73
|
+
description: 'Something that is DONE — a step, a service, a process.',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: WARDLEY_NATURE.data,
|
|
77
|
+
label: 'Data',
|
|
78
|
+
description: 'Something that is RECORDED — a dataset, a register.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: WARDLEY_NATURE.practice,
|
|
82
|
+
label: 'Practice',
|
|
83
|
+
description: 'A way of doing — a method, a convention, an operating model.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: WARDLEY_NATURE.knowledge,
|
|
87
|
+
label: 'Knowledge',
|
|
88
|
+
description: 'Something that is KNOWN — a model, a theory, a rule.',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
package/dist/nudges.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { QualityNudge } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
/**
|
|
3
|
+
* Wardley **map quality** — the checklist (PF13.9).
|
|
4
|
+
*
|
|
5
|
+
* Level 1 is `./rules.ts`: deterministic, decidable, real time. This file is
|
|
6
|
+
* level 2, and the split is the whole point of the taxonomy — a rule only enters
|
|
7
|
+
* the deterministic engine if an algorithm can decide it on persisted data
|
|
8
|
+
* inside the ~16 ms budget. Everything else is a NUDGE: an expectation offered
|
|
9
|
+
* as a checklist, never evaluated, never blocking anything. Ticking is assuming.
|
|
10
|
+
*
|
|
11
|
+
* ## The check-up rules are gone (PO decision, 02/08/2026)
|
|
12
|
+
*
|
|
13
|
+
* Q5 (tone convention) and Q6 (phase nomenclature) used to live beside these,
|
|
14
|
+
* as on-demand rules behind a "Run check-up" button. The panel is now the
|
|
15
|
+
* checklist and nothing else: the PO's recette found that a second verdict, a
|
|
16
|
+
* button and a scope line asked the reader to hold three different kinds of
|
|
17
|
+
* statement in their head over a map that mostly wanted a title. Nothing was
|
|
18
|
+
* deleted from the platform — the on-demand MOMENT (PF5.14) and the
|
|
19
|
+
* `tone-convention` / `majority-fact` families are still in the engine, still
|
|
20
|
+
* tested, and the next framework that wants a check-up declares one. Wardley
|
|
21
|
+
* simply stopped exposing one.
|
|
22
|
+
*
|
|
23
|
+
* Registered from the flag-gated `WardleyViewExtension`, beside the rules and
|
|
24
|
+
* the profiles: a checklist is tooling. Switching the flag off takes it away and
|
|
25
|
+
* leaves the ticks written on the map, unread, until it comes back (PF7.10).
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* **Q1–Q4** — the four things a Wardley map needs in order to do its job, and
|
|
29
|
+
* that no algorithm can check.
|
|
30
|
+
*
|
|
31
|
+
* Every one of them is about whether the map can be DISCUSSED. A map with no
|
|
32
|
+
* title is a diagram nobody can situate; one with no legend is a private
|
|
33
|
+
* notation; one whose evolution axis is drawn but not used is a value chain
|
|
34
|
+
* wearing a Wardley costume. The tool can put all four on screen and it cannot
|
|
35
|
+
* judge a single one — "the title contextualises the study" is a statement about
|
|
36
|
+
* meaning, and a rule claiming to have verified it would be lying.
|
|
37
|
+
*
|
|
38
|
+
* The wordings are the PO's own, from the review of 01/08/2026, carried as
|
|
39
|
+
* `fallback` so a host with no catalogue reads them exactly as written.
|
|
40
|
+
*/
|
|
41
|
+
export declare const WARDLEY_NUDGES: readonly QualityNudge[];
|