@formicoidea/labre-framework-ddd-context-map 0.32.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 +38 -0
- package/dist/actions.js +84 -0
- package/dist/background.d.ts +19 -0
- package/dist/background.js +51 -0
- package/dist/commands.js +37 -61
- package/dist/descriptor.d.ts +7 -2
- package/dist/descriptor.js +5 -2
- package/dist/element-renderer.d.ts +17 -0
- package/dist/element-renderer.js +15 -0
- package/dist/element-view.d.ts +17 -0
- package/dist/element-view.js +18 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/legend.d.ts +20 -0
- package/dist/legend.js +56 -0
- package/dist/nudges.d.ts +19 -0
- package/dist/nudges.js +55 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +73 -0
- package/dist/roles.d.ts +65 -0
- package/dist/roles.js +64 -0
- package/dist/rules.d.ts +2 -0
- package/dist/rules.js +264 -0
- package/dist/toolbar/board-config.d.ts +30 -0
- package/dist/toolbar/board-config.js +87 -0
- package/dist/toolbar/context-map-menu.d.ts +1 -1
- package/dist/toolbar/context-map-menu.js +1 -1
- 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 +9 -9
- package/dist/translations.js +16 -11
- package/dist/view.d.ts +17 -1
- package/dist/view.js +60 -2
- package/package.json +3 -3
package/dist/profiles.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Mapping validation profiles (WS2).
|
|
3
|
+
*
|
|
4
|
+
* DATA owned by the framework, like its rules and its roles. A profile is
|
|
5
|
+
* chosen per BOARD, not per document, and the choice rides on the board element
|
|
6
|
+
* itself — two maps at two levels of requirement coexist on one canvas.
|
|
7
|
+
*
|
|
8
|
+
* Registered from the flag-gated `DddContextMapViewExtension`, beside the
|
|
9
|
+
* rules: switching the Context Map flag off takes the choice away with the rest
|
|
10
|
+
* of the tooling, and a board already set to `strict` simply stops being checked
|
|
11
|
+
* until it comes back — the id stays written, untouched.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Sketch: every rule demoted to `audit`. Findings still reach `violations$` —
|
|
15
|
+
* a host panel and a conformance report see them — and the canvas says nothing.
|
|
16
|
+
*
|
|
17
|
+
* The DEFAULT, and deliberately so (PRD principle 3). A context map is drawn in
|
|
18
|
+
* a workshop, at speed, with half the links pointing the wrong way while
|
|
19
|
+
* somebody remembers which team is upstream; a tool arguing with that hand is a
|
|
20
|
+
* tool switched off within the hour. Being the default also means it WRITES
|
|
21
|
+
* NOTHING: a board on `sketch` carries no profile key, so every map ever drawn
|
|
22
|
+
* is on it, with no migration and no backfill.
|
|
23
|
+
*/
|
|
24
|
+
const sketch = {
|
|
25
|
+
id: 'context-map.sketch',
|
|
26
|
+
framework: 'ddd-context-map',
|
|
27
|
+
labelKey: 'com.labre.ddd-context-map.profile.sketch',
|
|
28
|
+
fallback: 'Sketch',
|
|
29
|
+
isDefault: true,
|
|
30
|
+
rules: {
|
|
31
|
+
'context-map.relationship-endpoints': 'audit',
|
|
32
|
+
'context-map.acl-conformist-exclusive': 'audit',
|
|
33
|
+
'context-map.pattern-on-customer-supplier': 'audit',
|
|
34
|
+
'context-map.acl-on-customer-supplier': 'audit',
|
|
35
|
+
'context-map.context-off-board': 'audit',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Strict: the deliverable level — a map that leaves the room. Every severity is
|
|
40
|
+
* spelled out rather than left absent, so the answer is readable in one place.
|
|
41
|
+
*
|
|
42
|
+
* ## The one that does not move
|
|
43
|
+
*
|
|
44
|
+
* `acl-on-customer-supplier` stays `audit` HERE TOO, and it is the reason a
|
|
45
|
+
* profile spells everything out. It is not a softer version of a contradiction,
|
|
46
|
+
* it is a different KIND of statement: the combination is legitimate — a team
|
|
47
|
+
* negotiating its needs upstream while still translating a model that is being
|
|
48
|
+
* retired — and only the team knows which case they are in. Promoting it with
|
|
49
|
+
* the others would turn a good question into a false accusation on every mature
|
|
50
|
+
* map in the corpus.
|
|
51
|
+
*
|
|
52
|
+
* Nothing here is `blocking-overridable`: nothing in this library implements
|
|
53
|
+
* refusal, and declaring it would be data claiming an effect that does not
|
|
54
|
+
* exist.
|
|
55
|
+
*/
|
|
56
|
+
const strict = {
|
|
57
|
+
id: 'context-map.strict',
|
|
58
|
+
framework: 'ddd-context-map',
|
|
59
|
+
labelKey: 'com.labre.ddd-context-map.profile.strict',
|
|
60
|
+
fallback: 'Strict',
|
|
61
|
+
rules: {
|
|
62
|
+
'context-map.relationship-endpoints': 'warning',
|
|
63
|
+
'context-map.acl-conformist-exclusive': 'warning',
|
|
64
|
+
'context-map.pattern-on-customer-supplier': 'warning',
|
|
65
|
+
// See the header: a judgement the map cannot make at any level.
|
|
66
|
+
'context-map.acl-on-customer-supplier': 'audit',
|
|
67
|
+
'context-map.context-off-board': 'warning',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export const CONTEXT_MAP_PROFILES = [
|
|
71
|
+
sketch,
|
|
72
|
+
strict,
|
|
73
|
+
];
|
package/dist/roles.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CM_RELATIONSHIPS } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import type { RoleDefs } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
/**
|
|
4
|
+
* Context Mapping role vocabulary (WS2).
|
|
5
|
+
*
|
|
6
|
+
* Roles are the semantic identity of a Context Map artefact — no rule will ever
|
|
7
|
+
* look at a shape type. Three families:
|
|
8
|
+
*
|
|
9
|
+
* - the **board** (`context-map:board`), the white card the map is drawn on;
|
|
10
|
+
* - the **bounded context** (`context-map:context`), the blue pill;
|
|
11
|
+
* - the **relationship** (`context-map:relationship`), the edge parent, with
|
|
12
|
+
* one child per DDD pattern.
|
|
13
|
+
*
|
|
14
|
+
* ## One role per pattern, and why
|
|
15
|
+
*
|
|
16
|
+
* The PO arbitration of 26/08/2026: a relationship is not a connector with a
|
|
17
|
+
* `pattern` property, it is nine different edges. Two reasons, both of them
|
|
18
|
+
* about what the rules can then say. A pattern-per-role means a rule is written
|
|
19
|
+
* on the pattern it is about (`acl` and `conformist` may not coexist) without
|
|
20
|
+
* the engine growing a way to read a property; and it means a combination the
|
|
21
|
+
* notation genuinely allows — C/S plus OHS plus PL on the same couple, which is
|
|
22
|
+
* DDD Crew's own example — is drawn as PARALLEL LINKS, each a statement of its
|
|
23
|
+
* own, rather than as a bag of flags nobody can point at.
|
|
24
|
+
*
|
|
25
|
+
* Hierarchy is DATA (`parent`): every pattern specialises
|
|
26
|
+
* `context-map:relationship`, so a rule written on the parent — the endpoint
|
|
27
|
+
* grammar, the self-loop ban — covers all nine for free (see `roleIsA`).
|
|
28
|
+
*
|
|
29
|
+
* ## `direction` only on the U/D patterns
|
|
30
|
+
*
|
|
31
|
+
* Tier 1 of `docs/adr/0010` says an edge's source is the subject of its verb.
|
|
32
|
+
* That only MEANS something for the five upstream/downstream patterns, where
|
|
33
|
+
* the notation itself has a direction: the source is the upstream context. The
|
|
34
|
+
* four symmetric ones (Partnership, Shared Kernel, Separate Ways, Big Ball of
|
|
35
|
+
* Mud) relate two contexts as equals — declaring a verb on them would announce a
|
|
36
|
+
* gesture ("drag from the upstream one") that means nothing, and the reveal (M2)
|
|
37
|
+
* would show a sentence the framework does not say. They therefore declare none,
|
|
38
|
+
* which is exactly what an untyped edge already did.
|
|
39
|
+
*
|
|
40
|
+
* ## Compat
|
|
41
|
+
*
|
|
42
|
+
* Nothing is backfilled. Relationships drawn before these roles existed — the
|
|
43
|
+
* old free connector + tag + U/D markers group — carry no role and are never
|
|
44
|
+
* evaluated (promesse #71): they stay drawings, in the documents they are in.
|
|
45
|
+
*/
|
|
46
|
+
/** The nine DDD Crew patterns, by the `kind` used at the creation sites. */
|
|
47
|
+
export type ContextMapPatternKind = (typeof CM_RELATIONSHIPS)[number]['kind'];
|
|
48
|
+
export type ContextMapRoleId = `context-map:${string}`;
|
|
49
|
+
export declare const CM_PATTERN_ROLE: Record<ContextMapPatternKind, ContextMapRoleId>;
|
|
50
|
+
/** The three roles that are not a pattern, plus the pattern ids. */
|
|
51
|
+
export declare const CONTEXT_MAP_ROLE: {
|
|
52
|
+
readonly partnership: `context-map:${string}`;
|
|
53
|
+
readonly sharedKernel: `context-map:${string}`;
|
|
54
|
+
readonly customerSupplier: `context-map:${string}`;
|
|
55
|
+
readonly conformist: `context-map:${string}`;
|
|
56
|
+
readonly acl: `context-map:${string}`;
|
|
57
|
+
readonly ohs: `context-map:${string}`;
|
|
58
|
+
readonly publishedLanguage: `context-map:${string}`;
|
|
59
|
+
readonly separateWays: `context-map:${string}`;
|
|
60
|
+
readonly bbom: `context-map:${string}`;
|
|
61
|
+
readonly board: "context-map:board";
|
|
62
|
+
readonly context: "context-map:context";
|
|
63
|
+
readonly relationship: "context-map:relationship";
|
|
64
|
+
};
|
|
65
|
+
export declare const CONTEXT_MAP_ROLES: RoleDefs;
|
package/dist/roles.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CM_RELATIONSHIPS } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
/**
|
|
3
|
+
* Role id per pattern kind, DERIVED from the shared preset table rather than
|
|
4
|
+
* restated: the kinds are camelCase (`sharedKernel`) and the role ids are
|
|
5
|
+
* kebab-case (`context-map:shared-kernel`), and the day a tenth pattern lands in
|
|
6
|
+
* `CM_RELATIONSHIPS` it gets its role here with no edit.
|
|
7
|
+
*/
|
|
8
|
+
const kebab = (kind) => kind.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
9
|
+
export const CM_PATTERN_ROLE = Object.fromEntries(CM_RELATIONSHIPS.map(preset => [
|
|
10
|
+
preset.kind,
|
|
11
|
+
`context-map:${kebab(preset.kind)}`,
|
|
12
|
+
]));
|
|
13
|
+
/** The three roles that are not a pattern, plus the pattern ids. */
|
|
14
|
+
export const CONTEXT_MAP_ROLE = {
|
|
15
|
+
board: 'context-map:board',
|
|
16
|
+
context: 'context-map:context',
|
|
17
|
+
relationship: 'context-map:relationship',
|
|
18
|
+
...CM_PATTERN_ROLE,
|
|
19
|
+
};
|
|
20
|
+
/** The DDD Crew wording, verbatim: `Upstream` is the source of the relation. */
|
|
21
|
+
const UPSTREAM_DIRECTION = {
|
|
22
|
+
verbKey: 'com.labre.ddd-context-map.role.relationship.verb',
|
|
23
|
+
verbFallback: 'is upstream of',
|
|
24
|
+
gestureHintKey: 'com.labre.ddd-context-map.role.relationship.gesture',
|
|
25
|
+
gestureHintFallback: 'Drag from the upstream context to the downstream one.',
|
|
26
|
+
};
|
|
27
|
+
const DEFS = [
|
|
28
|
+
// The board: a frame, not a context. It specialises nothing, so a rule
|
|
29
|
+
// written on `context-map:context` can never match the card its subjects
|
|
30
|
+
// sit on.
|
|
31
|
+
{
|
|
32
|
+
id: CONTEXT_MAP_ROLE.board,
|
|
33
|
+
kind: 'node',
|
|
34
|
+
labelKey: 'com.labre.ddd-context-map.role.board',
|
|
35
|
+
labelFallback: 'Context map',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: CONTEXT_MAP_ROLE.context,
|
|
39
|
+
kind: 'node',
|
|
40
|
+
labelKey: 'com.labre.ddd-context-map.role.context',
|
|
41
|
+
labelFallback: 'Bounded context',
|
|
42
|
+
},
|
|
43
|
+
// The edge parent. Carries no `direction` of its own: five of its nine
|
|
44
|
+
// children have one and four do not, and a verb on the parent would be
|
|
45
|
+
// inherited by the symmetric patterns that deny having one.
|
|
46
|
+
{
|
|
47
|
+
id: CONTEXT_MAP_ROLE.relationship,
|
|
48
|
+
kind: 'edge',
|
|
49
|
+
labelKey: 'com.labre.ddd-context-map.role.relationship',
|
|
50
|
+
labelFallback: 'Relationship',
|
|
51
|
+
},
|
|
52
|
+
...CM_RELATIONSHIPS.map((preset) => ({
|
|
53
|
+
id: CM_PATTERN_ROLE[preset.kind],
|
|
54
|
+
parent: CONTEXT_MAP_ROLE.relationship,
|
|
55
|
+
kind: 'edge',
|
|
56
|
+
labelKey: `com.labre.ddd-context-map.role.${kebab(preset.kind)}`,
|
|
57
|
+
labelFallback: preset.label,
|
|
58
|
+
// See the header: only the upstream/downstream patterns have a subject.
|
|
59
|
+
...(preset.upDown ? { direction: UPSTREAM_DIRECTION } : {}),
|
|
60
|
+
})),
|
|
61
|
+
];
|
|
62
|
+
// Null prototype: this is a lookup table keyed by ids that may one day come
|
|
63
|
+
// from host-supplied packs, so `defs['toString']` must not resolve.
|
|
64
|
+
export const CONTEXT_MAP_ROLES = Object.assign(Object.create(null), Object.fromEntries(DEFS.map(def => [def.id, def])));
|
package/dist/rules.d.ts
ADDED
package/dist/rules.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { CONTEXT_MAP_BACKGROUND } from './background.js';
|
|
2
|
+
import { CONTEXT_MAP_ROLE, CONTEXT_MAP_ROLES } from './roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* Context Mapping validation rules (WS2).
|
|
5
|
+
*
|
|
6
|
+
* DATA owned by the framework, versioned per rule: the engine
|
|
7
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
8
|
+
* concrete rule. Registered from the flag-gated `DddContextMapViewExtension`,
|
|
9
|
+
* so switching the Context Map flag off removes them with the rest of the
|
|
10
|
+
* tooling — maps already drawn keep rendering, they simply stop being checked
|
|
11
|
+
* (`docs/adr/0009`).
|
|
12
|
+
*
|
|
13
|
+
* ## What the grammar of a context map actually is
|
|
14
|
+
*
|
|
15
|
+
* Very little, and that is the point. A context map has no axes: nothing about
|
|
16
|
+
* WHERE a context sits means anything, so there is no order rule, no zone rule
|
|
17
|
+
* and no orientation rule to write. What it does have is a notation with
|
|
18
|
+
* CONTRADICTIONS in it — patterns that cannot both be true of the same couple —
|
|
19
|
+
* and those are exactly what the `relation-endpoints` family arbitrates.
|
|
20
|
+
*
|
|
21
|
+
* Four of the five rules below are therefore one family with four different
|
|
22
|
+
* questions, and every one of them stays silent on a sketch: an end that is not
|
|
23
|
+
* a bounded context (a cloud, a note, a plain rectangle) is outside the
|
|
24
|
+
* alphabet, and the whole edge goes unjudged.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* **CM1** — a relationship runs between two bounded contexts, once, and never
|
|
28
|
+
* onto itself.
|
|
29
|
+
*
|
|
30
|
+
* The base grammar, written on the PARENT edge role, which is what makes it one
|
|
31
|
+
* rule instead of nine: `roleIsA` resolves a triplet declared on
|
|
32
|
+
* `context-map:relationship` for every pattern that specialises it, so the
|
|
33
|
+
* single sanctioned sentence below — a context relates to a context — covers
|
|
34
|
+
* Partnership and Big Ball of Mud alike, and a tenth pattern inherits it on the
|
|
35
|
+
* day it is added.
|
|
36
|
+
*
|
|
37
|
+
* The two things it says:
|
|
38
|
+
*
|
|
39
|
+
* - **self-loop** — a context is not related to itself. Whatever the hand meant,
|
|
40
|
+
* the notation has no sentence for it;
|
|
41
|
+
* - **duplicate** — the same pattern drawn twice between the same two contexts,
|
|
42
|
+
* the same way round. Not two DIFFERENT patterns, which is legitimate and how
|
|
43
|
+
* C/S + OHS + PL is drawn; the same one, twice, which is one link too many on
|
|
44
|
+
* a map that is read by counting links.
|
|
45
|
+
*
|
|
46
|
+
* ## What `allowed` does here, and what it deliberately does NOT
|
|
47
|
+
*
|
|
48
|
+
* The matrix holds ONE sentence, and `context-map:context` is therefore the
|
|
49
|
+
* whole alphabet of this rule. That makes the off-matrix branch structurally
|
|
50
|
+
* unreachable — an end that is a context passes, and an end that is anything
|
|
51
|
+
* else is outside the alphabet, which is TOTAL silence for the edge (see
|
|
52
|
+
* {@link RelationEndpointsDef.allowed}).
|
|
53
|
+
*
|
|
54
|
+
* That is the intent, not an oversight. The matrix is here to declare the
|
|
55
|
+
* alphabet, and the alphabet is what makes the whole rule proportionate: a
|
|
56
|
+
* relationship drawn onto a CLOUD, onto a note, onto a rectangle somebody
|
|
57
|
+
* dropped on the board to think with, is a draft — the user is saying "there is
|
|
58
|
+
* something out there we integrate with", which on a context map is not merely
|
|
59
|
+
* tolerable but the correct notation. Judging it would be indicting the act of
|
|
60
|
+
* sketching (PRD principle 8).
|
|
61
|
+
*
|
|
62
|
+
* The branch becomes reachable the day a second NODE role lands — a role on the
|
|
63
|
+
* cloud, say — and on that day this declaration already says what to do with it
|
|
64
|
+
* without a line changing. Until then `offMatrix` carries no words of its own
|
|
65
|
+
* and the rule's own sentence stands behind it, unused.
|
|
66
|
+
*/
|
|
67
|
+
const relationshipEndpoints = {
|
|
68
|
+
id: 'context-map.relationship-endpoints',
|
|
69
|
+
framework: 'ddd-context-map',
|
|
70
|
+
family: 'relation-endpoints',
|
|
71
|
+
severity: 'warning',
|
|
72
|
+
// No `appliesTo`: the subject is a RELATION, and the role that names it is
|
|
73
|
+
// declared where the family reads it — naming one of the three indicted
|
|
74
|
+
// elements here would be data that lies.
|
|
75
|
+
roles: CONTEXT_MAP_ROLES,
|
|
76
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-endpoints',
|
|
77
|
+
messageFallback: 'This relationship does not run between two bounded contexts.',
|
|
78
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-endpoints.suggestion',
|
|
79
|
+
suggestionFallback: 'A context map relates bounded contexts — re-point the loose end onto one, or delete the link.',
|
|
80
|
+
version: 1,
|
|
81
|
+
provenance: {
|
|
82
|
+
source: 'recommendation',
|
|
83
|
+
reference: 'Context Mapping (DDD) — the relationship grammar of the notation',
|
|
84
|
+
},
|
|
85
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
86
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
87
|
+
endpoints: {
|
|
88
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
89
|
+
allowed: [
|
|
90
|
+
{
|
|
91
|
+
source: CONTEXT_MAP_ROLE.context,
|
|
92
|
+
edge: CONTEXT_MAP_ROLE.relationship,
|
|
93
|
+
target: CONTEXT_MAP_ROLE.context,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
forbidSelfLoop: true,
|
|
97
|
+
forbidDuplicate: true,
|
|
98
|
+
selfLoop: {
|
|
99
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-self-loop',
|
|
100
|
+
messageFallback: 'This relationship loops back onto its own context.',
|
|
101
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-self-loop.suggestion',
|
|
102
|
+
suggestionFallback: 'A bounded context integrates with OTHER contexts — drop the loose end on the context it actually relates to, or delete the link.',
|
|
103
|
+
},
|
|
104
|
+
duplicate: {
|
|
105
|
+
messageKey: 'com.labre.ddd-context-map.validation.relationship-duplicate',
|
|
106
|
+
messageFallback: 'This pattern is already drawn between these two contexts.',
|
|
107
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.relationship-duplicate.suggestion',
|
|
108
|
+
suggestionFallback: 'Delete the copy. Two DIFFERENT patterns on one couple are fine — C/S with OHS and PL is the notation working — but the same one twice says nothing new.',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* **CM2** — a context does not both conform to its upstream and protect itself
|
|
114
|
+
* from it.
|
|
115
|
+
*
|
|
116
|
+
* The textbook contradiction, and the one every context map workshop produces
|
|
117
|
+
* at least once. Conformist means "we take their model as it is"; an
|
|
118
|
+
* Anticorruption Layer means "we translate their model into ours because we
|
|
119
|
+
* refuse to take it as it is". Both are legitimate answers to the same pressure
|
|
120
|
+
* and they are opposite answers, so a couple carrying both has not decided —
|
|
121
|
+
* which is the finding.
|
|
122
|
+
*
|
|
123
|
+
* A `warning` and never a refusal: the map is a discussion aid, and the pair
|
|
124
|
+
* showing up on it is usually the moment the discussion starts. The user
|
|
125
|
+
* resolves it by deleting one, and only they know which.
|
|
126
|
+
*/
|
|
127
|
+
const aclConformistExclusive = {
|
|
128
|
+
id: 'context-map.acl-conformist-exclusive',
|
|
129
|
+
framework: 'ddd-context-map',
|
|
130
|
+
family: 'relation-endpoints',
|
|
131
|
+
severity: 'warning',
|
|
132
|
+
roles: CONTEXT_MAP_ROLES,
|
|
133
|
+
messageKey: 'com.labre.ddd-context-map.validation.acl-conformist',
|
|
134
|
+
messageFallback: 'These two contexts are related by both a Conformist and an Anticorruption Layer.',
|
|
135
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.acl-conformist.suggestion',
|
|
136
|
+
suggestionFallback: 'Conforming means taking the upstream model as it is; an ACL means refusing to. Keep the one the team actually does.',
|
|
137
|
+
version: 1,
|
|
138
|
+
provenance: {
|
|
139
|
+
source: 'recommendation',
|
|
140
|
+
reference: 'Context Mapping (DDD) — the pattern canon: an anticorruption layer and a conformist are opposite answers',
|
|
141
|
+
},
|
|
142
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
143
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
144
|
+
endpoints: {
|
|
145
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
146
|
+
// No `allowed`: CM1 already judges the sentences, and repeating the matrix
|
|
147
|
+
// here would report one mistake twice. This rule asks the pair question and
|
|
148
|
+
// nothing else.
|
|
149
|
+
exclusivePairs: [[CONTEXT_MAP_ROLE.acl, CONTEXT_MAP_ROLE.conformist]],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* **CM3** — Customer/Supplier already says how the two teams work; Conformist
|
|
154
|
+
* and Open Host Service say something else.
|
|
155
|
+
*
|
|
156
|
+
* C/S is a RELATIONSHIP OF NEGOTIATION: downstream is a customer whose needs
|
|
157
|
+
* enter the upstream team's backlog. Conformist is its negation — downstream
|
|
158
|
+
* has no say and takes what comes. OHS is a different answer again: upstream
|
|
159
|
+
* serves all comers through a published protocol precisely so it does NOT have
|
|
160
|
+
* to negotiate per consumer. Either one drawn on top of a C/S says the couple
|
|
161
|
+
* has not settled which conversation it is having.
|
|
162
|
+
*
|
|
163
|
+
* ACL is NOT in this pair list, on purpose — see {@link aclOnCustomerSupplier},
|
|
164
|
+
* where it is an audit finding rather than a warning.
|
|
165
|
+
*/
|
|
166
|
+
const patternOnCustomerSupplier = {
|
|
167
|
+
id: 'context-map.pattern-on-customer-supplier',
|
|
168
|
+
framework: 'ddd-context-map',
|
|
169
|
+
family: 'relation-endpoints',
|
|
170
|
+
severity: 'warning',
|
|
171
|
+
roles: CONTEXT_MAP_ROLES,
|
|
172
|
+
messageKey: 'com.labre.ddd-context-map.validation.pattern-on-customer-supplier',
|
|
173
|
+
messageFallback: 'This Customer/Supplier relationship carries a second pattern that contradicts it.',
|
|
174
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.pattern-on-customer-supplier.suggestion',
|
|
175
|
+
suggestionFallback: 'Customer/Supplier means the downstream needs are negotiated into the upstream backlog. Conformist says they are not; Open Host Service says the upstream serves everyone the same way. Keep one.',
|
|
176
|
+
version: 1,
|
|
177
|
+
provenance: {
|
|
178
|
+
source: 'recommendation',
|
|
179
|
+
reference: 'Context Mapping (DDD) — Customer/Supplier is a relationship of negotiation',
|
|
180
|
+
},
|
|
181
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
182
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
183
|
+
endpoints: {
|
|
184
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
185
|
+
exclusivePairs: [
|
|
186
|
+
[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.conformist],
|
|
187
|
+
[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.ohs],
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* **CM4** — an Anticorruption Layer on a Customer/Supplier is worth a second
|
|
193
|
+
* look, and nothing more.
|
|
194
|
+
*
|
|
195
|
+
* Deliberately `audit`, alone among the four. The combination is not wrong: a
|
|
196
|
+
* downstream team can perfectly well negotiate its needs upstream AND still
|
|
197
|
+
* translate the upstream model at its border, and mature teams do exactly that
|
|
198
|
+
* while a legacy model is being retired. But it is also, often, the trace of a
|
|
199
|
+
* negotiation that has stopped working — the customer built a translation layer
|
|
200
|
+
* because the supplier stopped listening.
|
|
201
|
+
*
|
|
202
|
+
* A tool cannot tell those two apart, so it does not get to interrupt: the
|
|
203
|
+
* finding reaches `violations$` for a host panel and a conformance report, and
|
|
204
|
+
* the canvas says nothing. `strict` leaves it at `audit` too, which is the whole
|
|
205
|
+
* reason a profile spells every severity out — this one is a judgement the map
|
|
206
|
+
* cannot make at any level of requirement.
|
|
207
|
+
*/
|
|
208
|
+
const aclOnCustomerSupplier = {
|
|
209
|
+
id: 'context-map.acl-on-customer-supplier',
|
|
210
|
+
framework: 'ddd-context-map',
|
|
211
|
+
family: 'relation-endpoints',
|
|
212
|
+
severity: 'audit',
|
|
213
|
+
roles: CONTEXT_MAP_ROLES,
|
|
214
|
+
messageKey: 'com.labre.ddd-context-map.validation.acl-on-customer-supplier',
|
|
215
|
+
messageFallback: 'This Customer/Supplier relationship also carries an Anticorruption Layer.',
|
|
216
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.acl-on-customer-supplier.suggestion',
|
|
217
|
+
suggestionFallback: 'Legitimate while a model is being retired — worth asking whether the negotiation still works if it is not.',
|
|
218
|
+
version: 1,
|
|
219
|
+
provenance: {
|
|
220
|
+
source: 'labre-convention',
|
|
221
|
+
reference: 'Labre convention — the notation forbids nothing here; a tool cannot tell the two readings apart',
|
|
222
|
+
},
|
|
223
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
224
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
225
|
+
endpoints: {
|
|
226
|
+
edgeRole: CONTEXT_MAP_ROLE.relationship,
|
|
227
|
+
exclusivePairs: [[CONTEXT_MAP_ROLE.customerSupplier, CONTEXT_MAP_ROLE.acl]],
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* **CM5** — a bounded context belongs on the map.
|
|
232
|
+
*
|
|
233
|
+
* The one geometric rule the framework has, and it is about membership rather
|
|
234
|
+
* than position: a context parked beside the card is not on the map, so nothing
|
|
235
|
+
* the map says covers it. Silent when the board carries no board element at all
|
|
236
|
+
* — a context map sketched on the bare canvas is a sketch, not an error, and so
|
|
237
|
+
* is one drawn before the board existed.
|
|
238
|
+
*/
|
|
239
|
+
const contextOffBoard = {
|
|
240
|
+
id: 'context-map.context-off-board',
|
|
241
|
+
framework: 'ddd-context-map',
|
|
242
|
+
family: 'element-in-background',
|
|
243
|
+
severity: 'warning',
|
|
244
|
+
appliesTo: CONTEXT_MAP_ROLE.context,
|
|
245
|
+
roles: CONTEXT_MAP_ROLES,
|
|
246
|
+
messageKey: 'com.labre.ddd-context-map.validation.context-off-board',
|
|
247
|
+
messageFallback: 'This bounded context sits outside the map.',
|
|
248
|
+
suggestionKey: 'com.labre.ddd-context-map.validation.context-off-board.suggestion',
|
|
249
|
+
suggestionFallback: 'Drag it onto the board, or grow the board to take it in.',
|
|
250
|
+
version: 1,
|
|
251
|
+
provenance: {
|
|
252
|
+
source: 'labre-convention',
|
|
253
|
+
reference: 'Labre convention — membership on this canvas, not a Context Mapping rule',
|
|
254
|
+
},
|
|
255
|
+
backgroundRole: CONTEXT_MAP_ROLE.board,
|
|
256
|
+
background: CONTEXT_MAP_BACKGROUND,
|
|
257
|
+
};
|
|
258
|
+
export const CONTEXT_MAP_RULES = [
|
|
259
|
+
relationshipEndpoints,
|
|
260
|
+
aclConformistExclusive,
|
|
261
|
+
patternOnCustomerSupplier,
|
|
262
|
+
aclOnCustomerSupplier,
|
|
263
|
+
contextOffBoard,
|
|
264
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ToolbarContext } from '@formicoidea/labre-core/shared/services';
|
|
2
|
+
/**
|
|
3
|
+
* The selected board's contextual toolbar: the resize toggle, and the automatic
|
|
4
|
+
* legend of what is actually drawn on the board. Registered ALWAYS-ON
|
|
5
|
+
* (`DddContextMapRenderViewExtension`) — a stored board must stay usable with
|
|
6
|
+
* the Context Map button switched off (`docs/adr/0009`), legend included: a
|
|
7
|
+
* legend is real editable elements, so generating one is authoring a document,
|
|
8
|
+
* not tooling that a flag may take away.
|
|
9
|
+
*
|
|
10
|
+
* This is THE legend of the module. The palette used to also offer a static,
|
|
11
|
+
* full-notation one; the PO's recette (27/08/2026) removed it — two legends were
|
|
12
|
+
* two answers to one question, and the honest answer is what is actually drawn.
|
|
13
|
+
* Same single-gesture shape as Core Domain Chart.
|
|
14
|
+
*/
|
|
15
|
+
export declare const contextMapBoardToolbarConfig: {
|
|
16
|
+
readonly actions: [{
|
|
17
|
+
readonly id: "a.toggle-resize";
|
|
18
|
+
readonly tooltip: "Enable / lock resizing";
|
|
19
|
+
readonly icon: import("lit-html").TemplateResult<1>;
|
|
20
|
+
readonly active: (ctx: ToolbarContext) => boolean;
|
|
21
|
+
readonly run: (ctx: ToolbarContext) => void;
|
|
22
|
+
}, {
|
|
23
|
+
readonly id: "b.legend";
|
|
24
|
+
readonly tooltip: "Generate the legend (notation present)";
|
|
25
|
+
readonly icon: import("lit-html").TemplateResult<1>;
|
|
26
|
+
readonly run: (ctx: ToolbarContext) => void;
|
|
27
|
+
}];
|
|
28
|
+
readonly when: (ctx: ToolbarContext) => boolean;
|
|
29
|
+
};
|
|
30
|
+
export declare const contextMapBoardToolbarExtension: import("@formicoidea/labre-core/store").ExtensionType;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { EdgelessCRUDIdentifier } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { createAutoLegend, dddLegendIcon } from '@formicoidea/labre-ddd-shared';
|
|
3
|
+
import { ContextMapBoardElementModel } from '@formicoidea/labre-core/model';
|
|
4
|
+
import { TelemetryProvider, ToolbarModuleExtension, } from '@formicoidea/labre-core/shared/services';
|
|
5
|
+
import { BlockFlavourIdentifier } from '@formicoidea/labre-core/std';
|
|
6
|
+
import { html } from 'lit';
|
|
7
|
+
import { CONTEXT_MAP_AUTO_LEGEND } from '../legend.js';
|
|
8
|
+
const ResizeIcon = html `<svg
|
|
9
|
+
width="24"
|
|
10
|
+
height="24"
|
|
11
|
+
viewBox="0 0 24 24"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
stroke-width="1.6"
|
|
15
|
+
stroke-linecap="round"
|
|
16
|
+
stroke-linejoin="round"
|
|
17
|
+
>
|
|
18
|
+
<path d="M9 5H5v4M15 19h4v-4" />
|
|
19
|
+
<path d="M5 5l6 6M19 19l-6-6" />
|
|
20
|
+
</svg>`;
|
|
21
|
+
/**
|
|
22
|
+
* The selected board's contextual toolbar: the resize toggle, and the automatic
|
|
23
|
+
* legend of what is actually drawn on the board. Registered ALWAYS-ON
|
|
24
|
+
* (`DddContextMapRenderViewExtension`) — a stored board must stay usable with
|
|
25
|
+
* the Context Map button switched off (`docs/adr/0009`), legend included: a
|
|
26
|
+
* legend is real editable elements, so generating one is authoring a document,
|
|
27
|
+
* not tooling that a flag may take away.
|
|
28
|
+
*
|
|
29
|
+
* This is THE legend of the module. The palette used to also offer a static,
|
|
30
|
+
* full-notation one; the PO's recette (27/08/2026) removed it — two legends were
|
|
31
|
+
* two answers to one question, and the honest answer is what is actually drawn.
|
|
32
|
+
* Same single-gesture shape as Core Domain Chart.
|
|
33
|
+
*/
|
|
34
|
+
export const contextMapBoardToolbarConfig = {
|
|
35
|
+
actions: [
|
|
36
|
+
{
|
|
37
|
+
id: 'a.toggle-resize',
|
|
38
|
+
tooltip: 'Enable / lock resizing',
|
|
39
|
+
icon: ResizeIcon,
|
|
40
|
+
active(ctx) {
|
|
41
|
+
const models = ctx.getSurfaceModelsByType(ContextMapBoardElementModel);
|
|
42
|
+
return models.length > 0 && models.every(model => model.resizeEnabled);
|
|
43
|
+
},
|
|
44
|
+
run(ctx) {
|
|
45
|
+
const models = ctx.getSurfaceModelsByType(ContextMapBoardElementModel);
|
|
46
|
+
if (!models.length)
|
|
47
|
+
return;
|
|
48
|
+
const enable = !models.every(model => model.resizeEnabled);
|
|
49
|
+
ctx.std.store.captureSync();
|
|
50
|
+
const crud = ctx.std.get(EdgelessCRUDIdentifier);
|
|
51
|
+
for (const model of models) {
|
|
52
|
+
crud.updateElement(model.id, { resizeEnabled: enable });
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'b.legend',
|
|
58
|
+
tooltip: 'Generate the legend (notation present)',
|
|
59
|
+
icon: dddLegendIcon,
|
|
60
|
+
run(ctx) {
|
|
61
|
+
const board = ctx.getSurfaceModelsByType(ContextMapBoardElementModel)[0];
|
|
62
|
+
if (!board)
|
|
63
|
+
return;
|
|
64
|
+
createAutoLegend(ctx.std, board, CONTEXT_MAP_AUTO_LEGEND);
|
|
65
|
+
ctx.std
|
|
66
|
+
.getOptional(TelemetryProvider)
|
|
67
|
+
?.track('FrameworkLegendCreated', {
|
|
68
|
+
// The WIRE value, which is not the module id: the framework is
|
|
69
|
+
// `ddd-context-map` in code and `context-map` in PostHog
|
|
70
|
+
// (`frameworks.ts` `telemetryKey`, and the only value
|
|
71
|
+
// `FrameworkElementEvent` accepts). Same convention as Wardley's own
|
|
72
|
+
// legend button, so the two are comparable.
|
|
73
|
+
framework: 'context-map',
|
|
74
|
+
element: 'legend',
|
|
75
|
+
page: 'whiteboard editor',
|
|
76
|
+
segment: 'element toolbar',
|
|
77
|
+
module: 'context-map toolbar',
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
when: (ctx) => ctx.getSurfaceModelsByType(ContextMapBoardElementModel).length > 0,
|
|
83
|
+
};
|
|
84
|
+
export const contextMapBoardToolbarExtension = ToolbarModuleExtension({
|
|
85
|
+
id: BlockFlavourIdentifier('affine:surface:contextMap'),
|
|
86
|
+
config: contextMapBoardToolbarConfig,
|
|
87
|
+
});
|
|
@@ -3,7 +3,7 @@ import { EdgelessCommandMenu } from '@formicoidea/labre-core/widgets/edgeless-to
|
|
|
3
3
|
/**
|
|
4
4
|
* Context Map palette, rendered from `contextMapCommands` for the
|
|
5
5
|
* `senior-menu` surface (`docs/adr/0008`). Keeps the DDD palettes' tighter gap
|
|
6
|
-
* — it is the widest one, at
|
|
6
|
+
* — it is the widest one, at 13 buttons.
|
|
7
7
|
*/
|
|
8
8
|
export declare class EdgelessDddContextMapMenu extends EdgelessCommandMenu {
|
|
9
9
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -4,7 +4,7 @@ import { css } from 'lit';
|
|
|
4
4
|
/**
|
|
5
5
|
* Context Map palette, rendered from `contextMapCommands` for the
|
|
6
6
|
* `senior-menu` surface (`docs/adr/0008`). Keeps the DDD palettes' tighter gap
|
|
7
|
-
* — it is the widest one, at
|
|
7
|
+
* — it is the widest one, at 13 buttons.
|
|
8
8
|
*/
|
|
9
9
|
export class EdgelessDddContextMapMenu extends EdgelessCommandMenu {
|
|
10
10
|
constructor() {
|
|
@@ -2,5 +2,6 @@ import { DddSeniorButtonBase } from '@formicoidea/labre-ddd-shared';
|
|
|
2
2
|
export declare class EdgelessDddContextMapSeniorButton extends DddSeniorButtonBase {
|
|
3
3
|
protected menuTag: "edgeless-ddd-context-map-menu";
|
|
4
4
|
protected label: string;
|
|
5
|
+
protected labelKey: string;
|
|
5
6
|
protected icon: import("lit-html").TemplateResult<2>;
|
|
6
7
|
}
|
|
@@ -4,6 +4,7 @@ export class EdgelessDddContextMapSeniorButton extends DddSeniorButtonBase {
|
|
|
4
4
|
super(...arguments);
|
|
5
5
|
this.menuTag = 'edgeless-ddd-context-map-menu';
|
|
6
6
|
this.label = 'Context Map';
|
|
7
|
+
this.labelKey = 'com.labre.framework.ddd-context-map';
|
|
7
8
|
this.icon = contextMapToolbarIcon;
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -3,6 +3,7 @@ import { html } from 'lit';
|
|
|
3
3
|
/** Independent senior tool — Context Map. */
|
|
4
4
|
export const contextMapSeniorTool = SeniorToolExtension('ddd-context-map', ({ block }) => ({
|
|
5
5
|
name: 'Context Map',
|
|
6
|
+
labelKey: 'com.labre.framework.ddd-context-map',
|
|
6
7
|
content: html `<edgeless-ddd-context-map-senior-button
|
|
7
8
|
.edgeless=${block}
|
|
8
9
|
></edgeless-ddd-context-map-senior-button>`,
|
package/dist/translations.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { type TranslationKeyManifestEntry } from '@formicoidea/labre-core/std';
|
|
2
2
|
/**
|
|
3
|
-
* THIS framework's contribution to the translation-key manifest
|
|
3
|
+
* THIS framework's contribution to the translation-key manifest — every
|
|
4
|
+
* `com.labre.*` key Context Mapping can hand to `TranslationProvider.t`,
|
|
5
|
+
* derived from the very declarations the editor registers (never restated).
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* core
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `contextMapCommands` into the command registry. See
|
|
12
|
-
* `packages/affine/all/src/translations.ts`.
|
|
7
|
+
* It lives HERE, not in `@labre/affine/translations`, because the bundled
|
|
8
|
+
* distribution splits the library along exactly this line:
|
|
9
|
+
* `@formicoidea/labre-core` is the editor MINUS the frameworks, and a host that
|
|
10
|
+
* installs `@formicoidea/labre-framework-ddd-context-map` composes this export
|
|
11
|
+
* into the catalogue it builds — the same sentence that already holds for
|
|
12
|
+
* `contextMapCommands`. See `packages/affine/all/src/translations.ts`.
|
|
13
13
|
*/
|
|
14
14
|
export declare const contextMapTranslationEntries: TranslationKeyManifestEntry[];
|