@formicoidea/labre-framework-ddd-core-domain 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +29 -0
- package/dist/actions.js +58 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +82 -0
- package/dist/core-domain/background.d.ts +4 -0
- package/dist/core-domain/background.js +370 -0
- package/dist/core-domain/element-renderer.d.ts +7 -4
- package/dist/core-domain/element-renderer.js +10 -90
- package/dist/core-domain/element-view.d.ts +6 -1
- package/dist/core-domain/element-view.js +10 -13
- package/dist/core-domain/legend.d.ts +27 -16
- package/dist/core-domain/legend.js +75 -65
- package/dist/core-domain/toolbar-config.js +12 -18
- package/dist/descriptor.d.ts +2 -1
- package/dist/descriptor.js +2 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/nudges.d.ts +21 -0
- package/dist/nudges.js +42 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +68 -0
- package/dist/roles.d.ts +66 -0
- package/dist/roles.js +146 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +206 -0
- package/dist/toolbar/core-domain-menu.d.ts +8 -11
- package/dist/toolbar/core-domain-menu.js +16 -94
- package/dist/toolbar/senior-button.d.ts +1 -0
- package/dist/toolbar/senior-button.js +1 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +14 -0
- package/dist/translations.js +20 -0
- package/dist/view.d.ts +3 -1
- package/dist/view.js +35 -2
- package/package.json +3 -3
- package/dist/core-domain/consts.d.ts +0 -32
- package/dist/core-domain/consts.js +0 -30
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BlockStdScope } from '@formicoidea/labre-core/std';
|
|
2
|
+
import { type GfxController } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
type Surface = NonNullable<GfxController['surface']>;
|
|
4
|
+
/**
|
|
5
|
+
* Post a Core Domain Chart background, centred on `(cx, cy)`.
|
|
6
|
+
*
|
|
7
|
+
* The size, the role and whether the handles are offered all come from the
|
|
8
|
+
* DECLARATION (`createWardleyBackground` is the pattern): the chart is a
|
|
9
|
+
* first-class role, so validation rules position artefacts against
|
|
10
|
+
* `core-domain:chart` and never against the `coreDomain` element type, and a
|
|
11
|
+
* chart posted from the sub-menu agrees with one posted from a template.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createCoreDomainChart(surface: Surface, cx: number, cy: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Activate the native connector tool, pre-styled for a movement over time
|
|
16
|
+
* (dashed, red, arrow at the far end) and TYPED with `core-domain:movement`.
|
|
17
|
+
*
|
|
18
|
+
* The gesture replaces the free arrow the sub-menu used to drop on the canvas,
|
|
19
|
+
* and the reason is `docs/adr/0010`: a movement is a sentence — "this context
|
|
20
|
+
* is moving to that position" — and its `source → target` pair only becomes a
|
|
21
|
+
* STATEMENT once the user drew it end to end, from the current position to the
|
|
22
|
+
* future one (the role's own gesture hint). `core-domain.malformed-movement`
|
|
23
|
+
* reads exactly that pair back.
|
|
24
|
+
*
|
|
25
|
+
* Arrows drawn before this change carry no role and stay neutral: they are
|
|
26
|
+
* drawings, and no rule will ever speak about them.
|
|
27
|
+
*/
|
|
28
|
+
export declare function activateMovement(std: BlockStdScope): void;
|
|
29
|
+
export {};
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { backgroundSize } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { ConnectorTool } from '@formicoidea/labre-core/gfx/connector';
|
|
3
|
+
import { MOVEMENT_COLOR } from '@formicoidea/labre-ddd-shared';
|
|
4
|
+
import { ConnectorMode, PointStyle, StrokeStyle } from '@formicoidea/labre-core/model';
|
|
5
|
+
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
6
|
+
import { GfxControllerIdentifier } from '@formicoidea/labre-core/std/gfx';
|
|
7
|
+
import { CORE_DOMAIN_BACKGROUND } from './core-domain/background.js';
|
|
8
|
+
import { CORE_DOMAIN_ROLE } from './roles.js';
|
|
9
|
+
/** The stroke width the movement arrow has always been drawn at. */
|
|
10
|
+
const MOVEMENT_STROKE_WIDTH = 2;
|
|
11
|
+
/**
|
|
12
|
+
* Post a Core Domain Chart background, centred on `(cx, cy)`.
|
|
13
|
+
*
|
|
14
|
+
* The size, the role and whether the handles are offered all come from the
|
|
15
|
+
* DECLARATION (`createWardleyBackground` is the pattern): the chart is a
|
|
16
|
+
* first-class role, so validation rules position artefacts against
|
|
17
|
+
* `core-domain:chart` and never against the `coreDomain` element type, and a
|
|
18
|
+
* chart posted from the sub-menu agrees with one posted from a template.
|
|
19
|
+
*/
|
|
20
|
+
export function createCoreDomainChart(surface, cx, cy) {
|
|
21
|
+
const { width, height } = backgroundSize(CORE_DOMAIN_BACKGROUND);
|
|
22
|
+
return surface.addElement({
|
|
23
|
+
type: CORE_DOMAIN_BACKGROUND.type,
|
|
24
|
+
role: CORE_DOMAIN_BACKGROUND.role,
|
|
25
|
+
resizeEnabled: CORE_DOMAIN_BACKGROUND.geometry.resizable,
|
|
26
|
+
xywh: new Bound(cx - width / 2, cy - height / 2, width, height).serialize(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Activate the native connector tool, pre-styled for a movement over time
|
|
31
|
+
* (dashed, red, arrow at the far end) and TYPED with `core-domain:movement`.
|
|
32
|
+
*
|
|
33
|
+
* The gesture replaces the free arrow the sub-menu used to drop on the canvas,
|
|
34
|
+
* and the reason is `docs/adr/0010`: a movement is a sentence — "this context
|
|
35
|
+
* is moving to that position" — and its `source → target` pair only becomes a
|
|
36
|
+
* STATEMENT once the user drew it end to end, from the current position to the
|
|
37
|
+
* future one (the role's own gesture hint). `core-domain.malformed-movement`
|
|
38
|
+
* reads exactly that pair back.
|
|
39
|
+
*
|
|
40
|
+
* Arrows drawn before this change carry no role and stay neutral: they are
|
|
41
|
+
* drawings, and no rule will ever speak about them.
|
|
42
|
+
*/
|
|
43
|
+
export function activateMovement(std) {
|
|
44
|
+
const gfx = std.get(GfxControllerIdentifier);
|
|
45
|
+
gfx.tool.setTool(ConnectorTool, {
|
|
46
|
+
mode: ConnectorMode.Straight,
|
|
47
|
+
role: CORE_DOMAIN_ROLE.movement,
|
|
48
|
+
// The look rides on the activation, never through the last-props store:
|
|
49
|
+
// the plain connector tool must keep the user's own style (#144 M1).
|
|
50
|
+
style: {
|
|
51
|
+
stroke: MOVEMENT_COLOR,
|
|
52
|
+
strokeStyle: StrokeStyle.Dash,
|
|
53
|
+
strokeWidth: MOVEMENT_STROKE_WIDTH,
|
|
54
|
+
frontEndpointStyle: PointStyle.None,
|
|
55
|
+
rearEndpointStyle: PointStyle.Arrow,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { addDot, addMarker, CD_SUBDOMAINS, MOVEMENT_COLOR, placeDddElement, TEAM_TOPOLOGIES, } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import { svg } from 'lit';
|
|
3
|
+
import { activateMovement, createCoreDomainChart } from './actions.js';
|
|
4
|
+
import { markerRole, subdomainRole } from './roles.js';
|
|
5
|
+
/**
|
|
6
|
+
* The Core Domain Chart palette as commands: the background, the five
|
|
7
|
+
* sub-domain dots, the three Team Topologies markers and the movement arrow.
|
|
8
|
+
* The notation legend stays in the map-background context menu (toolbar
|
|
9
|
+
* config) — the element toolbar is out of scope for `docs/adr/0008`.
|
|
10
|
+
*/
|
|
11
|
+
const chartSwatch = svg `<svg viewBox="0 0 24 24" fill="none"><rect x="4" y="3" width="17" height="17" fill="#4d9900" fill-opacity="0.5"/><rect x="4" y="3" width="6" height="17" fill="#9933ff" fill-opacity="0.5"/><path d="M4 20 V3 M4 20 H21" stroke="currentColor" stroke-width="1.8"/></svg>`;
|
|
12
|
+
const dotSwatch = (color) => svg `<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="7" fill="${color}" stroke="#1f2328" stroke-width="1.2"/></svg>`;
|
|
13
|
+
const movementSwatch = svg `<svg viewBox="0 0 24 24" fill="none"><path d="M3 18 L16 7" stroke="${MOVEMENT_COLOR}" stroke-width="2" stroke-dasharray="3 3"/><path d="M12 6 L18 5 L17 11" stroke="${MOVEMENT_COLOR}" stroke-width="2" fill="none"/></svg>`;
|
|
14
|
+
const markerSwatch = (fill, letter) => svg `<svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3" fill="${fill}" stroke="#1f2328" stroke-width="1.2"/><text x="12" y="16" text-anchor="middle" font-size="11" font-family="sans-serif" fill="#1f2328">${letter}</text></svg>`;
|
|
15
|
+
const SPECS = [
|
|
16
|
+
{
|
|
17
|
+
id: 'addChart',
|
|
18
|
+
label: 'Core Domain Chart',
|
|
19
|
+
iconKey: 'ddd-core-domain.chart',
|
|
20
|
+
element: 'background',
|
|
21
|
+
icon: chartSwatch,
|
|
22
|
+
run: std => placeDddElement(std, (surface, cx, cy) => createCoreDomainChart(surface, cx, cy)),
|
|
23
|
+
},
|
|
24
|
+
...CD_SUBDOMAINS.map((preset) => ({
|
|
25
|
+
id: `add${preset.kind[0].toUpperCase()}${preset.kind.slice(1)}`,
|
|
26
|
+
label: preset.label,
|
|
27
|
+
iconKey: `ddd-core-domain.subdomain.${preset.kind}`,
|
|
28
|
+
element: `subdomain:${preset.kind}`,
|
|
29
|
+
icon: dotSwatch(preset.fill),
|
|
30
|
+
run: std => placeDddElement(std, (surface, cx, cy) => addDot(surface, std, cx, cy, preset.fill, preset.label,
|
|
31
|
+
// The dot IS the sub-domain: the role rides on the ellipse, so a
|
|
32
|
+
// rule about where a sub-domain sits measures the artefact and not
|
|
33
|
+
// the group that also holds its name.
|
|
34
|
+
subdomainRole(preset.kind))),
|
|
35
|
+
})),
|
|
36
|
+
...TEAM_TOPOLOGIES.map((preset) => ({
|
|
37
|
+
id: `add${preset.kind[0].toUpperCase()}${preset.kind.slice(1)}`,
|
|
38
|
+
label: preset.label,
|
|
39
|
+
iconKey: `ddd-core-domain.team-topology.${preset.kind}`,
|
|
40
|
+
element: `team-topology:${preset.kind}`,
|
|
41
|
+
icon: markerSwatch(preset.fill, preset.letter),
|
|
42
|
+
run: std => placeDddElement(std, (surface, cx, cy) => addMarker(surface, std, cx, cy, {
|
|
43
|
+
fill: preset.fill,
|
|
44
|
+
letter: preset.letter,
|
|
45
|
+
label: preset.label,
|
|
46
|
+
// The square IS the marker, so the role rides on it and not on the
|
|
47
|
+
// group that also holds its caption — the same call as the dot.
|
|
48
|
+
// Without this the automatic legend, which detects by role and only
|
|
49
|
+
// by role, could not see the markers at all.
|
|
50
|
+
role: markerRole(preset.kind),
|
|
51
|
+
})),
|
|
52
|
+
})),
|
|
53
|
+
{
|
|
54
|
+
id: 'addMovement',
|
|
55
|
+
label: 'Movement over time',
|
|
56
|
+
iconKey: 'ddd-core-domain.movement',
|
|
57
|
+
// Historical telemetry value, unchanged by the gesture becoming a drag.
|
|
58
|
+
element: 'movement',
|
|
59
|
+
icon: movementSwatch,
|
|
60
|
+
// No longer a free arrow dropped at the viewport centre: the movement is a
|
|
61
|
+
// typed edge, so the user draws it from the current position to the future
|
|
62
|
+
// one and the pair they drew IS the statement (`docs/adr/0010`).
|
|
63
|
+
run: activateMovement,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
export const coreDomainCommands = SPECS.map((spec, order) => ({
|
|
67
|
+
id: `ddd-core-domain.${spec.id}`,
|
|
68
|
+
owner: 'ddd-core-domain',
|
|
69
|
+
kind: 'artefact',
|
|
70
|
+
labelKey: `com.labre.commands.ddd-core-domain.${spec.id}`,
|
|
71
|
+
labelFallback: spec.label,
|
|
72
|
+
category: 'chart',
|
|
73
|
+
iconKey: spec.iconKey,
|
|
74
|
+
surfaces: ['senior-menu', 'catalogue', 'palette', 'agent'],
|
|
75
|
+
order,
|
|
76
|
+
scope: 'edgeless',
|
|
77
|
+
defaultKeys: { mac: [], other: [] },
|
|
78
|
+
availability: 'always',
|
|
79
|
+
run: spec.run,
|
|
80
|
+
telemetry: { framework: 'ddd-core-domain', element: spec.element },
|
|
81
|
+
}));
|
|
82
|
+
export const coreDomainCommandIcons = Object.fromEntries(SPECS.map(spec => [spec.iconKey, spec.icon]));
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FrameworkBackgroundDef } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
/** The palette entries the notation is made of — the tone convention's reference. */
|
|
3
|
+
export declare const CORE_DOMAIN_LEGEND_TONES: readonly string[];
|
|
4
|
+
export declare const CORE_DOMAIN_BACKGROUND: FrameworkBackgroundDef;
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { CD_SUBDOMAINS, FONT_FAMILY } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import { CORE_DOMAIN_ROLE } from '../roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* The Core Domain Chart background, DECLARED.
|
|
5
|
+
*
|
|
6
|
+
* This file is the whole of what makes a Core Domain Chart look like a Core
|
|
7
|
+
* Domain Chart. There is no chart-specific rendering code left: the primitive
|
|
8
|
+
* (`FrameworkBackgroundDef` / `createFrameworkBackgroundRenderer` in
|
|
9
|
+
* `@labre/affine-block-surface`) paints this declaration, and would paint any
|
|
10
|
+
* other framework's the same way.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here changes the DOCUMENT. The persisted element type is still
|
|
13
|
+
* `coreDomain` and its props are untouched — `showZones`, `showLabels`,
|
|
14
|
+
* `resizeEnabled` — they are simply named by the declaration instead of being
|
|
15
|
+
* read by hand-written drawing code. A chart authored before this file existed
|
|
16
|
+
* opens with the same geometry, the same bands and the same words: every
|
|
17
|
+
* coordinate below is the old `consts.ts` value converted, once, into a ratio of
|
|
18
|
+
* the plot, and `__tests__/background.unit.spec.ts` pins the conversion against
|
|
19
|
+
* the absolute numbers it came from.
|
|
20
|
+
*
|
|
21
|
+
* ## Two readings of one frame
|
|
22
|
+
*
|
|
23
|
+
* `variantProp: 'variant'` (see `CoreDomainChartElementModel.variant`) selects
|
|
24
|
+
* between the CLASSIC chart — Generic / Supporting / Core — and the MIGRATION
|
|
25
|
+
* one, whose four quadrants name the migration conversation instead. Same axes,
|
|
26
|
+
* same geometry, same element: only the regions and the vertical axis' title
|
|
27
|
+
* change, which is precisely what a variant is for. No validation rule cites a
|
|
28
|
+
* migration zone.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* The authoring reference space, kept as documentation of where every ratio
|
|
32
|
+
* below comes from: the chart was drawn at 900 × 820 with its plot inset by the
|
|
33
|
+
* margins declared in {@link CORE_DOMAIN_BACKGROUND}, so the plot is 786 × 746
|
|
34
|
+
* and `(absolute - origin) / span` is the conversion.
|
|
35
|
+
*/
|
|
36
|
+
const PLOT = { x0: 60, y0: 24, width: 786, height: 746 };
|
|
37
|
+
/** An absolute X of the reference drawing, as a ratio of the plot. */
|
|
38
|
+
const rx = (x) => (x - PLOT.x0) / PLOT.width;
|
|
39
|
+
/** An absolute Y of the reference drawing, as a ratio of the plot. */
|
|
40
|
+
const ry = (y) => (y - PLOT.y0) / PLOT.height;
|
|
41
|
+
/** An absolute width, as a ratio of the plot width. */
|
|
42
|
+
const rw = (w) => w / PLOT.width;
|
|
43
|
+
/** An absolute height, as a ratio of the plot height. */
|
|
44
|
+
const rh = (h) => h / PLOT.height;
|
|
45
|
+
/**
|
|
46
|
+
* The colour code: every colour is named, never repeated as a hex.
|
|
47
|
+
*
|
|
48
|
+
* The last five entries are the **legend**, and they are palette entries for a
|
|
49
|
+
* reason: `core-domain.off-legend-colour` names them
|
|
50
|
+
* (`ToneConventionDef.palette`) instead of restating a colour, so the notation
|
|
51
|
+
* and the chart are restyled together, in one place. They are read straight off
|
|
52
|
+
* `CD_SUBDOMAINS`, the very table the sub-menu builds its five dots from — the
|
|
53
|
+
* convention and the swatches can therefore never drift apart.
|
|
54
|
+
*
|
|
55
|
+
* The zone tints carry their alpha BAKED INTO THE HEX (`…99` is the 0.6 the
|
|
56
|
+
* old renderer set with `globalAlpha`): a zone fill is handed to `fillStyle`
|
|
57
|
+
* verbatim, so eight-digit hex is how a translucent band is declared.
|
|
58
|
+
*/
|
|
59
|
+
const PALETTE = {
|
|
60
|
+
axis: '#000000',
|
|
61
|
+
title: '#000000',
|
|
62
|
+
tick: '#777777',
|
|
63
|
+
/** Zone names of the classic reading: white, over saturated bands. */
|
|
64
|
+
zoneLabel: '#ffffff',
|
|
65
|
+
/** Zone names of the migration reading: ink, over pale tints. */
|
|
66
|
+
zoneLabelDark: '#1f2328',
|
|
67
|
+
zoneGeneric: '#b3b3b399',
|
|
68
|
+
zoneSupporting: '#9933ff99',
|
|
69
|
+
zoneCore: '#4d990099',
|
|
70
|
+
zoneLowHangingFruit: '#4d990026',
|
|
71
|
+
zoneRiskSeeking: '#9933ff26',
|
|
72
|
+
zoneRiskAverse: '#b3b3b326',
|
|
73
|
+
zoneLastToothpaste: '#ff333326',
|
|
74
|
+
...Object.fromEntries(CD_SUBDOMAINS.map(preset => [preset.kind, preset.fill])),
|
|
75
|
+
};
|
|
76
|
+
/** The palette entries the notation is made of — the tone convention's reference. */
|
|
77
|
+
export const CORE_DOMAIN_LEGEND_TONES = CD_SUBDOMAINS.map(preset => preset.kind);
|
|
78
|
+
const AXIS_TEXT = {
|
|
79
|
+
size: 14,
|
|
80
|
+
weight: 600,
|
|
81
|
+
color: '@title',
|
|
82
|
+
};
|
|
83
|
+
const TICK_TEXT = { size: 12, color: '@tick' };
|
|
84
|
+
const ZONE_TEXT = {
|
|
85
|
+
size: 20,
|
|
86
|
+
weight: 700,
|
|
87
|
+
color: '@zoneLabel',
|
|
88
|
+
};
|
|
89
|
+
const CORE_TEXT = { ...ZONE_TEXT, size: 26 };
|
|
90
|
+
const MIGRATION_TEXT = {
|
|
91
|
+
size: 20,
|
|
92
|
+
weight: 700,
|
|
93
|
+
color: '@zoneLabelDark',
|
|
94
|
+
};
|
|
95
|
+
/** A centred zone name, gated by `showLabels` like every other word on the chart. */
|
|
96
|
+
function zoneLabel(id, key, fallback, x, y, style) {
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
labelKey: `com.labre.core-domain.background.zone.${key}`,
|
|
100
|
+
fallback,
|
|
101
|
+
anchor: { x: rx(x), y: ry(y) },
|
|
102
|
+
style,
|
|
103
|
+
align: 'center',
|
|
104
|
+
visibleProp: 'showLabels',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The classic reading: the three named bands of the DDD Crew chart, plus the
|
|
109
|
+
* fourth (unnamed) quadrant the template tints without ever writing on.
|
|
110
|
+
*
|
|
111
|
+
* `variants: ['classic']` is what makes them a READING rather than the chart:
|
|
112
|
+
* turn the frame to `migration` and these regions are not there — which is also
|
|
113
|
+
* why `core-domain.outsourced-core` falls silent on a migration chart instead of
|
|
114
|
+
* measuring against a quadrant nobody can see.
|
|
115
|
+
*
|
|
116
|
+
* NOTE on the ids: `supporting-low-complexity` names the bottom-right quadrant
|
|
117
|
+
* — high differentiation, LOW complexity, since the complexity axis runs
|
|
118
|
+
* upwards. (The plan first named it `supporting-high-complexity`, factually
|
|
119
|
+
* inverted; corrected at the merge recette of 26/08/2026 — no rule cites it.)
|
|
120
|
+
*/
|
|
121
|
+
const CLASSIC_ZONES = [
|
|
122
|
+
{
|
|
123
|
+
id: 'generic',
|
|
124
|
+
variants: ['classic'],
|
|
125
|
+
rect: { x: rx(70), y: ry(30), w: rw(150), h: rh(720) },
|
|
126
|
+
fill: '@zoneGeneric',
|
|
127
|
+
fillVisibleProp: 'showZones',
|
|
128
|
+
label: zoneLabel('generic', 'generic', 'Generic', 150, 474, ZONE_TEXT),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'supporting-low-diff',
|
|
132
|
+
variants: ['classic'],
|
|
133
|
+
rect: { x: rx(220), y: ry(30), w: rw(220), h: rh(720) },
|
|
134
|
+
fill: '@zoneSupporting',
|
|
135
|
+
fillVisibleProp: 'showZones',
|
|
136
|
+
label: zoneLabel('supporting', 'supporting', 'Supporting', 340, 474, ZONE_TEXT),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'core',
|
|
140
|
+
variants: ['classic'],
|
|
141
|
+
rect: { x: rx(440), y: ry(30), w: rw(400), h: rh(360) },
|
|
142
|
+
fill: '@zoneCore',
|
|
143
|
+
fillVisibleProp: 'showZones',
|
|
144
|
+
label: zoneLabel('core', 'core', 'Core', 640, 214, CORE_TEXT),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
// The template tints this one and writes nothing on it, so neither does the
|
|
148
|
+
// declaration: an invented name would be prose nobody asked for.
|
|
149
|
+
id: 'supporting-low-complexity',
|
|
150
|
+
variants: ['classic'],
|
|
151
|
+
rect: { x: rx(440), y: ry(390), w: rw(400), h: rh(360) },
|
|
152
|
+
fill: '@zoneSupporting',
|
|
153
|
+
fillVisibleProp: 'showZones',
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
/**
|
|
157
|
+
* The migration reading: the same frame, four quadrants, and the vocabulary of
|
|
158
|
+
* a modernisation conversation.
|
|
159
|
+
*
|
|
160
|
+
* Regions of READING only — deliberately not cited by any rule. What counts as
|
|
161
|
+
* a low-hanging fruit is a judgement about cost and appetite that no coordinate
|
|
162
|
+
* decides, and a rule reading it off a dot's position would be inventing a
|
|
163
|
+
* verdict the chart never claimed.
|
|
164
|
+
*/
|
|
165
|
+
const MIGRATION_ZONES = [
|
|
166
|
+
{
|
|
167
|
+
id: 'last-toothpaste',
|
|
168
|
+
variants: ['migration'],
|
|
169
|
+
rect: { x: 0, y: 0, w: 0.5, h: 0.5 },
|
|
170
|
+
fill: '@zoneLastToothpaste',
|
|
171
|
+
fillVisibleProp: 'showZones',
|
|
172
|
+
label: {
|
|
173
|
+
id: 'lastToothpaste',
|
|
174
|
+
labelKey: 'com.labre.core-domain.background.zone.last-toothpaste',
|
|
175
|
+
fallback: 'Last toothpaste',
|
|
176
|
+
anchor: { x: 0.25, y: 0.25 },
|
|
177
|
+
style: MIGRATION_TEXT,
|
|
178
|
+
align: 'center',
|
|
179
|
+
visibleProp: 'showLabels',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'risk-seeking',
|
|
184
|
+
variants: ['migration'],
|
|
185
|
+
rect: { x: 0.5, y: 0, w: 0.5, h: 0.5 },
|
|
186
|
+
fill: '@zoneRiskSeeking',
|
|
187
|
+
fillVisibleProp: 'showZones',
|
|
188
|
+
label: {
|
|
189
|
+
id: 'riskSeeking',
|
|
190
|
+
labelKey: 'com.labre.core-domain.background.zone.risk-seeking',
|
|
191
|
+
fallback: 'Risk-seeking',
|
|
192
|
+
anchor: { x: 0.75, y: 0.25 },
|
|
193
|
+
style: MIGRATION_TEXT,
|
|
194
|
+
align: 'center',
|
|
195
|
+
visibleProp: 'showLabels',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'risk-averse',
|
|
200
|
+
variants: ['migration'],
|
|
201
|
+
rect: { x: 0, y: 0.5, w: 0.5, h: 0.5 },
|
|
202
|
+
fill: '@zoneRiskAverse',
|
|
203
|
+
fillVisibleProp: 'showZones',
|
|
204
|
+
label: {
|
|
205
|
+
id: 'riskAverse',
|
|
206
|
+
labelKey: 'com.labre.core-domain.background.zone.risk-averse',
|
|
207
|
+
fallback: 'Risk-averse',
|
|
208
|
+
anchor: { x: 0.25, y: 0.75 },
|
|
209
|
+
style: MIGRATION_TEXT,
|
|
210
|
+
align: 'center',
|
|
211
|
+
visibleProp: 'showLabels',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: 'lhf',
|
|
216
|
+
variants: ['migration'],
|
|
217
|
+
rect: { x: 0.5, y: 0.5, w: 0.5, h: 0.5 },
|
|
218
|
+
fill: '@zoneLowHangingFruit',
|
|
219
|
+
fillVisibleProp: 'showZones',
|
|
220
|
+
label: {
|
|
221
|
+
id: 'lowHangingFruit',
|
|
222
|
+
labelKey: 'com.labre.core-domain.background.zone.lhf',
|
|
223
|
+
fallback: 'Low-hanging fruit',
|
|
224
|
+
anchor: { x: 0.75, y: 0.75 },
|
|
225
|
+
style: MIGRATION_TEXT,
|
|
226
|
+
align: 'center',
|
|
227
|
+
visibleProp: 'showLabels',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
/**
|
|
232
|
+
* The two axes. Complexity runs up the left edge, Business differentiation
|
|
233
|
+
* along the bottom; both arrows point "forward", i.e. towards MORE.
|
|
234
|
+
*
|
|
235
|
+
* Neither axis carries a `visibleProp`: the frame of reference is the chart, and
|
|
236
|
+
* the template has never offered to hide it. `showLabels` gates the WORDS — the
|
|
237
|
+
* titles and the four Low/High ticks — which is the toggle the toolbar has
|
|
238
|
+
* always had (and which the `showLabels` prop has always claimed to cover, zone
|
|
239
|
+
* names included).
|
|
240
|
+
*
|
|
241
|
+
* `arrowSize: 9` reproduces the reference arrowhead's LENGTH exactly; the
|
|
242
|
+
* primitive draws it 9 wide against the template's 10, a half-unit either side
|
|
243
|
+
* of a nine-unit triangle.
|
|
244
|
+
*/
|
|
245
|
+
const CORE_DOMAIN_AXES = [
|
|
246
|
+
{
|
|
247
|
+
id: 'complexity',
|
|
248
|
+
orientation: 'vertical',
|
|
249
|
+
at: 0,
|
|
250
|
+
arrow: 'forward',
|
|
251
|
+
arrowSize: 9,
|
|
252
|
+
stroke: { color: '@axis', width: 2 },
|
|
253
|
+
title: {
|
|
254
|
+
id: 'complexityTitle',
|
|
255
|
+
// The classic reading's vertical axis. The migration one relabels it —
|
|
256
|
+
// see `MIGRATION_AXIS_TITLE` below, and why it rides in `endLabels`.
|
|
257
|
+
variants: ['classic'],
|
|
258
|
+
labelKey: 'com.labre.core-domain.background.axis.complexity',
|
|
259
|
+
fallback: 'Complexity',
|
|
260
|
+
anchor: { x: 0, y: ry(400), dx: -32 },
|
|
261
|
+
style: AXIS_TEXT,
|
|
262
|
+
vertical: true,
|
|
263
|
+
visibleProp: 'showLabels',
|
|
264
|
+
},
|
|
265
|
+
endLabels: [
|
|
266
|
+
{
|
|
267
|
+
id: 'complexityLow',
|
|
268
|
+
labelKey: 'com.labre.core-domain.background.complexity.low',
|
|
269
|
+
fallback: 'Low',
|
|
270
|
+
anchor: { x: 0, y: ry(758), dx: -12 },
|
|
271
|
+
style: TICK_TEXT,
|
|
272
|
+
vertical: true,
|
|
273
|
+
visibleProp: 'showLabels',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'complexityHigh',
|
|
277
|
+
labelKey: 'com.labre.core-domain.background.complexity.high',
|
|
278
|
+
fallback: 'High',
|
|
279
|
+
anchor: { x: 0, y: ry(44), dx: -22 },
|
|
280
|
+
style: TICK_TEXT,
|
|
281
|
+
vertical: true,
|
|
282
|
+
visibleProp: 'showLabels',
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* The migration reading's vertical axis title, at the very position and
|
|
286
|
+
* in the very style the classic one occupies.
|
|
287
|
+
*
|
|
288
|
+
* It rides in `endLabels` because an axis declares ONE `title`, and the
|
|
289
|
+
* two are alternatives selected by the variant rather than two things
|
|
290
|
+
* drawn together. `endLabels` is the only list of free texts an axis
|
|
291
|
+
* owns; the alternative would have been a second declaration of the whole
|
|
292
|
+
* background, which is exactly what a variant exists to avoid.
|
|
293
|
+
*/
|
|
294
|
+
{
|
|
295
|
+
id: 'complexityTitleMigration',
|
|
296
|
+
variants: ['migration'],
|
|
297
|
+
labelKey: 'com.labre.core-domain.background.axis.migration-cost',
|
|
298
|
+
fallback: 'Cost of migration',
|
|
299
|
+
anchor: { x: 0, y: ry(400), dx: -32 },
|
|
300
|
+
style: AXIS_TEXT,
|
|
301
|
+
vertical: true,
|
|
302
|
+
visibleProp: 'showLabels',
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: 'differentiation',
|
|
308
|
+
orientation: 'horizontal',
|
|
309
|
+
at: 1,
|
|
310
|
+
arrow: 'forward',
|
|
311
|
+
arrowSize: 9,
|
|
312
|
+
stroke: { color: '@axis', width: 2 },
|
|
313
|
+
title: {
|
|
314
|
+
id: 'differentiationTitle',
|
|
315
|
+
labelKey: 'com.labre.core-domain.background.axis.differentiation',
|
|
316
|
+
fallback: 'Business differentiation',
|
|
317
|
+
anchor: { x: rx(450), y: 1, dy: 30 },
|
|
318
|
+
style: AXIS_TEXT,
|
|
319
|
+
align: 'center',
|
|
320
|
+
visibleProp: 'showLabels',
|
|
321
|
+
},
|
|
322
|
+
endLabels: [
|
|
323
|
+
{
|
|
324
|
+
id: 'differentiationLow',
|
|
325
|
+
labelKey: 'com.labre.core-domain.background.differentiation.low',
|
|
326
|
+
fallback: 'Low',
|
|
327
|
+
anchor: { x: rx(84), y: 1, dy: 22 },
|
|
328
|
+
style: TICK_TEXT,
|
|
329
|
+
align: 'center',
|
|
330
|
+
visibleProp: 'showLabels',
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: 'differentiationHigh',
|
|
334
|
+
labelKey: 'com.labre.core-domain.background.differentiation.high',
|
|
335
|
+
fallback: 'High',
|
|
336
|
+
anchor: { x: rx(838), y: 1, dy: 22 },
|
|
337
|
+
style: TICK_TEXT,
|
|
338
|
+
align: 'center',
|
|
339
|
+
visibleProp: 'showLabels',
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
export const CORE_DOMAIN_BACKGROUND = {
|
|
345
|
+
type: 'coreDomain',
|
|
346
|
+
// The chart is a first-class role: validation rules position artefacts
|
|
347
|
+
// against `core-domain:chart`, never against the `coreDomain` element type.
|
|
348
|
+
role: CORE_DOMAIN_ROLE.chart,
|
|
349
|
+
variantProp: 'variant',
|
|
350
|
+
geometry: {
|
|
351
|
+
width: 900,
|
|
352
|
+
height: 820,
|
|
353
|
+
lockAspectRatio: true,
|
|
354
|
+
// The chart has always offered its handles (`resizeEnabled` defaults to
|
|
355
|
+
// true on the model); the declaration says so, and the toolbar toggle takes
|
|
356
|
+
// over from there.
|
|
357
|
+
resizable: true,
|
|
358
|
+
margin: { top: 24, right: 54, bottom: 50, left: 60 },
|
|
359
|
+
},
|
|
360
|
+
chrome: {
|
|
361
|
+
fontFamily: FONT_FAMILY,
|
|
362
|
+
palette: PALETTE,
|
|
363
|
+
// No card, on purpose: the template draws its translucent bands straight
|
|
364
|
+
// onto the canvas, and painting a white rectangle under them would be a
|
|
365
|
+
// visual change dressed up as a refactor.
|
|
366
|
+
surface: {},
|
|
367
|
+
},
|
|
368
|
+
zones: [...CLASSIC_ZONES, ...MIGRATION_ZONES],
|
|
369
|
+
axes: CORE_DOMAIN_AXES,
|
|
370
|
+
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { type ElementRenderer } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import type { CoreDomainChartElementModel } from '@formicoidea/labre-core/model';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The Core Domain Chart background renderer — the framework-background
|
|
5
|
+
* primitive, driven by {@link CORE_DOMAIN_BACKGROUND}.
|
|
6
|
+
*
|
|
7
|
+
* There is no chart-specific drawing code any more: the bands, the two axes with
|
|
8
|
+
* their arrowheads, the zone names and the Low/High ticks are all declared, and
|
|
9
|
+
* `__tests__/background.unit.spec.ts` pins the picture against the absolute
|
|
10
|
+
* constants the deleted imperative renderer drew from.
|
|
8
11
|
*/
|
|
9
12
|
export declare const coreDomain: ElementRenderer<CoreDomainChartElementModel>;
|
|
10
13
|
export declare const CoreDomainRendererExtension: import("@formicoidea/labre-core/store").ExtensionType & {
|
|
@@ -1,93 +1,13 @@
|
|
|
1
|
-
import { ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
-
import {
|
|
3
|
-
import { AXIS, COLORS, REF_H, REF_W, ZONE_LABELS, ZONES } from './consts.js';
|
|
1
|
+
import { createFrameworkBackgroundRenderer, ElementRendererExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { CORE_DOMAIN_BACKGROUND } from './background.js';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The Core Domain Chart background renderer — the framework-background
|
|
5
|
+
* primitive, driven by {@link CORE_DOMAIN_BACKGROUND}.
|
|
6
|
+
*
|
|
7
|
+
* There is no chart-specific drawing code any more: the bands, the two axes with
|
|
8
|
+
* their arrowheads, the zone names and the Low/High ticks are all declared, and
|
|
9
|
+
* `__tests__/background.unit.spec.ts` pins the picture against the absolute
|
|
10
|
+
* constants the deleted imperative renderer drew from.
|
|
9
11
|
*/
|
|
10
|
-
export const coreDomain = (
|
|
11
|
-
const [, , w, h] = model.deserializedXYWH;
|
|
12
|
-
const cx = w / 2;
|
|
13
|
-
const cy = h / 2;
|
|
14
|
-
ctx.setTransform(matrix.translateSelf(cx, cy).rotateSelf(model.rotate).translateSelf(-cx, -cy));
|
|
15
|
-
const { s, ox, oy } = refScale(w, h, REF_W, REF_H);
|
|
16
|
-
ctx.translate(ox, oy);
|
|
17
|
-
ctx.scale(s, s);
|
|
18
|
-
// ── Translucent zone bands ──────────────────────────────────────────
|
|
19
|
-
if (model.showZones) {
|
|
20
|
-
ctx.globalAlpha = 0.6;
|
|
21
|
-
for (const z of ZONES) {
|
|
22
|
-
ctx.fillStyle = z.fill;
|
|
23
|
-
ctx.fillRect(z.x, z.y, z.w, z.h);
|
|
24
|
-
}
|
|
25
|
-
ctx.globalAlpha = 1;
|
|
26
|
-
}
|
|
27
|
-
// ── Axes with arrow heads ───────────────────────────────────────────
|
|
28
|
-
ctx.strokeStyle = COLORS.axis;
|
|
29
|
-
ctx.fillStyle = COLORS.axis;
|
|
30
|
-
ctx.lineWidth = 2;
|
|
31
|
-
ctx.lineJoin = 'round';
|
|
32
|
-
// Y axis (up)
|
|
33
|
-
ctx.beginPath();
|
|
34
|
-
ctx.moveTo(AXIS.ox, AXIS.oy);
|
|
35
|
-
ctx.lineTo(AXIS.ox, AXIS.top + 6);
|
|
36
|
-
ctx.stroke();
|
|
37
|
-
arrow(ctx, AXIS.ox, AXIS.top, 0);
|
|
38
|
-
// X axis (right)
|
|
39
|
-
ctx.beginPath();
|
|
40
|
-
ctx.moveTo(AXIS.ox, AXIS.oy);
|
|
41
|
-
ctx.lineTo(AXIS.right - 6, AXIS.oy);
|
|
42
|
-
ctx.stroke();
|
|
43
|
-
arrow(ctx, AXIS.right, AXIS.oy, 90);
|
|
44
|
-
ctx.textBaseline = 'alphabetic';
|
|
45
|
-
// ── Zone names ──────────────────────────────────────────────────────
|
|
46
|
-
ctx.textAlign = 'center';
|
|
47
|
-
for (const [text, x, y, size] of ZONE_LABELS) {
|
|
48
|
-
ctx.fillStyle = COLORS.zoneLabel;
|
|
49
|
-
ctx.font = `700 ${size}px ${FONT_FAMILY}`;
|
|
50
|
-
ctx.fillText(text, x, y);
|
|
51
|
-
}
|
|
52
|
-
// ── Axis titles + Low/High ticks ────────────────────────────────────
|
|
53
|
-
if (model.showLabels) {
|
|
54
|
-
ctx.fillStyle = COLORS.title;
|
|
55
|
-
ctx.font = `600 14px ${FONT_FAMILY}`;
|
|
56
|
-
ctx.save();
|
|
57
|
-
ctx.translate(28, 400);
|
|
58
|
-
ctx.rotate(-Math.PI / 2);
|
|
59
|
-
ctx.fillText('Complexity', 0, 0);
|
|
60
|
-
ctx.restore();
|
|
61
|
-
ctx.fillText('Business differentiation', 450, 800);
|
|
62
|
-
ctx.fillStyle = COLORS.tick;
|
|
63
|
-
ctx.font = `12px ${FONT_FAMILY}`;
|
|
64
|
-
// X ticks
|
|
65
|
-
ctx.fillText('Low', 84, 792);
|
|
66
|
-
ctx.fillText('High', 838, 792);
|
|
67
|
-
// Y ticks
|
|
68
|
-
ctx.save();
|
|
69
|
-
ctx.translate(48, 758);
|
|
70
|
-
ctx.rotate(-Math.PI / 2);
|
|
71
|
-
ctx.fillText('Low', 0, 0);
|
|
72
|
-
ctx.restore();
|
|
73
|
-
ctx.save();
|
|
74
|
-
ctx.translate(38, 44);
|
|
75
|
-
ctx.rotate(-Math.PI / 2);
|
|
76
|
-
ctx.fillText('High', 0, 0);
|
|
77
|
-
ctx.restore();
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
/** Filled triangular arrow head at (x, y); `deg` rotates it (0 = pointing up). */
|
|
81
|
-
function arrow(ctx, x, y, deg) {
|
|
82
|
-
ctx.save();
|
|
83
|
-
ctx.translate(x, y);
|
|
84
|
-
ctx.rotate((deg * Math.PI) / 180);
|
|
85
|
-
ctx.beginPath();
|
|
86
|
-
ctx.moveTo(0, 0);
|
|
87
|
-
ctx.lineTo(-5, 9);
|
|
88
|
-
ctx.lineTo(5, 9);
|
|
89
|
-
ctx.closePath();
|
|
90
|
-
ctx.fill();
|
|
91
|
-
ctx.restore();
|
|
92
|
-
}
|
|
12
|
+
export const coreDomain = createFrameworkBackgroundRenderer(CORE_DOMAIN_BACKGROUND);
|
|
93
13
|
export const CoreDomainRendererExtension = ElementRendererExtension('coreDomain', coreDomain);
|