@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// The declarations ARE the two frames: what they look like, where their plot is
|
|
2
|
+
// and which words they carry are all read off these two values (`docs/adr/0009`
|
|
3
|
+
// on why a framework declares rather than draws). Exported so a host — and the
|
|
4
|
+
// audit's own consumers — can answer those questions without a canvas.
|
|
5
|
+
export { C4_BOARD_BACKGROUND, C4_BOUNDARY_BACKGROUND } from './background.js';
|
|
6
|
+
export { BOUNDARY_LABEL, DESCRIPTION_PLACEHOLDER, NODE_LABEL, NODE_PALETTE, NODE_RADIUS, NODE_SIZE, NODE_STROKE_WIDTH, RELATIONSHIP_STROKE, RELATIONSHIP_WIDTH, } from './consts.js';
|
|
7
|
+
// The middle tier of an element's label, SEMI-DERIVED: the word from the kind,
|
|
8
|
+
// the technology from the author. Pure and total over the nine kinds and over
|
|
9
|
+
// every string an author can type, so a host, a rule or a reading can name an
|
|
10
|
+
// element exactly as the canvas does — and read the technology back out of it.
|
|
11
|
+
export { C4_TYPE_PLACEHOLDER, C4_TYPE_TAKES_TECHNOLOGY, C4_TYPE_WORD, c4MorphedTypeLine, c4TypeLine, normalizeC4TypeLine, technologyOfTypeLine, TYPE_TECHNOLOGY_PLACEHOLDER, } from './type-line.js';
|
|
12
|
+
// What a C4 shape is BORN as, shared by the palette and — since the morph — by
|
|
13
|
+
// the toolbar that says one artefact differently, so the two can never disagree
|
|
14
|
+
// about what a database looks like.
|
|
15
|
+
export { c4MorphClears, c4MorphProps, c4NodeProps, GLYPH_BODY_KINDS, } from './presets.js';
|
|
16
|
+
// What an artefact may BECOME: the declared families, the spec the generic
|
|
17
|
+
// morph module is registered with, and the resolution from a selected group to
|
|
18
|
+
// the shape inside it. Data a host can read without an editor.
|
|
19
|
+
export { C4_MORPH_FAMILIES, C4_MORPH_SPEC, c4MorphedTitle, c4NodeOfComponent, } from './morph.js';
|
|
20
|
+
// A C4 component — the shape and its own words, grouped. Where the two written
|
|
21
|
+
// tiers are placed at creation, and which of them belongs to which node. Pure,
|
|
22
|
+
// so the creation site, the exporter and a host all resolve a component the
|
|
23
|
+
// same way.
|
|
24
|
+
export { c4ComponentSiblings, c4ComponentTiers, c4StatedDescription, c4StatedName, c4StatedTechnology, c4TierBoxes, c4TierText, } from './component.js';
|
|
25
|
+
// Which of the three C4 diagrams a board declares it draws, and the words the
|
|
26
|
+
// picker offers them under — DATA, like the rules that read the fact.
|
|
27
|
+
export { C4_BOARD_LEVEL_MENU } from './levels.js';
|
|
28
|
+
// The levels of requirement, and the rules they arbitrate: DATA a host can
|
|
29
|
+
// read, ship and reason about without an editor.
|
|
30
|
+
export { C4_PROFILES } from './profiles.js';
|
|
31
|
+
export { C4_ROLE, C4_ROLE_OF_KIND, C4_ROLES, } from './roles.js';
|
|
32
|
+
// The half of the export that needs an editor, kept apart from the half that
|
|
33
|
+
// does not — same split BPMN's index makes for the same reason.
|
|
34
|
+
export { c4BoardsForExport, c4ExportBoardOf } from './actions.js';
|
|
35
|
+
// The export itself: a PURE function over element models — models in, mermaid
|
|
36
|
+
// out — so a host can export a board it never rendered, and a rule or a test can
|
|
37
|
+
// call it with plain stubs.
|
|
38
|
+
export { C4_MERMAID_OF_KIND, exportC4Mermaid, toMermaidAlias, toMermaidText, } from './export.js';
|
|
39
|
+
// C4's entries in the interchange registry (`docs/adr/0012`) — one today,
|
|
40
|
+
// mermaid OUT. Exported whole so a host can ask what C4 can read and write
|
|
41
|
+
// without mounting an editor, and call it without one either.
|
|
42
|
+
export { C4_INTERCHANGE, C4_MERMAID_EXPORT, C4_MERMAID_EXTENSION, C4_MERMAID_FORMAT, C4_MERMAID_MIME, c4BoardFrom, c4SafeFilename, } from './interchange.js';
|
|
43
|
+
export { C4_ELEMENT_MATRIX, C4_RELATIONSHIP_MATRIX, C4_RULES, } from './rules.js';
|
|
44
|
+
// The toolbox, for the host that composes the command registry and the
|
|
45
|
+
// translation-key manifest out of the frameworks it installed (see
|
|
46
|
+
// `packages/affine/all/src/{commands,translations}.ts`).
|
|
47
|
+
export { c4CommandIcons, c4Commands } from './commands.js';
|
|
48
|
+
export { c4TranslationEntries } from './translations.js';
|
|
49
|
+
// The legend TABLE — what the board's automatic legend can say. Exported for
|
|
50
|
+
// the same reason the two background declarations are: it answers "what does
|
|
51
|
+
// this framework document about itself" without a canvas.
|
|
52
|
+
export { C4_AUTO_LEGEND } from './legend.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { InterchangeCapability, InterchangeExportCapability, InterchangeFormat } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import type { GfxPrimitiveElementModel } from '@formicoidea/labre-core/std/gfx';
|
|
3
|
+
import { type C4ExportBoard } from './export.js';
|
|
4
|
+
/**
|
|
5
|
+
* C4's entries in the interchange registry (`docs/adr/0012`, P1).
|
|
6
|
+
*
|
|
7
|
+
* One entry today — mermaid OUT — declared exactly as BPMN declares its `.bpmn`
|
|
8
|
+
* export: C4 can write a mermaid C4 diagram and cannot yet read one, and the
|
|
9
|
+
* registry says so instead of leaving a reader to infer a symmetry nobody
|
|
10
|
+
* implemented.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is pure. The serializer was already a pure function with no
|
|
13
|
+
* `std` in sight, and this file adds no editor to it: it only picks the
|
|
14
|
+
* artefacts the serializer speaks about out of the elements it is handed.
|
|
15
|
+
*
|
|
16
|
+
* One C4-specific reading of the contract: the SELECTION is expressed by the
|
|
17
|
+
* caller through which boards it includes in the element list. A C4 board is
|
|
18
|
+
* one level of one model, so `exportC4Mermaid` scopes to the boards it is
|
|
19
|
+
* given — every `c4Board` in the list becomes a document, and the caller that
|
|
20
|
+
* wants only the selected ones simply leaves the others out. A headless host
|
|
21
|
+
* that hands over the whole surface gets every board, one document each, which
|
|
22
|
+
* is the honest whole-surface answer for this framework.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* mermaid C4 source. **Semantic** — the file carries a model, not a picture, so
|
|
26
|
+
* it takes the whole preservation contract the day an importer is written.
|
|
27
|
+
*
|
|
28
|
+
* `.mmd` is the extension the mermaid CLI and every editor plugin watch for.
|
|
29
|
+
* The format carries no `mime`, where `BPMN_XML_FORMAT` carries
|
|
30
|
+
* `application/xml`, and the difference is the truth rather than an oversight:
|
|
31
|
+
* mermaid has no registered media type, so there is none to declare. What a
|
|
32
|
+
* DOWNLOAD is served as is a different question with an answer — `text/plain`,
|
|
33
|
+
* because an invented type is a file some browsers refuse to save — and that
|
|
34
|
+
* answer is stated on the export result, where it belongs.
|
|
35
|
+
*
|
|
36
|
+
* The charset rides along with it because the text is the whole payload: unlike
|
|
37
|
+
* XML, a mermaid file has no prolog to declare its own encoding in.
|
|
38
|
+
*/
|
|
39
|
+
export declare const C4_MERMAID_EXTENSION = ".mmd";
|
|
40
|
+
export declare const C4_MERMAID_MIME = "text/plain;charset=utf-8";
|
|
41
|
+
export declare const C4_MERMAID_FORMAT: InterchangeFormat;
|
|
42
|
+
/**
|
|
43
|
+
* The artefacts the exporter speaks about, picked out of a list of elements and
|
|
44
|
+
* kept in the order they were given.
|
|
45
|
+
*
|
|
46
|
+
* Six lists and not four: since the PO's recette of 28/08/2026 a C4 component
|
|
47
|
+
* is a shape, its three written tiers as canvas TEXT elements, and the GROUP
|
|
48
|
+
* that makes the four one thing — so the technology and the description the
|
|
49
|
+
* exporter writes are read off the canvas rather than off two model fields.
|
|
50
|
+
* Both lists are collected whole and unfiltered, because a tier belongs to its
|
|
51
|
+
* node through the group and not through the geometry (`component.ts`).
|
|
52
|
+
*
|
|
53
|
+
* Document order matters for the reason it does everywhere in this framework:
|
|
54
|
+
* it is the tie-break attribution breaks on — a centre inside two overlapping
|
|
55
|
+
* boundaries goes to the first — and the audit's `attribute()` breaks it the
|
|
56
|
+
* same way. Sorting here would make the export disagree with the badge the
|
|
57
|
+
* user can see.
|
|
58
|
+
*/
|
|
59
|
+
export declare function c4BoardFrom(elements: readonly GfxPrimitiveElementModel[]): C4ExportBoard;
|
|
60
|
+
/**
|
|
61
|
+
* A name a file system will accept, minus the extension.
|
|
62
|
+
*
|
|
63
|
+
* The same transformations `bpmnSafeFilename` applies and for the same reasons
|
|
64
|
+
* — reserved characters become `-`, whitespace runs collapse, the result is
|
|
65
|
+
* capped, and the Windows tail of dots and spaces is trimmed AFTER the cap so
|
|
66
|
+
* the extension is not the thing that gets eaten. Duplicated rather than
|
|
67
|
+
* imported: a filename rule is not API, and coupling two frameworks to share
|
|
68
|
+
* eight lines would be the worse trade.
|
|
69
|
+
*/
|
|
70
|
+
export declare function c4SafeFilename(raw: string | undefined): string;
|
|
71
|
+
/** `c4:mermaid:export` — C4's first capability. */
|
|
72
|
+
export declare const C4_MERMAID_EXPORT: InterchangeExportCapability;
|
|
73
|
+
/** Everything C4 registers, in one list the view extension can hand over. */
|
|
74
|
+
export declare const C4_INTERCHANGE: readonly InterchangeCapability[];
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { interchangeCapabilityId } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { C4BoardElementModel, C4BoundaryElementModel, C4NodeElementModel, ConnectorElementModel, GroupElementModel, TextElementModel, } from '@formicoidea/labre-core/model';
|
|
3
|
+
import { exportC4Mermaid } from './export.js';
|
|
4
|
+
/**
|
|
5
|
+
* C4's entries in the interchange registry (`docs/adr/0012`, P1).
|
|
6
|
+
*
|
|
7
|
+
* One entry today — mermaid OUT — declared exactly as BPMN declares its `.bpmn`
|
|
8
|
+
* export: C4 can write a mermaid C4 diagram and cannot yet read one, and the
|
|
9
|
+
* registry says so instead of leaving a reader to infer a symmetry nobody
|
|
10
|
+
* implemented.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is pure. The serializer was already a pure function with no
|
|
13
|
+
* `std` in sight, and this file adds no editor to it: it only picks the
|
|
14
|
+
* artefacts the serializer speaks about out of the elements it is handed.
|
|
15
|
+
*
|
|
16
|
+
* One C4-specific reading of the contract: the SELECTION is expressed by the
|
|
17
|
+
* caller through which boards it includes in the element list. A C4 board is
|
|
18
|
+
* one level of one model, so `exportC4Mermaid` scopes to the boards it is
|
|
19
|
+
* given — every `c4Board` in the list becomes a document, and the caller that
|
|
20
|
+
* wants only the selected ones simply leaves the others out. A headless host
|
|
21
|
+
* that hands over the whole surface gets every board, one document each, which
|
|
22
|
+
* is the honest whole-surface answer for this framework.
|
|
23
|
+
*/
|
|
24
|
+
/* ── The format ───────────────────────────────────────────────────────── */
|
|
25
|
+
/**
|
|
26
|
+
* mermaid C4 source. **Semantic** — the file carries a model, not a picture, so
|
|
27
|
+
* it takes the whole preservation contract the day an importer is written.
|
|
28
|
+
*
|
|
29
|
+
* `.mmd` is the extension the mermaid CLI and every editor plugin watch for.
|
|
30
|
+
* The format carries no `mime`, where `BPMN_XML_FORMAT` carries
|
|
31
|
+
* `application/xml`, and the difference is the truth rather than an oversight:
|
|
32
|
+
* mermaid has no registered media type, so there is none to declare. What a
|
|
33
|
+
* DOWNLOAD is served as is a different question with an answer — `text/plain`,
|
|
34
|
+
* because an invented type is a file some browsers refuse to save — and that
|
|
35
|
+
* answer is stated on the export result, where it belongs.
|
|
36
|
+
*
|
|
37
|
+
* The charset rides along with it because the text is the whole payload: unlike
|
|
38
|
+
* XML, a mermaid file has no prolog to declare its own encoding in.
|
|
39
|
+
*/
|
|
40
|
+
export const C4_MERMAID_EXTENSION = '.mmd';
|
|
41
|
+
export const C4_MERMAID_MIME = 'text/plain;charset=utf-8';
|
|
42
|
+
export const C4_MERMAID_FORMAT = {
|
|
43
|
+
id: 'mermaid',
|
|
44
|
+
tier: 'semantic',
|
|
45
|
+
extensions: [C4_MERMAID_EXTENSION],
|
|
46
|
+
};
|
|
47
|
+
/* ── Pure board helpers ───────────────────────────────────────────────── */
|
|
48
|
+
/**
|
|
49
|
+
* The artefacts the exporter speaks about, picked out of a list of elements and
|
|
50
|
+
* kept in the order they were given.
|
|
51
|
+
*
|
|
52
|
+
* Six lists and not four: since the PO's recette of 28/08/2026 a C4 component
|
|
53
|
+
* is a shape, its three written tiers as canvas TEXT elements, and the GROUP
|
|
54
|
+
* that makes the four one thing — so the technology and the description the
|
|
55
|
+
* exporter writes are read off the canvas rather than off two model fields.
|
|
56
|
+
* Both lists are collected whole and unfiltered, because a tier belongs to its
|
|
57
|
+
* node through the group and not through the geometry (`component.ts`).
|
|
58
|
+
*
|
|
59
|
+
* Document order matters for the reason it does everywhere in this framework:
|
|
60
|
+
* it is the tie-break attribution breaks on — a centre inside two overlapping
|
|
61
|
+
* boundaries goes to the first — and the audit's `attribute()` breaks it the
|
|
62
|
+
* same way. Sorting here would make the export disagree with the badge the
|
|
63
|
+
* user can see.
|
|
64
|
+
*/
|
|
65
|
+
export function c4BoardFrom(elements) {
|
|
66
|
+
const boards = [];
|
|
67
|
+
const nodes = [];
|
|
68
|
+
const boundaries = [];
|
|
69
|
+
const connectors = [];
|
|
70
|
+
const texts = [];
|
|
71
|
+
const groups = [];
|
|
72
|
+
for (const element of elements) {
|
|
73
|
+
if (element instanceof C4BoardElementModel)
|
|
74
|
+
boards.push(element);
|
|
75
|
+
else if (element instanceof C4NodeElementModel)
|
|
76
|
+
nodes.push(element);
|
|
77
|
+
else if (element instanceof C4BoundaryElementModel)
|
|
78
|
+
boundaries.push(element);
|
|
79
|
+
else if (element instanceof ConnectorElementModel)
|
|
80
|
+
connectors.push(element);
|
|
81
|
+
else if (element instanceof TextElementModel)
|
|
82
|
+
texts.push(element);
|
|
83
|
+
else if (element instanceof GroupElementModel)
|
|
84
|
+
groups.push(element);
|
|
85
|
+
}
|
|
86
|
+
return { boards, nodes, boundaries, connectors, texts, groups };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A name a file system will accept, minus the extension.
|
|
90
|
+
*
|
|
91
|
+
* The same transformations `bpmnSafeFilename` applies and for the same reasons
|
|
92
|
+
* — reserved characters become `-`, whitespace runs collapse, the result is
|
|
93
|
+
* capped, and the Windows tail of dots and spaces is trimmed AFTER the cap so
|
|
94
|
+
* the extension is not the thing that gets eaten. Duplicated rather than
|
|
95
|
+
* imported: a filename rule is not API, and coupling two frameworks to share
|
|
96
|
+
* eight lines would be the worse trade.
|
|
97
|
+
*/
|
|
98
|
+
export function c4SafeFilename(raw) {
|
|
99
|
+
const safe = (raw ?? '')
|
|
100
|
+
.trim()
|
|
101
|
+
.replaceAll(/[\\/:*?"<>|]/g, '-')
|
|
102
|
+
.replaceAll(/\s+/g, ' ')
|
|
103
|
+
.trim()
|
|
104
|
+
.slice(0, 120)
|
|
105
|
+
.replace(/[. ]+$/, '');
|
|
106
|
+
return safe || 'diagram';
|
|
107
|
+
}
|
|
108
|
+
/* ── The capability ───────────────────────────────────────────────────── */
|
|
109
|
+
/**
|
|
110
|
+
* The board as a mermaid C4 document.
|
|
111
|
+
*
|
|
112
|
+
* A thin adapter and nothing else: it picks the C4 artefacts out of the
|
|
113
|
+
* elements it was handed, runs {@link exportC4Mermaid} — unchanged — and names
|
|
114
|
+
* the file. There is no second door: `c4.exportMermaid` calls THIS, so the
|
|
115
|
+
* command and the registry cannot produce different bytes, different filenames
|
|
116
|
+
* or different content types — there is nowhere for them to differ.
|
|
117
|
+
*
|
|
118
|
+
* No `warnings`, where the BPMN exporter has some: the C4 writer drops nothing
|
|
119
|
+
* it could have written down. What it leaves out — a neutral connector, a shape
|
|
120
|
+
* with no role, a relationship whose far end is on another board — is not a
|
|
121
|
+
* loss but a statement the author never made (`docs/adr/0010`), and a warning
|
|
122
|
+
* about it would be the export telling the user off for their own drawing.
|
|
123
|
+
*/
|
|
124
|
+
const runC4MermaidExport = (elements, context) => {
|
|
125
|
+
const name = c4SafeFilename(context.name);
|
|
126
|
+
return {
|
|
127
|
+
text: exportC4Mermaid(c4BoardFrom(elements)),
|
|
128
|
+
filename: `${name}${C4_MERMAID_EXTENSION}`,
|
|
129
|
+
mime: C4_MERMAID_MIME,
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
/** `c4:mermaid:export` — C4's first capability. */
|
|
133
|
+
export const C4_MERMAID_EXPORT = {
|
|
134
|
+
id: interchangeCapabilityId('c4', C4_MERMAID_FORMAT.id, 'export'),
|
|
135
|
+
framework: 'c4',
|
|
136
|
+
format: C4_MERMAID_FORMAT,
|
|
137
|
+
direction: 'export',
|
|
138
|
+
run: runC4MermaidExport,
|
|
139
|
+
};
|
|
140
|
+
/** Everything C4 registers, in one list the view extension can hand over. */
|
|
141
|
+
export const C4_INTERCHANGE = [
|
|
142
|
+
C4_MERMAID_EXPORT,
|
|
143
|
+
];
|
package/dist/legend.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AutoLegendSpec } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
/**
|
|
3
|
+
* What the C4 board's automatic legend can say — a TABLE, and nothing else: the
|
|
4
|
+
* scan, the placement and the box are `createAutoLegend`'s job, shared with the
|
|
5
|
+
* three DDD boards.
|
|
6
|
+
*
|
|
7
|
+
* Every row is DERIVED twice over: its WORDING from the role vocabulary's own
|
|
8
|
+
* `labelFallback` ({@link roleLabel}), and its COLOUR from the very palette the
|
|
9
|
+
* creation site paints with ({@link NODE_PALETTE}). Neither is restated here, so
|
|
10
|
+
* renaming a role renames its legend row and restyling the pack restyles its
|
|
11
|
+
* swatches — which is what keeps a legend a description of the board rather than
|
|
12
|
+
* a second opinion about it.
|
|
13
|
+
*
|
|
14
|
+
* ## Why `exact` on the container, and only there
|
|
15
|
+
*
|
|
16
|
+
* `c4:database` specialises `c4:container`, so an inclusive entry on the parent
|
|
17
|
+
* would put a "Container" row — with the container's blue square — on a board
|
|
18
|
+
* carrying nothing but cylinders. The row would name a shape that is nowhere on
|
|
19
|
+
* the diagram. EDGY's four base kinds hit this first and `AutoLegendEntry.exact`
|
|
20
|
+
* exists for it: base and specialisation become two separate statements, each
|
|
21
|
+
* listed when it is the thing the user actually drew.
|
|
22
|
+
*
|
|
23
|
+
* The other four element roles are childless, so `exact` would change nothing
|
|
24
|
+
* for them and is left off — the flag marks the places where the distinction is
|
|
25
|
+
* real.
|
|
26
|
+
*
|
|
27
|
+
* ## What is missing, and why
|
|
28
|
+
*
|
|
29
|
+
* `mobile` and `browser` get no row of their own: they carry `c4:container`,
|
|
30
|
+
* because a phone app and a single-page app are containers with a picture
|
|
31
|
+
* (`roles.ts`). Detection is by role and only by role, so a board of nothing but
|
|
32
|
+
* mobile apps lists "Container" — which is the honest answer about what the
|
|
33
|
+
* boxes MEAN. `person-ext` and `system-ext` collapse the same way onto person
|
|
34
|
+
* and system, for the same reason: the grey says "out of scope", not "a
|
|
35
|
+
* different sort of thing".
|
|
36
|
+
*/
|
|
37
|
+
export declare const C4_AUTO_LEGEND: AutoLegendSpec;
|
package/dist/legend.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { roleLabel } from '@formicoidea/labre-ddd-shared';
|
|
2
|
+
import { BOUNDARY_STROKE, NODE_PALETTE, RELATIONSHIP_STROKE } from './consts.js';
|
|
3
|
+
import { C4_ROLE, C4_ROLES } from './roles.js';
|
|
4
|
+
/**
|
|
5
|
+
* What the C4 board's automatic legend can say — a TABLE, and nothing else: the
|
|
6
|
+
* scan, the placement and the box are `createAutoLegend`'s job, shared with the
|
|
7
|
+
* three DDD boards.
|
|
8
|
+
*
|
|
9
|
+
* Every row is DERIVED twice over: its WORDING from the role vocabulary's own
|
|
10
|
+
* `labelFallback` ({@link roleLabel}), and its COLOUR from the very palette the
|
|
11
|
+
* creation site paints with ({@link NODE_PALETTE}). Neither is restated here, so
|
|
12
|
+
* renaming a role renames its legend row and restyling the pack restyles its
|
|
13
|
+
* swatches — which is what keeps a legend a description of the board rather than
|
|
14
|
+
* a second opinion about it.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `exact` on the container, and only there
|
|
17
|
+
*
|
|
18
|
+
* `c4:database` specialises `c4:container`, so an inclusive entry on the parent
|
|
19
|
+
* would put a "Container" row — with the container's blue square — on a board
|
|
20
|
+
* carrying nothing but cylinders. The row would name a shape that is nowhere on
|
|
21
|
+
* the diagram. EDGY's four base kinds hit this first and `AutoLegendEntry.exact`
|
|
22
|
+
* exists for it: base and specialisation become two separate statements, each
|
|
23
|
+
* listed when it is the thing the user actually drew.
|
|
24
|
+
*
|
|
25
|
+
* The other four element roles are childless, so `exact` would change nothing
|
|
26
|
+
* for them and is left off — the flag marks the places where the distinction is
|
|
27
|
+
* real.
|
|
28
|
+
*
|
|
29
|
+
* ## What is missing, and why
|
|
30
|
+
*
|
|
31
|
+
* `mobile` and `browser` get no row of their own: they carry `c4:container`,
|
|
32
|
+
* because a phone app and a single-page app are containers with a picture
|
|
33
|
+
* (`roles.ts`). Detection is by role and only by role, so a board of nothing but
|
|
34
|
+
* mobile apps lists "Container" — which is the honest answer about what the
|
|
35
|
+
* boxes MEAN. `person-ext` and `system-ext` collapse the same way onto person
|
|
36
|
+
* and system, for the same reason: the grey says "out of scope", not "a
|
|
37
|
+
* different sort of thing".
|
|
38
|
+
*/
|
|
39
|
+
export const C4_AUTO_LEGEND = {
|
|
40
|
+
title: 'Legend',
|
|
41
|
+
width: 290,
|
|
42
|
+
roles: C4_ROLES,
|
|
43
|
+
sections: [
|
|
44
|
+
{
|
|
45
|
+
title: 'Elements',
|
|
46
|
+
entries: [
|
|
47
|
+
{
|
|
48
|
+
role: C4_ROLE.person,
|
|
49
|
+
row: {
|
|
50
|
+
swatch: 'square',
|
|
51
|
+
color: NODE_PALETTE.person.fill,
|
|
52
|
+
label: roleLabel(C4_ROLES, C4_ROLE.person),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
role: C4_ROLE.system,
|
|
57
|
+
row: {
|
|
58
|
+
swatch: 'square',
|
|
59
|
+
color: NODE_PALETTE.system.fill,
|
|
60
|
+
label: roleLabel(C4_ROLES, C4_ROLE.system),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
role: C4_ROLE.container,
|
|
65
|
+
// See the note above: a board of cylinders must not claim a container.
|
|
66
|
+
exact: true,
|
|
67
|
+
row: {
|
|
68
|
+
swatch: 'square',
|
|
69
|
+
color: NODE_PALETTE.container.fill,
|
|
70
|
+
label: roleLabel(C4_ROLES, C4_ROLE.container),
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
role: C4_ROLE.database,
|
|
75
|
+
row: {
|
|
76
|
+
swatch: 'square',
|
|
77
|
+
color: NODE_PALETTE.database.fill,
|
|
78
|
+
label: roleLabel(C4_ROLES, C4_ROLE.database),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
role: C4_ROLE.component,
|
|
83
|
+
row: {
|
|
84
|
+
swatch: 'square',
|
|
85
|
+
color: NODE_PALETTE.component.fill,
|
|
86
|
+
label: roleLabel(C4_ROLES, C4_ROLE.component),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
title: 'Frames',
|
|
93
|
+
entries: [
|
|
94
|
+
{
|
|
95
|
+
role: C4_ROLE.boundary,
|
|
96
|
+
row: {
|
|
97
|
+
// A line, not a square: a boundary has no body — it is the dashed
|
|
98
|
+
// frame itself, and a filled swatch would draw the one thing this
|
|
99
|
+
// background deliberately does not paint.
|
|
100
|
+
swatch: 'line',
|
|
101
|
+
color: BOUNDARY_STROKE,
|
|
102
|
+
dashed: true,
|
|
103
|
+
label: roleLabel(C4_ROLES, C4_ROLE.boundary),
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
title: 'Relations',
|
|
110
|
+
entries: [
|
|
111
|
+
{
|
|
112
|
+
role: C4_ROLE.relationship,
|
|
113
|
+
row: {
|
|
114
|
+
swatch: 'line',
|
|
115
|
+
color: RELATIONSHIP_STROKE,
|
|
116
|
+
dashed: true,
|
|
117
|
+
label: roleLabel(C4_ROLES, C4_ROLE.relationship),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
package/dist/levels.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { C4BoardLevel } from '@formicoidea/labre-core/model';
|
|
2
|
+
/**
|
|
3
|
+
* The LEVEL a board declares, as the picker offers it — DATA, like everything
|
|
4
|
+
* else this framework contributes.
|
|
5
|
+
*
|
|
6
|
+
* A C4 diagram is drawn at one level: the context of a system, the containers
|
|
7
|
+
* inside it, the components inside one of those. Which one a sheet is showing
|
|
8
|
+
* used to live nowhere the tool could read — the title is free text, and a board
|
|
9
|
+
* called "Payments" says nothing about which of the three it is. So the board
|
|
10
|
+
* carries an optional `level` (`C4BoardElementModel`), this table is the words
|
|
11
|
+
* for it, and `rules.ts` is what judges the sheet against it.
|
|
12
|
+
*
|
|
13
|
+
* ## The title stays the author's
|
|
14
|
+
*
|
|
15
|
+
* Choosing a level RENAMES NOTHING. The level is a declared fact sitting beside
|
|
16
|
+
* the title, not a replacement for it: a context diagram of the payments
|
|
17
|
+
* platform is still called whatever its author called it, and the picker writes
|
|
18
|
+
* one prop and touches no other.
|
|
19
|
+
*
|
|
20
|
+
* ## Why "Free sketch" is an option and not the absence of one
|
|
21
|
+
*
|
|
22
|
+
* The default is no level at all, and it has to be REACHABLE: a user who set a
|
|
23
|
+
* level and then decided the sheet is a working surface after all must be able
|
|
24
|
+
* to say so, and the way back cannot be "delete the board and draw another".
|
|
25
|
+
* Picking it CLEARS the field rather than writing a fourth value — the same call
|
|
26
|
+
* the validation profile picker makes for its default level of requirement — so
|
|
27
|
+
* a board on Free sketch is byte-identical to every C4 board drawn before this
|
|
28
|
+
* existed.
|
|
29
|
+
*
|
|
30
|
+
* ## All four C's are offered, `code` included
|
|
31
|
+
*
|
|
32
|
+
* C4 is named after Context, Containers, Components and Code, and the picker
|
|
33
|
+
* says all four. The fourth is the one C4's own author tells you to skip unless
|
|
34
|
+
* the picture is generated from the source, and this editor draws no code-level
|
|
35
|
+
* artefact — but that is a fact about the TOOLING, not about what a sheet may
|
|
36
|
+
* declare. An architect keeping a board for the class diagram they paste in from
|
|
37
|
+
* elsewhere is entitled to say what it is, and a picker that refused would be
|
|
38
|
+
* presenting our limitation as the notation's.
|
|
39
|
+
*
|
|
40
|
+
* Two of the four are judged by no rule at all — `component` and `code` — and
|
|
41
|
+
* `rules.ts` says why: a component diagram legitimately shows everything C4
|
|
42
|
+
* names, and nothing in the pack knows what a code diagram admits. Both are
|
|
43
|
+
* declarations the tool records and does not check, which is a perfectly honest
|
|
44
|
+
* thing for a declaration to be.
|
|
45
|
+
*/
|
|
46
|
+
export interface C4BoardLevelOption {
|
|
47
|
+
/**
|
|
48
|
+
* What gets written on the board — or `undefined`, which CLEARS the field and
|
|
49
|
+
* puts the sheet back to a free sketch.
|
|
50
|
+
*/
|
|
51
|
+
level: C4BoardLevel | undefined;
|
|
52
|
+
/** i18n key of the entry's words; resolved by the host. */
|
|
53
|
+
labelKey: string;
|
|
54
|
+
/** The framework's own wording, for a host that ships no catalogue. */
|
|
55
|
+
labelFallback: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The picker, whole: its own heading and its five entries, in the order a
|
|
59
|
+
* reader zooms — no level, then the four C's from out to in.
|
|
60
|
+
*
|
|
61
|
+
* One object rather than a loose array plus a stray heading constant, because
|
|
62
|
+
* the manifest walks DECLARATIONS: `c4TranslationEntries` hands this value to
|
|
63
|
+
* `collectTranslationKeys` and every key below reaches a host's catalogue with
|
|
64
|
+
* nothing restated anywhere.
|
|
65
|
+
*/
|
|
66
|
+
export declare const C4_BOARD_LEVEL_MENU: {
|
|
67
|
+
labelKey: string;
|
|
68
|
+
labelFallback: string;
|
|
69
|
+
options: readonly C4BoardLevelOption[];
|
|
70
|
+
};
|
package/dist/levels.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** i18n key stem: `context` → `com.labre.c4.level.context`. */
|
|
2
|
+
const levelKey = (name) => `com.labre.c4.level.${name}`;
|
|
3
|
+
/**
|
|
4
|
+
* The picker, whole: its own heading and its five entries, in the order a
|
|
5
|
+
* reader zooms — no level, then the four C's from out to in.
|
|
6
|
+
*
|
|
7
|
+
* One object rather than a loose array plus a stray heading constant, because
|
|
8
|
+
* the manifest walks DECLARATIONS: `c4TranslationEntries` hands this value to
|
|
9
|
+
* `collectTranslationKeys` and every key below reaches a host's catalogue with
|
|
10
|
+
* nothing restated anywhere.
|
|
11
|
+
*/
|
|
12
|
+
export const C4_BOARD_LEVEL_MENU = {
|
|
13
|
+
labelKey: levelKey('section'),
|
|
14
|
+
labelFallback: 'Level',
|
|
15
|
+
options: [
|
|
16
|
+
{
|
|
17
|
+
// The default, and the only entry that writes nothing.
|
|
18
|
+
level: undefined,
|
|
19
|
+
labelKey: levelKey('none'),
|
|
20
|
+
labelFallback: 'Free sketch',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
level: 'context',
|
|
24
|
+
labelKey: levelKey('context'),
|
|
25
|
+
labelFallback: 'Context',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
level: 'container',
|
|
29
|
+
labelKey: levelKey('container'),
|
|
30
|
+
labelFallback: 'Container',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
level: 'component',
|
|
34
|
+
labelKey: levelKey('component'),
|
|
35
|
+
labelFallback: 'Component',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// The fourth C. Recorded, exported and readable; judged by no rule — see
|
|
39
|
+
// the header, and `rules.ts` on why an empty forbidden list is data that
|
|
40
|
+
// can never fire.
|
|
41
|
+
level: 'code',
|
|
42
|
+
labelKey: levelKey('code'),
|
|
43
|
+
labelFallback: 'Code',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
package/dist/morph.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type MorphSpec } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
import { C4NodeElementModel, type C4NodeKind } from '@formicoidea/labre-core/model';
|
|
3
|
+
import type { GfxPrimitiveElementModel } from '@formicoidea/labre-core/std/gfx';
|
|
4
|
+
/**
|
|
5
|
+
* What a C4 artefact may BECOME — the declaration behind the "Change type"
|
|
6
|
+
* dropdown on a selected component's contextual toolbar.
|
|
7
|
+
*
|
|
8
|
+
* ## Families, and the one thing that makes them safe here
|
|
9
|
+
*
|
|
10
|
+
* A person and an external person are the same actor, said with a different
|
|
11
|
+
* ownership; a container, a database, a mobile app and a browser app are the
|
|
12
|
+
* same level of the model, drawn with different silhouettes. Realising halfway
|
|
13
|
+
* through a container diagram that the box should have been the cylinder is
|
|
14
|
+
* modelling, not a mistake — and the only way through it today is delete,
|
|
15
|
+
* re-draw, re-connect, and retype three tiers of words.
|
|
16
|
+
*
|
|
17
|
+
* The three families below are DATA. Nothing derives them, and the role tree
|
|
18
|
+
* would have been the wrong source in the same way it is for BPMN: `roleIsA`
|
|
19
|
+
* makes `c4:database` a `c4:container`, so a derivation would reach in one
|
|
20
|
+
* direction and not the other, and it says nothing at all about
|
|
21
|
+
* `person`/`person-ext`, which are ONE role and still two artefacts.
|
|
22
|
+
*
|
|
23
|
+
* What makes these three families cheap is a property they happen to have and
|
|
24
|
+
* a later one may not: **every member of a family lays its words out
|
|
25
|
+
* identically**. `c4TierBoxes` differs on exactly one thing — the person's head
|
|
26
|
+
* clearance, which both people share — and `NODE_SIZE` on exactly one, the
|
|
27
|
+
* person's taller silhouette, which both people also share. So a morph inside
|
|
28
|
+
* a family needs no re-layout at all: the three tiers stay where they are, the
|
|
29
|
+
* group's derived box stays what it was, and the geometry a morph promises not
|
|
30
|
+
* to touch is genuinely untouched. `component` is deliberately in NO family
|
|
31
|
+
* partly for that reason and mostly for a better one: a component is a part of
|
|
32
|
+
* a container, not another drawing of one, and offering the swap would invite
|
|
33
|
+
* a diagram that mixes two levels of the model — the one thing C4 exists to
|
|
34
|
+
* stop. The two boundaries are frames rather than artefacts and are excluded
|
|
35
|
+
* for the reason `roles.ts` gives.
|
|
36
|
+
*/
|
|
37
|
+
export declare const C4_MORPH_FAMILIES: readonly (readonly C4NodeKind[])[];
|
|
38
|
+
/**
|
|
39
|
+
* The `c4Node` shape a selected GROUP is the component of — `undefined` when
|
|
40
|
+
* the group is not one.
|
|
41
|
+
*
|
|
42
|
+
* A C4 component is a native `group` holding the shape and its three lines of
|
|
43
|
+
* words (PO recette, 28/08/2026), so what a click selects is the group and what
|
|
44
|
+
* carries `kind` is the shape. This is the whole of the indirection, and it is
|
|
45
|
+
* also the gate: a plain group somebody lassoed round three rectangles, a
|
|
46
|
+
* Wardley component (a group of a circle and its label) and a group of two C4
|
|
47
|
+
* components all answer `undefined`, so none of them is ever offered the menu.
|
|
48
|
+
*
|
|
49
|
+
* TWO shapes is a refusal rather than a first-wins pick. A group holding two
|
|
50
|
+
* components is a group somebody made of two components — morphing "it" would
|
|
51
|
+
* mean picking one of them by document order, and the honest answer to an
|
|
52
|
+
* ambiguous selection is the same one the generic module gives to a mixed
|
|
53
|
+
* family: nothing.
|
|
54
|
+
*/
|
|
55
|
+
export declare function c4NodeOfComponent(model: GfxPrimitiveElementModel): C4NodeElementModel | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The name a component should carry once its shape has morphed — or `null`
|
|
58
|
+
* when the name is the AUTHOR's and must not be touched.
|
|
59
|
+
*
|
|
60
|
+
* Exactly one case rewrites: the title is the source kind's own creation
|
|
61
|
+
* prompt, letter for letter, which is what a component nobody has named still
|
|
62
|
+
* says. Everything else — a name typed over it, a prompt with a word added, a
|
|
63
|
+
* cleared title, another kind's prompt — is content, and content survives a
|
|
64
|
+
* morph untouched.
|
|
65
|
+
*
|
|
66
|
+
* This is the tier the type line does NOT cover, and the only one where a user
|
|
67
|
+
* sees the contradiction: {@link NODE_LABEL} differs across the container
|
|
68
|
+
* family where `C4_TYPE_WORD` does not, so a fresh container morphed to a
|
|
69
|
+
* database would otherwise be a cylinder captioned "Container".
|
|
70
|
+
*
|
|
71
|
+
* Pure and total over every string, like its sibling in `type-line.ts`: the
|
|
72
|
+
* input is a canvas text element somebody may have typed anything into.
|
|
73
|
+
*/
|
|
74
|
+
export declare function c4MorphedTitle(from: C4NodeKind, to: C4NodeKind, rawText: string | null | undefined): string | null;
|
|
75
|
+
/**
|
|
76
|
+
* C4's morph declaration, handed to the generic `morphToolbarConfig`.
|
|
77
|
+
*
|
|
78
|
+
* `modelType` is the native `GroupElementModel` and not `C4NodeElementModel`,
|
|
79
|
+
* because the group is what a click on a component selects. Everything that
|
|
80
|
+
* makes that safe is in {@link c4NodeOfComponent}: the toolbar's own
|
|
81
|
+
* homogeneity test only proves that every selected element is A group, and the
|
|
82
|
+
* resolution proves that every one of them is one of OURS.
|
|
83
|
+
*
|
|
84
|
+
* `propsOf` is the shipped creation builder minus `type` / `xywh` / `text`
|
|
85
|
+
* ({@link c4MorphProps}), so the palette and the morph cannot disagree about
|
|
86
|
+
* what a database looks like; `clearOf` is empty on today's table and derived
|
|
87
|
+
* rather than assumed so (`presets.ts`).
|
|
88
|
+
*/
|
|
89
|
+
export declare const C4_MORPH_SPEC: MorphSpec<C4NodeKind>;
|