@formicoidea/labre-framework-edgy 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 +18 -0
- package/dist/actions.js +55 -2
- package/dist/board-renderer.js +4 -1
- package/dist/commands.js +21 -3
- package/dist/element-renderer.js +5 -2
- package/dist/label-layout.js +8 -2
- package/dist/legend.d.ts +2 -0
- package/dist/legend.js +109 -0
- package/dist/metamodel.d.ts +96 -0
- package/dist/metamodel.js +128 -0
- package/dist/nudges.d.ts +26 -0
- package/dist/nudges.js +54 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +66 -0
- package/dist/relation-resolver.d.ts +32 -0
- package/dist/relation-resolver.js +101 -0
- package/dist/relation.d.ts +142 -0
- package/dist/relation.js +188 -0
- package/dist/roles.d.ts +108 -0
- package/dist/roles.js +182 -0
- package/dist/rules.d.ts +43 -0
- package/dist/rules.js +153 -0
- package/dist/templates/index.d.ts +5 -13
- package/dist/templates/index.js +210 -107
- package/dist/toolbar/config.d.ts +10 -0
- package/dist/toolbar/config.js +38 -1
- package/dist/toolbar/edgy-senior-button.js +8 -2
- package/dist/toolbar/icons.d.ts +8 -0
- package/dist/toolbar/icons.js +13 -0
- package/dist/toolbar/senior-tool.js +1 -0
- package/dist/translations.d.ts +17 -7
- package/dist/translations.js +23 -9
- package/dist/view.d.ts +5 -2
- package/dist/view.js +54 -5
- package/package.json +6 -3
package/dist/roles.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, edgyElementLabel, } from './metamodel.js';
|
|
2
|
+
import { NODE_LABEL } from './node/consts.js';
|
|
3
|
+
/** Role ids, keyed by the name used at the creation sites. */
|
|
4
|
+
export const EDGY_ROLE = {
|
|
5
|
+
element: 'edgy:element',
|
|
6
|
+
// The four persisted kinds (`EdgyNodeKind`).
|
|
7
|
+
people: 'edgy:people',
|
|
8
|
+
outcome: 'edgy:outcome',
|
|
9
|
+
object: 'edgy:object',
|
|
10
|
+
activity: 'edgy:activity',
|
|
11
|
+
// The twelve official elements.
|
|
12
|
+
purpose: 'edgy:purpose',
|
|
13
|
+
capability: 'edgy:capability',
|
|
14
|
+
task: 'edgy:task',
|
|
15
|
+
story: 'edgy:story',
|
|
16
|
+
process: 'edgy:process',
|
|
17
|
+
journey: 'edgy:journey',
|
|
18
|
+
content: 'edgy:content',
|
|
19
|
+
asset: 'edgy:asset',
|
|
20
|
+
channel: 'edgy:channel',
|
|
21
|
+
organisation: 'edgy:organisation',
|
|
22
|
+
product: 'edgy:product',
|
|
23
|
+
brand: 'edgy:brand',
|
|
24
|
+
// The two frames.
|
|
25
|
+
background: 'edgy:background',
|
|
26
|
+
facets: 'edgy:facets',
|
|
27
|
+
board: 'edgy:board',
|
|
28
|
+
// The parent of the 22 verbs.
|
|
29
|
+
relation: 'edgy:relation',
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A canonical verb, as a role id: `is part of` → `edgy:is-part-of`.
|
|
33
|
+
*
|
|
34
|
+
* The verb IS the identifier, so a relation added to the metamodel gets its
|
|
35
|
+
* role for free and nobody has to remember to name it. No collision with the
|
|
36
|
+
* element roles above is possible in the metamodel as it stands, and a future
|
|
37
|
+
* verb colliding with an element name would be caught by the unit spec, which
|
|
38
|
+
* counts the vocabulary.
|
|
39
|
+
*/
|
|
40
|
+
function verbRoleId(verb) {
|
|
41
|
+
return `edgy:${verb.replace(/\s+/g, '-')}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Role id per canonical verb, DERIVED from {@link EDGY_DYNAMIC_RELATIONS}.
|
|
45
|
+
*
|
|
46
|
+
* Derived rather than restated: the metamodel is written once (`./metamodel.ts`)
|
|
47
|
+
* and the template that draws it, the vocabulary that names it and the rule that
|
|
48
|
+
* checks it all read the same table. `requires` appears on three rows and gets
|
|
49
|
+
* ONE role — a verb is a verb wherever it is spoken, and the three sentences it
|
|
50
|
+
* belongs to are three triplets, not three roles.
|
|
51
|
+
*/
|
|
52
|
+
export const EDGY_VERB_ROLE = Object.assign(Object.create(null), Object.fromEntries(EDGY_DYNAMIC_RELATIONS.map(([, , verb]) => [verb, verbRoleId(verb)])));
|
|
53
|
+
/**
|
|
54
|
+
* Key of an ORDERED pair of element roles: `edgy:process edgy:capability`.
|
|
55
|
+
*
|
|
56
|
+
* A SPACE separates them, and not `-`, `:` or `>`: a role id already contains a
|
|
57
|
+
* colon and may contain a dash (`edgy:is-part-of`), and a separator that can
|
|
58
|
+
* occur inside a value is a lookup table with collisions waiting in it. No role
|
|
59
|
+
* id has ever contained whitespace — `verbRoleId` above replaces it on the one
|
|
60
|
+
* family that could.
|
|
61
|
+
*/
|
|
62
|
+
export const edgyPairKey = (source, target) => `${source} ${target}`;
|
|
63
|
+
/**
|
|
64
|
+
* Ordered pair of ELEMENT roles → the canonical verb the metamodel gives it,
|
|
65
|
+
* DERIVED from {@link EDGY_DYNAMIC_RELATIONS}.
|
|
66
|
+
*
|
|
67
|
+
* The metamodel's 24 rows are 24 DISTINCT ordered pairs, so the verb of a
|
|
68
|
+
* relation is entirely determined by which two elements it runs between: there
|
|
69
|
+
* is never a choice to offer. That is what lets the toolbox ship ONE "Relation"
|
|
70
|
+
* entry instead of twenty-two, and what `./relation.ts` reads to name a
|
|
71
|
+
* hand-drawn link after the pair the user attached it to.
|
|
72
|
+
*
|
|
73
|
+
* Derived, never restated — same contract as {@link EDGY_VERB_ROLE}: a relation
|
|
74
|
+
* added to `./metamodel.ts` becomes nameable by hand without anybody editing
|
|
75
|
+
* this file.
|
|
76
|
+
*/
|
|
77
|
+
export const EDGY_PAIR_TO_VERB = Object.assign(Object.create(null), Object.fromEntries(EDGY_DYNAMIC_RELATIONS.map(([source, target, verb]) => [
|
|
78
|
+
edgyPairKey(EDGY_ROLE[source], EDGY_ROLE[target]),
|
|
79
|
+
verb,
|
|
80
|
+
])));
|
|
81
|
+
/** i18n key stem of a role id: `edgy:is-part-of` → `com.labre.edgy.role.is-part-of`. */
|
|
82
|
+
const roleKey = (id) => `com.labre.edgy.role.${id.slice('edgy:'.length)}`;
|
|
83
|
+
const ELEMENT_DEFS = [
|
|
84
|
+
// The root of every artefact a board is made of. A rule written here covers
|
|
85
|
+
// the four kinds and the twelve elements at once — which is exactly what
|
|
86
|
+
// `edgy.overlapping-artefacts` needs and why the root exists.
|
|
87
|
+
{
|
|
88
|
+
id: EDGY_ROLE.element,
|
|
89
|
+
kind: 'node',
|
|
90
|
+
labelKey: roleKey(EDGY_ROLE.element),
|
|
91
|
+
labelFallback: 'Element',
|
|
92
|
+
},
|
|
93
|
+
// The four PERSISTED kinds. They are the base shapes the toolbox offers, so
|
|
94
|
+
// they are what an element created from the palette carries: somebody
|
|
95
|
+
// dropping an "Object" on the board has said "object" and nothing more, and
|
|
96
|
+
// the role says exactly that much.
|
|
97
|
+
//
|
|
98
|
+
// The fallback is the wording the palette itself writes inside the shape
|
|
99
|
+
// (`node/consts.ts`), so a reader who meets the role in a legend or a
|
|
100
|
+
// direction reveal meets the word they dropped on the board.
|
|
101
|
+
...['people', 'outcome', 'object', 'activity'].map(kind => ({
|
|
102
|
+
id: EDGY_ROLE[kind],
|
|
103
|
+
parent: EDGY_ROLE.element,
|
|
104
|
+
kind: 'node',
|
|
105
|
+
labelKey: roleKey(EDGY_ROLE[kind]),
|
|
106
|
+
labelFallback: NODE_LABEL[kind],
|
|
107
|
+
})),
|
|
108
|
+
// The twelve official elements, each under the kind the metamodel draws it
|
|
109
|
+
// with — Purpose is an outcome, Story an activity, Channel an object. The
|
|
110
|
+
// parent is READ from `EDGY_DYNAMIC_NODES` rather than restated: the diagram
|
|
111
|
+
// and the vocabulary cannot drift apart if there is only one table. So is the
|
|
112
|
+
// fallback wording, from the same table's own names.
|
|
113
|
+
...Object.entries(EDGY_DYNAMIC_NODES).map(([name, { kind }]) => ({
|
|
114
|
+
id: EDGY_ROLE[name],
|
|
115
|
+
parent: EDGY_ROLE[kind],
|
|
116
|
+
kind: 'node',
|
|
117
|
+
labelKey: roleKey(EDGY_ROLE[name]),
|
|
118
|
+
labelFallback: edgyElementLabel(name),
|
|
119
|
+
})),
|
|
120
|
+
];
|
|
121
|
+
const BACKGROUND_DEFS = [
|
|
122
|
+
// The frame, and deliberately NOT a child of `edgy:element`: a rule written
|
|
123
|
+
// on the artefacts must never match the board they are drawn on. Two frames
|
|
124
|
+
// specialise it — the facets diagram and the blank board — so a rule
|
|
125
|
+
// attributes its findings to whichever one the user is working on without
|
|
126
|
+
// naming either.
|
|
127
|
+
{
|
|
128
|
+
id: EDGY_ROLE.background,
|
|
129
|
+
kind: 'node',
|
|
130
|
+
labelKey: roleKey(EDGY_ROLE.background),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: EDGY_ROLE.facets,
|
|
134
|
+
parent: EDGY_ROLE.background,
|
|
135
|
+
kind: 'node',
|
|
136
|
+
labelKey: roleKey(EDGY_ROLE.facets),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: EDGY_ROLE.board,
|
|
140
|
+
parent: EDGY_ROLE.background,
|
|
141
|
+
kind: 'node',
|
|
142
|
+
labelKey: roleKey(EDGY_ROLE.board),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
/**
|
|
146
|
+
* The relations: the parent, then one child per canonical verb.
|
|
147
|
+
*
|
|
148
|
+
* The parent carries no `direction` — it names no verb, so it has no sentence
|
|
149
|
+
* to state. Every child does, and the gesture hint is derived from the verb for
|
|
150
|
+
* the same reason the id is: "drag from the X to the Y" is the same sentence
|
|
151
|
+
* with the same two holes for all 22 of them, and writing it out 22 times would
|
|
152
|
+
* be 22 chances to write it out wrong.
|
|
153
|
+
*/
|
|
154
|
+
const RELATION_DEFS = [
|
|
155
|
+
{
|
|
156
|
+
id: EDGY_ROLE.relation,
|
|
157
|
+
kind: 'edge',
|
|
158
|
+
labelKey: roleKey(EDGY_ROLE.relation),
|
|
159
|
+
labelFallback: 'Relation',
|
|
160
|
+
},
|
|
161
|
+
...Object.entries(EDGY_VERB_ROLE).map(([verb, id]) => ({
|
|
162
|
+
id,
|
|
163
|
+
parent: EDGY_ROLE.relation,
|
|
164
|
+
kind: 'edge',
|
|
165
|
+
labelKey: roleKey(id),
|
|
166
|
+
labelFallback: verb.charAt(0).toUpperCase() + verb.slice(1),
|
|
167
|
+
direction: {
|
|
168
|
+
verbKey: `${roleKey(id)}.verb`,
|
|
169
|
+
verbFallback: verb,
|
|
170
|
+
gestureHintKey: `${roleKey(id)}.gesture`,
|
|
171
|
+
gestureHintFallback: `Drag from the element that is the subject of "${verb}" to its object.`,
|
|
172
|
+
},
|
|
173
|
+
})),
|
|
174
|
+
];
|
|
175
|
+
const DEFS = [
|
|
176
|
+
...ELEMENT_DEFS,
|
|
177
|
+
...BACKGROUND_DEFS,
|
|
178
|
+
...RELATION_DEFS,
|
|
179
|
+
];
|
|
180
|
+
// Null prototype: this is a lookup table keyed by ids that may one day come
|
|
181
|
+
// from host-supplied packs, so `defs['toString']` must not resolve.
|
|
182
|
+
export const EDGY_ROLES = Object.assign(Object.create(null), Object.fromEntries(DEFS.map(def => [def.id, def])));
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { EndpointTriplet, ValidationRule } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
/**
|
|
3
|
+
* EDGY validation rules (WS1).
|
|
4
|
+
*
|
|
5
|
+
* DATA owned by the framework, versioned per rule: the engine
|
|
6
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
7
|
+
* concrete rule. Adding an EDGY rule is adding an entry to this array.
|
|
8
|
+
*
|
|
9
|
+
* Registered from the flag-gated `EdgyViewExtension`, so switching the EDGY
|
|
10
|
+
* flag off removes the rules with the rest of the tooling — boards already
|
|
11
|
+
* drawn keep rendering, they simply stop being checked (`docs/adr/0009`).
|
|
12
|
+
*
|
|
13
|
+
* ## Two rules, and the ones deliberately left out
|
|
14
|
+
*
|
|
15
|
+
* The PO arbitration of 26/08/2026 put EDGY's two JUDGEMENT controls — "every
|
|
16
|
+
* intersection element is linked to both its parent facets" and "every element
|
|
17
|
+
* wears its facet's colour" — in `./nudges.ts` rather than here. Neither is
|
|
18
|
+
* decidable: the first would have to guess which circle an element belongs to
|
|
19
|
+
* from where somebody dropped it, and the second would indict a board whose
|
|
20
|
+
* author uses their own palette. A checklist can ask them; an algorithm cannot
|
|
21
|
+
* answer them.
|
|
22
|
+
*
|
|
23
|
+
* ## Debt: `tree-mixed-kinds`
|
|
24
|
+
*
|
|
25
|
+
* EDGY's own notation has TREES — a capability tree, an organisation tree —
|
|
26
|
+
* whose levels must all be the same kind of element. There is no tree on this
|
|
27
|
+
* canvas yet: nothing draws one, nothing stores one, and no role names one. The
|
|
28
|
+
* rule is therefore deferred rather than written against an artefact that does
|
|
29
|
+
* not exist. When a tree lands, it comes with its own role and this is where its
|
|
30
|
+
* rule goes.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* The 24 sanctioned sentences, DERIVED from the metamodel.
|
|
34
|
+
*
|
|
35
|
+
* One triplet per row of `EDGY_DYNAMIC_RELATIONS`, read exactly as
|
|
36
|
+
* `docs/adr/0010` reads a typed edge: source is the subject, target the object.
|
|
37
|
+
* The matrix is never restated — a relation added to the metamodel is drawn by
|
|
38
|
+
* the template, gets a role from `roles.ts` and becomes legal here, all from the
|
|
39
|
+
* one table. `requires` contributes three triplets under one role, which is what
|
|
40
|
+
* it means for a verb to be spoken of three different pairs.
|
|
41
|
+
*/
|
|
42
|
+
export declare const EDGY_ALLOWED_RELATIONS: readonly EndpointTriplet[];
|
|
43
|
+
export declare const EDGY_RULES: readonly ValidationRule[];
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { EDGY_DYNAMIC_RELATIONS } from './metamodel.js';
|
|
2
|
+
import { EDGY_ROLE, EDGY_ROLES, EDGY_VERB_ROLE, } from './roles.js';
|
|
3
|
+
/**
|
|
4
|
+
* EDGY validation rules (WS1).
|
|
5
|
+
*
|
|
6
|
+
* DATA owned by the framework, versioned per rule: the engine
|
|
7
|
+
* (`@labre/affine-block-surface`) knows how to evaluate a FAMILY, never a
|
|
8
|
+
* concrete rule. Adding an EDGY rule is adding an entry to this array.
|
|
9
|
+
*
|
|
10
|
+
* Registered from the flag-gated `EdgyViewExtension`, so switching the EDGY
|
|
11
|
+
* flag off removes the rules with the rest of the tooling — boards already
|
|
12
|
+
* drawn keep rendering, they simply stop being checked (`docs/adr/0009`).
|
|
13
|
+
*
|
|
14
|
+
* ## Two rules, and the ones deliberately left out
|
|
15
|
+
*
|
|
16
|
+
* The PO arbitration of 26/08/2026 put EDGY's two JUDGEMENT controls — "every
|
|
17
|
+
* intersection element is linked to both its parent facets" and "every element
|
|
18
|
+
* wears its facet's colour" — in `./nudges.ts` rather than here. Neither is
|
|
19
|
+
* decidable: the first would have to guess which circle an element belongs to
|
|
20
|
+
* from where somebody dropped it, and the second would indict a board whose
|
|
21
|
+
* author uses their own palette. A checklist can ask them; an algorithm cannot
|
|
22
|
+
* answer them.
|
|
23
|
+
*
|
|
24
|
+
* ## Debt: `tree-mixed-kinds`
|
|
25
|
+
*
|
|
26
|
+
* EDGY's own notation has TREES — a capability tree, an organisation tree —
|
|
27
|
+
* whose levels must all be the same kind of element. There is no tree on this
|
|
28
|
+
* canvas yet: nothing draws one, nothing stores one, and no role names one. The
|
|
29
|
+
* rule is therefore deferred rather than written against an artefact that does
|
|
30
|
+
* not exist. When a tree lands, it comes with its own role and this is where its
|
|
31
|
+
* rule goes.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The 24 sanctioned sentences, DERIVED from the metamodel.
|
|
35
|
+
*
|
|
36
|
+
* One triplet per row of `EDGY_DYNAMIC_RELATIONS`, read exactly as
|
|
37
|
+
* `docs/adr/0010` reads a typed edge: source is the subject, target the object.
|
|
38
|
+
* The matrix is never restated — a relation added to the metamodel is drawn by
|
|
39
|
+
* the template, gets a role from `roles.ts` and becomes legal here, all from the
|
|
40
|
+
* one table. `requires` contributes three triplets under one role, which is what
|
|
41
|
+
* it means for a verb to be spoken of three different pairs.
|
|
42
|
+
*/
|
|
43
|
+
export const EDGY_ALLOWED_RELATIONS = EDGY_DYNAMIC_RELATIONS.map(([source, target, verb]) => ({
|
|
44
|
+
source: EDGY_ROLE[source],
|
|
45
|
+
edge: EDGY_VERB_ROLE[verb],
|
|
46
|
+
target: EDGY_ROLE[target],
|
|
47
|
+
}));
|
|
48
|
+
/**
|
|
49
|
+
* **E1** — a relation says something the EDGY metamodel does not.
|
|
50
|
+
*
|
|
51
|
+
* EDGY is a language before it is a diagram: its 24 relations are the sentences
|
|
52
|
+
* the method sanctions, and a link drawn between the wrong two elements — or
|
|
53
|
+
* drawn the right way round between the right two but carrying the wrong verb —
|
|
54
|
+
* is a modelling mistake no amount of dragging fixes. "A journey traverses a
|
|
55
|
+
* channel" is EDGY; "a channel traverses a journey" is a sentence nobody can
|
|
56
|
+
* act on.
|
|
57
|
+
*
|
|
58
|
+
* ## What stays silent
|
|
59
|
+
*
|
|
60
|
+
* Everything the `relation-endpoints` family already keeps quiet about, and one
|
|
61
|
+
* of them matters more here than anywhere: **a plain connector carries no role
|
|
62
|
+
* and is never judged**. Free links are how an EDGY workshop actually runs —
|
|
63
|
+
* somebody draws an arrow to say "these two have something to do with each
|
|
64
|
+
* other" and names it later — and the day the tool starts indicting that
|
|
65
|
+
* gesture is the day it gets switched off. Only the typed relations the toolbox
|
|
66
|
+
* and the templates stamp are read, and only when BOTH ends are official
|
|
67
|
+
* elements: a relation onto a bare sticky, onto a People node or onto an
|
|
68
|
+
* element of another framework is outside the alphabet, hence outside the
|
|
69
|
+
* conversation.
|
|
70
|
+
*
|
|
71
|
+
* Severity `warning` and not `blocking-overridable`: nothing in this library
|
|
72
|
+
* refuses a gesture, so declaring the blocking level would be data claiming an
|
|
73
|
+
* effect that does not exist (the note `wardley/rules.ts` carries at length).
|
|
74
|
+
* The profiles demote it to `audit` on a sketch.
|
|
75
|
+
*/
|
|
76
|
+
const nonCanonicalLink = {
|
|
77
|
+
id: 'edgy.non-canonical-link',
|
|
78
|
+
framework: 'edgy',
|
|
79
|
+
family: 'relation-endpoints',
|
|
80
|
+
severity: 'warning',
|
|
81
|
+
// No `appliesTo`: the subject is a RELATION, and the role that names it is
|
|
82
|
+
// declared where the family reads it — naming one of the three indicted
|
|
83
|
+
// elements here would be data that lies.
|
|
84
|
+
roles: EDGY_ROLES,
|
|
85
|
+
messageKey: 'com.labre.edgy.validation.non-canonical-link',
|
|
86
|
+
messageFallback: 'This relation is not one the EDGY metamodel declares between these two elements.',
|
|
87
|
+
suggestionKey: 'com.labre.edgy.validation.non-canonical-link.suggestion',
|
|
88
|
+
suggestionFallback: 'Read the link out loud — source, verb, target. Reverse it, re-point an end, or use the verb EDGY gives these two elements.',
|
|
89
|
+
version: 1,
|
|
90
|
+
provenance: {
|
|
91
|
+
source: 'recommendation',
|
|
92
|
+
reference: 'EDGY (Enterprise Design) — the 24 relations its metamodel sanctions',
|
|
93
|
+
},
|
|
94
|
+
// Not a frame the rule measures against — a sentence is right or wrong
|
|
95
|
+
// wherever it is written — but the facets diagram or board a finding is
|
|
96
|
+
// ATTRIBUTED to, so the arbitration "ignore this rule on the whole board" has
|
|
97
|
+
// somewhere to live (the `no-overlap` pattern).
|
|
98
|
+
backgroundRole: EDGY_ROLE.background,
|
|
99
|
+
endpoints: {
|
|
100
|
+
// The PARENT role: every verb specialises it, so the rule reads all 22 of
|
|
101
|
+
// them and no free connector.
|
|
102
|
+
edgeRole: EDGY_ROLE.relation,
|
|
103
|
+
allowed: EDGY_ALLOWED_RELATIONS,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* **E2** — two artefacts must not sit on top of each other.
|
|
108
|
+
*
|
|
109
|
+
* A readability rule, the same one Wardley ships and for the same reason: a
|
|
110
|
+
* board you cannot read is useless, while a momentary overlap during a drag is
|
|
111
|
+
* not a mistake. It matters more on an EDGY facets diagram than almost
|
|
112
|
+
* anywhere, because WHERE an element sits is what says which facet it belongs
|
|
113
|
+
* to: an artefact hidden under another has lost its meaning, not just its
|
|
114
|
+
* legibility.
|
|
115
|
+
*
|
|
116
|
+
* ONE declared pair, `element × element`, because `roleIsA` covers the whole
|
|
117
|
+
* subtree — the four kinds and the twelve official elements — from the root.
|
|
118
|
+
* Backgrounds are outside that subtree by construction, so an element sitting on
|
|
119
|
+
* the diagram it belongs to is never a collision.
|
|
120
|
+
*
|
|
121
|
+
* `minPenetration: 4` model units, the calibration Wardley's W3 arrived at on
|
|
122
|
+
* the recette of 01/08/2026: under a twentieth of an EDGY node's 80-unit height
|
|
123
|
+
* and about twice a connector's stroke, so two boxes whose corners share a hair
|
|
124
|
+
* are silent while a box genuinely covering another is not. Verified against
|
|
125
|
+
* every layout this package ships: the twelve nodes of the EDGY dynamic
|
|
126
|
+
* template — the only stamped layout, hence the only one this rule can see —
|
|
127
|
+
* clear each other by 80 model units at the closest, twenty times the
|
|
128
|
+
* threshold.
|
|
129
|
+
*/
|
|
130
|
+
const overlappingArtefacts = {
|
|
131
|
+
id: 'edgy.overlapping-artefacts',
|
|
132
|
+
framework: 'edgy',
|
|
133
|
+
family: 'no-overlap',
|
|
134
|
+
severity: 'warning',
|
|
135
|
+
// No `appliesTo`: the subjects of a `no-overlap` rule are the pairs below.
|
|
136
|
+
roles: EDGY_ROLES,
|
|
137
|
+
messageKey: 'com.labre.edgy.validation.overlapping-artefacts',
|
|
138
|
+
messageFallback: 'These two overlap and make the board harder to read.',
|
|
139
|
+
suggestionKey: 'com.labre.edgy.validation.overlapping-artefacts.suggestion',
|
|
140
|
+
suggestionFallback: 'Move one of them aside — on a facets diagram, where an element sits is what says which facet it belongs to.',
|
|
141
|
+
version: 1,
|
|
142
|
+
provenance: {
|
|
143
|
+
source: 'labre-convention',
|
|
144
|
+
reference: 'Labre readability convention — no EDGY rule speaks of overlapping ink',
|
|
145
|
+
},
|
|
146
|
+
backgroundRole: EDGY_ROLE.background,
|
|
147
|
+
overlap: [[EDGY_ROLE.element, EDGY_ROLE.element]],
|
|
148
|
+
minPenetration: 4,
|
|
149
|
+
};
|
|
150
|
+
export const EDGY_RULES = [
|
|
151
|
+
nonCanonicalLink,
|
|
152
|
+
overlappingArtefacts,
|
|
153
|
+
];
|
|
@@ -6,20 +6,12 @@ import { type Template, type TemplateCategory } from '@formicoidea/labre-core/gf
|
|
|
6
6
|
*/
|
|
7
7
|
export declare const DYN_SCALE = 4.8;
|
|
8
8
|
/**
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* (`labelOffset.distance` ≈ .75), short peer links keep the middle.
|
|
9
|
+
* The metamodel itself — the 12 elements and the 24 relations — now lives in
|
|
10
|
+
* `../metamodel.ts`, so the role vocabulary can DERIVE from it without this
|
|
11
|
+
* module and that one importing each other. Re-exported here under the names
|
|
12
|
+
* they have always had: nothing that reads them had to change.
|
|
14
13
|
*/
|
|
15
|
-
export
|
|
16
|
-
export declare const EDGY_DYNAMIC_NODES: Record<string, {
|
|
17
|
-
kind: 'outcome' | 'object' | 'activity';
|
|
18
|
-
cx: number;
|
|
19
|
-
cy: number;
|
|
20
|
-
w?: number;
|
|
21
|
-
fill: string;
|
|
22
|
-
}>;
|
|
14
|
+
export { EDGY_DYNAMIC_NODES, EDGY_DYNAMIC_RELATIONS, type EdgyElementName, } from '../metamodel.js';
|
|
23
15
|
/**
|
|
24
16
|
* Reference coords → template model coords: the background element sits at
|
|
25
17
|
* (0,0) and renders the cropped circles box, so a reference point maps to
|