@formicoidea/labre-framework-c4 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.
Files changed (61) hide show
  1. package/dist/actions.d.ts +179 -0
  2. package/dist/actions.js +375 -0
  3. package/dist/background.d.ts +77 -0
  4. package/dist/background.js +223 -0
  5. package/dist/commands.d.ts +4 -0
  6. package/dist/commands.js +221 -0
  7. package/dist/component.d.ts +192 -0
  8. package/dist/component.js +188 -0
  9. package/dist/consts.d.ts +331 -0
  10. package/dist/consts.js +384 -0
  11. package/dist/descriptor.d.ts +12 -0
  12. package/dist/descriptor.js +10 -0
  13. package/dist/effects.d.ts +9 -0
  14. package/dist/effects.js +6 -0
  15. package/dist/element-renderer.d.ts +18 -0
  16. package/dist/element-renderer.js +14 -0
  17. package/dist/element-view.d.ts +51 -0
  18. package/dist/element-view.js +146 -0
  19. package/dist/export.d.ts +184 -0
  20. package/dist/export.js +454 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.js +52 -0
  23. package/dist/interchange.d.ts +74 -0
  24. package/dist/interchange.js +143 -0
  25. package/dist/legend.d.ts +37 -0
  26. package/dist/legend.js +123 -0
  27. package/dist/levels.d.ts +70 -0
  28. package/dist/levels.js +46 -0
  29. package/dist/morph.d.ts +89 -0
  30. package/dist/morph.js +229 -0
  31. package/dist/node/node-renderer.d.ts +6 -0
  32. package/dist/node/node-renderer.js +304 -0
  33. package/dist/node/node-view.d.ts +45 -0
  34. package/dist/node/node-view.js +80 -0
  35. package/dist/node/type-line-watcher.d.ts +70 -0
  36. package/dist/node/type-line-watcher.js +142 -0
  37. package/dist/presets.d.ts +84 -0
  38. package/dist/presets.js +149 -0
  39. package/dist/profiles.d.ts +2 -0
  40. package/dist/profiles.js +177 -0
  41. package/dist/roles.d.ts +116 -0
  42. package/dist/roles.js +303 -0
  43. package/dist/rules.d.ts +95 -0
  44. package/dist/rules.js +1261 -0
  45. package/dist/toolbar/c4-menu.d.ts +11 -0
  46. package/dist/toolbar/c4-menu.js +14 -0
  47. package/dist/toolbar/c4-senior-button.d.ts +19 -0
  48. package/dist/toolbar/c4-senior-button.js +23 -0
  49. package/dist/toolbar/config.d.ts +150 -0
  50. package/dist/toolbar/config.js +436 -0
  51. package/dist/toolbar/icons.d.ts +90 -0
  52. package/dist/toolbar/icons.js +157 -0
  53. package/dist/toolbar/senior-tool.d.ts +1 -0
  54. package/dist/toolbar/senior-tool.js +11 -0
  55. package/dist/translations.d.ts +18 -0
  56. package/dist/translations.js +42 -0
  57. package/dist/type-line.d.ts +175 -0
  58. package/dist/type-line.js +244 -0
  59. package/dist/view.d.ts +33 -0
  60. package/dist/view.js +148 -0
  61. package/package.json +34 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The middle tier of a C4 element's label — `[Person]`, `[Software System]`,
3
+ * `[Container: Java]` — SEMI-DERIVED: the word comes from the kind, the
4
+ * technology from the author.
5
+ *
6
+ * ## Semi-derived, and where the line actually lives
7
+ *
8
+ * The line is a real canvas TEXT element now (PO recette, 28/08/2026): a C4
9
+ * component is the shape and its own words, grouped, and every tier is edited in
10
+ * place like any other text on the canvas rather than typed into a form. So the
11
+ * string IS stored — but only half of it is the author's. The bracketed word is
12
+ * the notation's and comes from `kind`, which is what the renderer paints and
13
+ * what the exporter maps; letting an author retype it would be letting the words
14
+ * disagree with the picture.
15
+ *
16
+ * {@link normalizeC4TypeLine} is what keeps the halves apart. It runs when an
17
+ * edit COMMITS, reads whatever was typed for the one thing the notation leaves
18
+ * to the author — the technology — and writes the canonical line back. Type
19
+ * `Java` and the line becomes `[Container: Java]`; type `[Database: Java]` on a
20
+ * cylinder and it becomes `[Container: Java]`, because a database IS a container
21
+ * and the stencil says so.
22
+ */
23
+ /**
24
+ * The base word each kind is announced by, verbatim from the stencil's own
25
+ * `<desc>` strings (`C4Model_default.svg`, the PO's reference model).
26
+ *
27
+ * Two readings are worth spelling out, because both look like mistakes:
28
+ *
29
+ * - `database` says **Container**, not "Database". The stencil labels the
30
+ * cylinder `[Container: technology]` exactly as it labels the plain box: a
31
+ * database is a CONTAINER, and the cylinder is a picture of one, not a fourth
32
+ * level. (The mermaid export still emits `ContainerDb` — that is the one
33
+ * specialisation mermaid's own grammar draws, and it is a different question
34
+ * from what the box says on the canvas.)
35
+ * - an `-ext` variant says the same word as the kind it is external to. What
36
+ * "external" changes is the COLOUR — grey — and the stencil's gray sheet
37
+ * carries the identical `[Person]` / `[Software System]` wording.
38
+ *
39
+ * `Record<C4NodeKind, …>` and therefore compile-total: a kind added to the model
40
+ * without a word to announce it fails the build here.
41
+ */
42
+ export const C4_TYPE_WORD = {
43
+ person: 'Person',
44
+ 'person-ext': 'Person',
45
+ system: 'Software System',
46
+ 'system-ext': 'Software System',
47
+ container: 'Container',
48
+ database: 'Container',
49
+ mobile: 'Container',
50
+ browser: 'Container',
51
+ component: 'Component',
52
+ };
53
+ /**
54
+ * The type line as it is drawn, brackets included.
55
+ *
56
+ * The technology is appended only when the author actually set one — an empty
57
+ * box, or one holding nothing but spaces, is not a technology and must not
58
+ * produce a dangling `[Container: ]`. Whitespace runs collapse for the same
59
+ * reason they do in the mermaid sanitizer: the line is one line.
60
+ *
61
+ * Pure, total and `std`-free, so the renderer, the exporter and a test can all
62
+ * ask the same question and get the same answer.
63
+ */
64
+ export function c4TypeLine(kind, technology) {
65
+ const word = C4_TYPE_WORD[kind];
66
+ const techn = (technology ?? '').replaceAll(/\s+/g, ' ').trim();
67
+ return techn ? `[${word}: ${techn}]` : `[${word}]`;
68
+ }
69
+ /* ── Reading the line back ─────────────────────────────────────────────── */
70
+ /**
71
+ * The type words, lowercased — the vocabulary a `<word>:` prefix is recognised
72
+ * against, and the whole reason {@link technologyOfTypeLine} does not simply cut
73
+ * at the first colon.
74
+ *
75
+ * Derived from {@link C4_TYPE_WORD} rather than restated, so a word added to the
76
+ * notation is understood on the way back in the moment it can be written out.
77
+ *
78
+ * Cutting at the first colon unconditionally was the obvious implementation and
79
+ * it is wrong on the one input an architect is most likely to paste: a URL. `an
80
+ * author typing "https://internal/docs" as their technology would have it read
81
+ * as the prefix `https` and keep `//internal/docs`. Matching the VOCABULARY
82
+ * instead can only ever strip a word the notation itself writes.
83
+ */
84
+ const KNOWN_TYPE_WORDS = new Set(Object.values(C4_TYPE_WORD).map(word => word.toLowerCase()));
85
+ /**
86
+ * The technology an author stated in a type line, whatever shape they left it
87
+ * in — `''` when they stated none.
88
+ *
89
+ * Total over every string, which is what it has to be: this reads a canvas TEXT
90
+ * element somebody edited in place, so its input is not a field with a grammar,
91
+ * it is whatever was typed. Every one of these is a line this has met:
92
+ *
93
+ * ```
94
+ * [Container: Java] → Java the canonical form
95
+ * Container: Java → Java brackets deleted while editing
96
+ * Java → Java the whole line selected and retyped
97
+ * [Container] → '' the word alone: no technology stated
98
+ * [Container: ] → '' the technology deleted, brackets kept
99
+ * https://x/docs → https://x/docs a colon that is NOT a prefix
100
+ * ```
101
+ *
102
+ * Pure and `std`-free, so the commit hook, the exporter and a test all read the
103
+ * same line the same way.
104
+ *
105
+ * It deliberately does NOT know about the creation placeholder: a node created
106
+ * and never touched carries `[Container: technology]`, and reading that as "no
107
+ * technology" here would make a focus-and-blur silently rewrite it to
108
+ * `[Container]` — the normalizer would eat the stencil's own prompt. The
109
+ * placeholder is a question for whoever asks what the element STATES, which is
110
+ * the exporter, and it is answered there.
111
+ */
112
+ export function technologyOfTypeLine(text) {
113
+ const flat = (text ?? '')
114
+ .replaceAll(/[[\]]/g, ' ')
115
+ .replaceAll(/\s+/g, ' ')
116
+ .trim();
117
+ const colon = flat.indexOf(':');
118
+ // No colon at all: the line is either the bare notation word — which states no
119
+ // technology — or it is the technology, typed on its own.
120
+ if (colon < 0)
121
+ return KNOWN_TYPE_WORDS.has(flat.toLowerCase()) ? '' : flat;
122
+ const head = flat.slice(0, colon).trim().toLowerCase();
123
+ if (!KNOWN_TYPE_WORDS.has(head))
124
+ return flat;
125
+ return flat.slice(colon + 1).trim();
126
+ }
127
+ /**
128
+ * A type line as it must be STORED, whatever was typed into it.
129
+ *
130
+ * The semi-derived rule of the PO's recette, in one function: read the one half
131
+ * the author owns, write the whole line back from the kind. `[Person: Java]`
132
+ * typed on a container becomes `[Container: Java]`, a bare `Java` becomes
133
+ * `[Container: Java]`, and an emptied line becomes `[Container]`.
134
+ *
135
+ * Idempotent by construction — `normalize(normalize(x)) === normalize(x)` —
136
+ * which is what lets it run on every commit without a guard.
137
+ */
138
+ export function normalizeC4TypeLine(kind, rawText) {
139
+ return c4TypeLine(kind, technologyOfTypeLine(rawText));
140
+ }
141
+ /* ── What a fresh element says before anybody writes on it ─────────────── */
142
+ /**
143
+ * The word a fresh type line prompts the author with, in the technology's slot.
144
+ *
145
+ * The stencil's own: its container reads `[Container: technology]` before
146
+ * anybody fills it in. A prompt rather than a value — see
147
+ * {@link C4_TYPE_PLACEHOLDER}.
148
+ */
149
+ export const TYPE_TECHNOLOGY_PLACEHOLDER = 'technology';
150
+ /**
151
+ * Which kinds announce a technology in their type line AT ALL.
152
+ *
153
+ * The notation's own division and not mermaid's, though the two agree: a person
154
+ * is not built with a technology and a software system's is a level down, so
155
+ * both are written `[Person]` / `[Software System]` full stop. Every container
156
+ * — the cylinder, the phone and the browser window included — and every
157
+ * component carries `[Word: technology]`, because at those two levels "what is
158
+ * it built with" is the question the diagram exists to answer.
159
+ *
160
+ * This decides the PLACEHOLDER only. An author who types a technology onto a
161
+ * person gets it drawn, exactly as `c4TypeLine` has always drawn it: the
162
+ * notation is a prompt here, not a validator.
163
+ */
164
+ export const C4_TYPE_TAKES_TECHNOLOGY = {
165
+ person: false,
166
+ 'person-ext': false,
167
+ system: false,
168
+ 'system-ext': false,
169
+ container: true,
170
+ database: true,
171
+ mobile: true,
172
+ browser: true,
173
+ component: true,
174
+ };
175
+ /**
176
+ * The type line a freshly created element is born carrying, per kind.
177
+ *
178
+ * Every tier of a C4 component exists from the moment it is drawn (PO
179
+ * arbitration, 28/08/2026), so the author meets three lines of stencil rather
180
+ * than a box and two invisible slots they have to be told about. What they meet
181
+ * is what the official stencil shows: `[Person]`, `[Software System]`,
182
+ * `[Container: technology]`, `[Component: technology]`.
183
+ *
184
+ * A PROMPT, not a value: {@link C4_TYPE_PLACEHOLDER} is what the exporter
185
+ * compares against to decide the author has stated nothing yet, so an untouched
186
+ * element exports as `Container(alias, "Container")` rather than as one built
187
+ * with a technology literally called "technology".
188
+ */
189
+ export const C4_TYPE_PLACEHOLDER = Object.fromEntries(Object.keys(C4_TYPE_WORD).map(kind => [
190
+ kind,
191
+ c4TypeLine(kind, C4_TYPE_TAKES_TECHNOLOGY[kind] ? TYPE_TECHNOLOGY_PLACEHOLDER : undefined),
192
+ ]));
193
+ /* ── What the line becomes when the shape becomes something else ───────── */
194
+ /**
195
+ * The type line a component should carry once its shape has morphed from `from`
196
+ * to `to` — or `null` when the line is the AUTHOR's and must not be touched.
197
+ *
198
+ * ## Why a morph has to ask this at all
199
+ *
200
+ * The bracketed word is derived from `kind` (see the head of this file), so a
201
+ * shape that becomes a component while its caption still reads `[Container]` is
202
+ * a picture contradicting its own words. But the caption is a canvas TEXT
203
+ * element an architect may have typed anything into, and rewriting that would
204
+ * be the morph taking away something nobody asked it to touch. So the rule is
205
+ * the narrowest one that fixes the contradiction:
206
+ *
207
+ * - the SOURCE kind's untouched prompt (`[Container: technology]`) becomes the
208
+ * TARGET's own prompt — `[Person]` on the way to a person, not `[Person:
209
+ * technology]`, because a person is not built with a technology
210
+ * ({@link C4_TYPE_TAKES_TECHNOLOGY});
211
+ * - a line that is exactly what the source kind DERIVES — `[Container: React]`,
212
+ * the form {@link normalizeC4TypeLine} leaves behind after every edit — is
213
+ * re-derived for the target, technology and all: `[Component: React]`;
214
+ * - anything else is the author's and comes back `null`. A line reading
215
+ * `see ADR 0042`, or one mid-edit, survives the morph verbatim.
216
+ *
217
+ * The placeholder is tested FIRST and not folded into the second branch, which
218
+ * would find it too: `technologyOfTypeLine('[Container: technology]')` is the
219
+ * literal word `technology`, and carrying it across would hand a person the
220
+ * `[Person: technology]` no stencil ever draws.
221
+ *
222
+ * ## Inert on today's families, and kept anyway
223
+ *
224
+ * Every family declared in `./morph.ts` shares one {@link C4_TYPE_WORD} — all
225
+ * four containers say `Container`, both people say `Person`, both systems say
226
+ * `Software System` — and shares its {@link C4_TYPE_TAKES_TECHNOLOGY} answer
227
+ * too, so on the shipped table this function returns the line it was given and
228
+ * nothing visibly happens. That is a property of the FAMILIES, which are data
229
+ * and grow by declaration, not of the notation: the moment a family gains a
230
+ * member that announces itself differently, the caption follows the shape
231
+ * without anyone being prompted to remember it should.
232
+ *
233
+ * Pure, total and `std`-free, like everything else in this file.
234
+ */
235
+ export function c4MorphedTypeLine(from, to, rawText) {
236
+ const text = (rawText ?? '').trim();
237
+ if (!text)
238
+ return null;
239
+ if (text === C4_TYPE_PLACEHOLDER[from])
240
+ return C4_TYPE_PLACEHOLDER[to];
241
+ if (text !== normalizeC4TypeLine(from, text))
242
+ return null;
243
+ return c4TypeLine(to, technologyOfTypeLine(text));
244
+ }
package/dist/view.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { type ViewExtensionContext, ViewExtensionProvider } from '@formicoidea/labre-core/ext-loader';
2
+ /**
3
+ * C4 rendering — ALWAYS registered, independent of any flag. Boards, boundaries
4
+ * and nodes already drawn must paint, stay selectable, stay editable and keep
5
+ * their resize gating whatever the tooling flag says. See `docs/adr/0009`.
6
+ *
7
+ * This is the RENDER half. The creation tooling — the senior button, its menu
8
+ * and its commands — is {@link C4ViewExtension} below, exactly as
9
+ * `BpmnViewExtension` is separate from `BpmnRenderViewExtension`. Nothing in
10
+ * this class may become flag-gated: a stored document needs every one of these
11
+ * registrations to load and paint.
12
+ */
13
+ export declare class C4RenderViewExtension extends ViewExtensionProvider {
14
+ name: string;
15
+ setup(context: ViewExtensionContext): void;
16
+ }
17
+ /**
18
+ * C4 creation tooling — flag-gated (`c4`): the senior toolbar button, its
19
+ * sub-menu, the thirteen commands behind them, the board's legend button, the
20
+ * component's "Change type" dropdown, and the validation rules and profiles.
21
+ *
22
+ * All of it is tooling in the sense `docs/adr/0009` means: a diagram drawn while
23
+ * the flag was on keeps painting, stays selectable and keeps its contextual
24
+ * toolbar when it goes off — only the ways to add new elements go away, the
25
+ * legend included since generating one CREATES elements, and the checking stops.
26
+ * The profile a board was put on stays written, unread, until the flag comes
27
+ * back.
28
+ */
29
+ export declare class C4ViewExtension extends ViewExtensionProvider {
30
+ name: string;
31
+ effect(): void;
32
+ setup(context: ViewExtensionContext): void;
33
+ }
package/dist/view.js ADDED
@@ -0,0 +1,148 @@
1
+ import { FrameworkBackgroundInteractionExtension, InterchangeExtension, morphToolbarConfig, ValidationProfileExtension, ValidationRuleExtension, } from '@formicoidea/labre-core/blocks/surface';
2
+ import { ViewExtensionProvider, } from '@formicoidea/labre-core/ext-loader';
3
+ import { ToolbarModuleExtension, toolbarModuleKey, } from '@formicoidea/labre-core/shared/services';
4
+ import { BlockFlavourIdentifier, CommandExtension } from '@formicoidea/labre-core/std';
5
+ import { RoleVocabularyExtension } from '@formicoidea/labre-core/std/gfx';
6
+ import { C4_BOARD_BACKGROUND, C4_BOUNDARY_BACKGROUND } from './background.js';
7
+ import { c4CommandIcons, c4Commands } from './commands.js';
8
+ import { effects } from './effects.js';
9
+ import { C4BoardRendererExtension, C4BoundaryRendererExtension, } from './element-renderer.js';
10
+ import { C4BoardView, C4BoundaryView } from './element-view.js';
11
+ import { C4_INTERCHANGE } from './interchange.js';
12
+ import { C4_MORPH_SPEC } from './morph.js';
13
+ import { C4NodeRendererExtension } from './node/node-renderer.js';
14
+ import { C4NodeView } from './node/node-view.js';
15
+ import { C4TypeLineWatcher } from './node/type-line-watcher.js';
16
+ import { C4_PROFILES } from './profiles.js';
17
+ import { C4_ROLES } from './roles.js';
18
+ import { C4_RULES } from './rules.js';
19
+ import { c4BoardToolbarExtension, c4BoardToolingToolbarExtension, } from './toolbar/config.js';
20
+ import { c4SeniorTool } from './toolbar/senior-tool.js';
21
+ /**
22
+ * C4 rendering — ALWAYS registered, independent of any flag. Boards, boundaries
23
+ * and nodes already drawn must paint, stay selectable, stay editable and keep
24
+ * their resize gating whatever the tooling flag says. See `docs/adr/0009`.
25
+ *
26
+ * This is the RENDER half. The creation tooling — the senior button, its menu
27
+ * and its commands — is {@link C4ViewExtension} below, exactly as
28
+ * `BpmnViewExtension` is separate from `BpmnRenderViewExtension`. Nothing in
29
+ * this class may become flag-gated: a stored document needs every one of these
30
+ * registrations to load and paint.
31
+ */
32
+ export class C4RenderViewExtension extends ViewExtensionProvider {
33
+ constructor() {
34
+ super(...arguments);
35
+ this.name = 'affine-c4-render-gfx';
36
+ }
37
+ setup(context) {
38
+ super.setup(context);
39
+ context.register(C4BoardView);
40
+ context.register(C4BoardRendererExtension);
41
+ context.register(C4BoundaryView);
42
+ context.register(C4BoundaryRendererExtension);
43
+ context.register(C4NodeView);
44
+ context.register(C4NodeRendererExtension);
45
+ // The role VOCABULARY, always on. A role is written in the document, not in
46
+ // the tooling: the direction reveal of the relationship, the inversion
47
+ // command and the toolbar entry that must not lie about a typed edge all
48
+ // read this, and they have to keep working on a diagram drawn while the flag
49
+ // was on and opened while it is off (`docs/adr/0009`, `docs/adr/0010`).
50
+ context.register(RoleVocabularyExtension(C4_ROLES));
51
+ if (this.isEdgeless(context.scope)) {
52
+ // Resize gating, driven by the declarations like every other framework
53
+ // background: the handles follow `resizeEnabled`, which both declarations
54
+ // seed to `true` — a diagram and a boundary are both stretched to fit.
55
+ context.register(FrameworkBackgroundInteractionExtension(C4_BOARD_BACKGROUND));
56
+ context.register(FrameworkBackgroundInteractionExtension(C4_BOUNDARY_BACKGROUND));
57
+ // The selected board's own row — the resize toggle half of it. Always-on
58
+ // for the reason `docs/adr/0009` gives: a stored board must keep its
59
+ // handles usable with the C4 button switched off. The legend button is a
60
+ // second module, registered by the flag-gated half below.
61
+ context.register(c4BoardToolbarExtension);
62
+ // Keeps a component's type line semi-derived while its author types into
63
+ // it: the word from the kind, the technology from them. Always-on for the
64
+ // reason `docs/adr/0009` gives — it authors nothing, it keeps an element
65
+ // already in the document readable, and a diagram drawn while the C4
66
+ // button was on must stay editable when it goes off.
67
+ context.register(C4TypeLineWatcher);
68
+ }
69
+ }
70
+ }
71
+ /**
72
+ * C4 creation tooling — flag-gated (`c4`): the senior toolbar button, its
73
+ * sub-menu, the thirteen commands behind them, the board's legend button, the
74
+ * component's "Change type" dropdown, and the validation rules and profiles.
75
+ *
76
+ * All of it is tooling in the sense `docs/adr/0009` means: a diagram drawn while
77
+ * the flag was on keeps painting, stays selectable and keeps its contextual
78
+ * toolbar when it goes off — only the ways to add new elements go away, the
79
+ * legend included since generating one CREATES elements, and the checking stops.
80
+ * The profile a board was put on stays written, unread, until the flag comes
81
+ * back.
82
+ */
83
+ export class C4ViewExtension extends ViewExtensionProvider {
84
+ constructor() {
85
+ super(...arguments);
86
+ this.name = 'affine-c4-gfx';
87
+ }
88
+ effect() {
89
+ super.effect();
90
+ // Defines the senior button and its menu — tooling-only custom elements.
91
+ effects();
92
+ }
93
+ setup(context) {
94
+ super.setup(context);
95
+ if (this.isEdgeless(context.scope)) {
96
+ context.register(ValidationRuleExtension(C4_RULES));
97
+ context.register(ValidationProfileExtension(C4_PROFILES));
98
+ // Writing mermaid files, declared rather than assumed (`docs/adr/0012`).
99
+ // Tooling like the rest of this class: with the flag off there is nothing
100
+ // to export WITH, while a stored diagram keeps painting (`docs/adr/0009`).
101
+ context.register(InterchangeExtension(C4_INTERCHANGE));
102
+ context.register(c4SeniorTool);
103
+ context.register(CommandExtension(c4Commands, c4CommandIcons));
104
+ // The flag-gated half of the selected BOARD's row, through the `custom:`
105
+ // flavour slot — the shape wardley, bpmn and the context map all use to
106
+ // hang flag-gated entries off a row whose base is always-on. One module,
107
+ // carrying the legend button AND the Validation dropdown, because the two
108
+ // halves are gated by this one flag and there is no reason to spend a
109
+ // second registration on them (`toolbarModuleKey`, used further down for
110
+ // the group's row, is what a THIRD contributor to one element needs).
111
+ //
112
+ // There is no boundary module, and no rule of this pack goes unarbitrated
113
+ // for want of one: the board alone arbitrates the checklist (PO,
114
+ // 28/08/2026), and a boundary inherits its board's choice in the engine
115
+ // (`inheritChosenProfiles`), so the two boundary-anchored rules harden
116
+ // with the rest. See `toolbar/config.ts`, where every flavour claim of
117
+ // this framework lives.
118
+ context.register(c4BoardToolingToolbarExtension);
119
+ // The "Change type" dropdown on a selected COMPONENT's contextual
120
+ // toolbar — the generic module, parameterized by C4's own families.
121
+ //
122
+ // ## Why the key carries an owner
123
+ //
124
+ // A C4 component is a native `group`, so the row the toolbar draws for it
125
+ // is the GROUP's row, merged from `affine:surface:group`,
126
+ // `custom:affine:surface:group` and the two surface wildcards. Both group
127
+ // keys were already claimed — the first by the native group operations
128
+ // (rename, ungroup), the second by Wardley's qualification dropdown,
129
+ // which is on the group for the very same reason this is — and a second
130
+ // module on either would have thrown `DuplicateServiceDefinitionError`
131
+ // before the editor finished setting up. That ceiling of two contributors
132
+ // per element is what `toolbarModuleKey` lifts: the module is registered
133
+ // under the DISTINCT variant `custom:affine:surface:group#c4-morph` and
134
+ // the registry hands it to the same row (`toolbar-service/registry.ts`).
135
+ //
136
+ // ## Why here
137
+ //
138
+ // In the flag-gated half, because a morph is TOOLING: a component drawn
139
+ // while the flag was on keeps its kind, its role, its colours, its words
140
+ // and its place in every rule when the flag goes off — it just stops
141
+ // being something the toolbar offers to say differently (`docs/adr/0009`).
142
+ context.register(ToolbarModuleExtension({
143
+ id: BlockFlavourIdentifier(toolbarModuleKey('custom:affine:surface:group', 'c4-morph')),
144
+ config: morphToolbarConfig(C4_MORPH_SPEC),
145
+ }));
146
+ }
147
+ }
148
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@formicoidea/labre-framework-c4",
3
+ "description": "Labre c4 framework for @formicoidea/labre-core.",
4
+ "version": "0.33.0",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "author": "lajola",
8
+ "contributors": [
9
+ "toeverything"
10
+ ],
11
+ "license": "MPL-2.0",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./view": {
18
+ "types": "./dist/view.d.ts",
19
+ "import": "./dist/view.js"
20
+ },
21
+ "./descriptor": {
22
+ "types": "./dist/descriptor.d.ts",
23
+ "import": "./dist/descriptor.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "dependencies": {
30
+ "@formicoidea/labre-core": "0.33.0",
31
+ "@formicoidea/labre-ddd-shared": "0.33.0",
32
+ "lit": "^3.2.0"
33
+ }
34
+ }