@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.
- package/dist/actions.d.ts +179 -0
- package/dist/actions.js +375 -0
- package/dist/background.d.ts +77 -0
- package/dist/background.js +223 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +221 -0
- package/dist/component.d.ts +192 -0
- package/dist/component.js +188 -0
- package/dist/consts.d.ts +331 -0
- package/dist/consts.js +384 -0
- package/dist/descriptor.d.ts +12 -0
- package/dist/descriptor.js +10 -0
- package/dist/effects.d.ts +9 -0
- package/dist/effects.js +6 -0
- package/dist/element-renderer.d.ts +18 -0
- package/dist/element-renderer.js +14 -0
- package/dist/element-view.d.ts +51 -0
- package/dist/element-view.js +146 -0
- package/dist/export.d.ts +184 -0
- package/dist/export.js +454 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +52 -0
- package/dist/interchange.d.ts +74 -0
- package/dist/interchange.js +143 -0
- package/dist/legend.d.ts +37 -0
- package/dist/legend.js +123 -0
- package/dist/levels.d.ts +70 -0
- package/dist/levels.js +46 -0
- package/dist/morph.d.ts +89 -0
- package/dist/morph.js +229 -0
- package/dist/node/node-renderer.d.ts +6 -0
- package/dist/node/node-renderer.js +304 -0
- package/dist/node/node-view.d.ts +45 -0
- package/dist/node/node-view.js +80 -0
- package/dist/node/type-line-watcher.d.ts +70 -0
- package/dist/node/type-line-watcher.js +142 -0
- package/dist/presets.d.ts +84 -0
- package/dist/presets.js +149 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +177 -0
- package/dist/roles.d.ts +116 -0
- package/dist/roles.js +303 -0
- package/dist/rules.d.ts +95 -0
- package/dist/rules.js +1261 -0
- package/dist/toolbar/c4-menu.d.ts +11 -0
- package/dist/toolbar/c4-menu.js +14 -0
- package/dist/toolbar/c4-senior-button.d.ts +19 -0
- package/dist/toolbar/c4-senior-button.js +23 -0
- package/dist/toolbar/config.d.ts +150 -0
- package/dist/toolbar/config.js +436 -0
- package/dist/toolbar/icons.d.ts +90 -0
- package/dist/toolbar/icons.js +157 -0
- package/dist/toolbar/senior-tool.d.ts +1 -0
- package/dist/toolbar/senior-tool.js +11 -0
- package/dist/translations.d.ts +18 -0
- package/dist/translations.js +42 -0
- package/dist/type-line.d.ts +175 -0
- package/dist/type-line.js +244 -0
- package/dist/view.d.ts +33 -0
- package/dist/view.js +148 -0
- 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
|
+
}
|