@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,188 @@
1
+ import { DESCRIPTION_FONT_SIZE, DESCRIPTION_LINES, DESCRIPTION_PLACEHOLDER, PERSON_BODY_TOP, TIER_LINE_HEIGHT, TIER_MARGIN, TIER_SIDE_INSET, TITLE_FONT_SIZE, TITLE_LINES, TITLE_TYPE_GAP, TYPE_DESCRIPTION_GAP, TYPE_FONT_SIZE, } from './consts.js';
2
+ import { C4_ROLE } from './roles.js';
3
+ import { technologyOfTypeLine, TYPE_TECHNOLOGY_PLACEHOLDER } from './type-line.js';
4
+ /**
5
+ * The three text boxes of a component, laid out against the node's own box.
6
+ *
7
+ * ## The rhythm
8
+ *
9
+ * A margin, the name over two lines, a small gap, the type line, a wider gap,
10
+ * the description over two, and the same margin again — which is exactly what
11
+ * {@link NODE_BOX} is tall enough for, because the box is derived from this
12
+ * stack rather than the stack fitted into the box. So the default element is
13
+ * neither cramped nor half empty, and the two gaps say what they mean: the name
14
+ * and its type line are one heading, the sentence under them is a second
15
+ * statement.
16
+ *
17
+ * The tiers are stacked by walking DOWN — each one placed under the last plus
18
+ * its gap — rather than by six independent offsets. Six offsets is six chances
19
+ * for two tiers to overlap; a walk cannot produce one.
20
+ *
21
+ * ## The person
22
+ *
23
+ * `bodyTop` is the one asymmetry and it is the stencil's: a person's head stands
24
+ * clear ABOVE a body of the standard height, and its words are laid out in the
25
+ * BODY (`v:textRect` is the body box exactly), so the whole stack starts below
26
+ * the head rather than across it.
27
+ *
28
+ * ## What this is not
29
+ *
30
+ * A creation-time answer and nothing more. The tiers are real elements from the
31
+ * moment they are drawn: an author who moves one has moved it, and nothing here
32
+ * runs again to put it back. It is also proportional in the one direction that
33
+ * matters — a node dragged taller keeps its margins where they were, because
34
+ * they are absolutes; only the person's head, which is a picture, scales.
35
+ */
36
+ export function c4TierBoxes(kind, x, y, w, h) {
37
+ const bodyTop = kind === 'person' || kind === 'person-ext' ? h * PERSON_BODY_TOP : 0;
38
+ const inset = w * TIER_SIDE_INSET;
39
+ const width = w - inset * 2;
40
+ let top = bodyTop + TIER_MARGIN;
41
+ const tier = (fontSize, lines, gapAfter) => {
42
+ const box = {
43
+ x: x + inset,
44
+ y: y + top,
45
+ w: width,
46
+ h: fontSize * TIER_LINE_HEIGHT * lines,
47
+ };
48
+ top += box.h + gapAfter;
49
+ return box;
50
+ };
51
+ return {
52
+ title: tier(TITLE_FONT_SIZE, TITLE_LINES, TITLE_TYPE_GAP),
53
+ typeLine: tier(TYPE_FONT_SIZE, 1, TYPE_DESCRIPTION_GAP),
54
+ description: tier(DESCRIPTION_FONT_SIZE, DESCRIPTION_LINES, 0),
55
+ };
56
+ }
57
+ /**
58
+ * The tiers of the component a node belongs to — `{}` when it belongs to none.
59
+ *
60
+ * ## Group membership, then roles
61
+ *
62
+ * The group answers "which words are THIS node's" — two containers side by side
63
+ * both have a `[Container: …]` under them, and only the grouping says which is
64
+ * which. The role then answers "which of these words is the type line", which
65
+ * position in `children` cannot: a group's child order is an implementation
66
+ * detail that a reorder, a copy or a regroup rewrites, while a role is written
67
+ * on the element and travels with it.
68
+ *
69
+ * ## What a bare node resolves to, and why that is the right answer
70
+ *
71
+ * `{}` — no name, no technology, no description. Which is exactly what happens
72
+ * to a node whose group was released (native "ungroup"), to one whose texts were
73
+ * deleted, and to one drawn before this change. None of those is an error and
74
+ * none of them is guessed at: an element with no words on it states nothing, and
75
+ * the export says so by writing nothing. The picture is still a C4 element — the
76
+ * role is on the shape and survives everything.
77
+ *
78
+ * The NAME is the one tier with somewhere else to look, and only for the last of
79
+ * those three: an element drawn before the title became a child keeps its name
80
+ * in the shape's own inner text. See {@link c4StatedName}.
81
+ *
82
+ * The FIRST group holding the node wins, and the first text of each role in it.
83
+ * Groups nest, so a component grouped again inside a bigger group has two
84
+ * ancestors; document order picks the one written first, which is the innermost
85
+ * the creation site made.
86
+ */
87
+ export function c4ComponentTiers(nodeId, groups, texts) {
88
+ const siblings = new Set(c4ComponentSiblings(nodeId, groups));
89
+ if (siblings.size === 0)
90
+ return {};
91
+ const tiers = {};
92
+ for (const text of texts) {
93
+ if (!siblings.has(text.id))
94
+ continue;
95
+ if (text.role === C4_ROLE.title)
96
+ tiers.title ??= text;
97
+ else if (text.role === C4_ROLE['type-line'])
98
+ tiers.typeLine ??= text;
99
+ else if (text.role === C4_ROLE.description)
100
+ tiers.description ??= text;
101
+ }
102
+ return tiers;
103
+ }
104
+ /**
105
+ * Everything grouped with this element — `[]` when it is grouped with nothing.
106
+ *
107
+ * The other direction of the same question {@link c4ComponentTiers} asks, and
108
+ * the one the commit hook needs: given a type line somebody has just finished
109
+ * typing into, which shape does it belong to, so its kind can supply the word?
110
+ *
111
+ * The element itself is among the siblings, which is what the id list actually
112
+ * says and what saves the caller from reasoning about whether it was excluded.
113
+ */
114
+ export function c4ComponentSiblings(elementId, groups) {
115
+ const group = groups.find(candidate => candidate.childIds.includes(elementId));
116
+ return group ? group.childIds : [];
117
+ }
118
+ /* ── What a tier actually states ───────────────────────────────────────── */
119
+ /** Whatever a tier says, as a plain trimmed string. */
120
+ export function c4TierText(tier) {
121
+ const value = tier?.text;
122
+ if (value === null || value === undefined)
123
+ return '';
124
+ return String(value).trim();
125
+ }
126
+ /**
127
+ * The NAME a component states — its `c4:title` tier if it has one, and the
128
+ * shape's own inner text if it has not.
129
+ *
130
+ * ## No placeholder suppression, unlike the other two
131
+ *
132
+ * A fresh element's title reads `Container`, and that goes into the export
133
+ * verbatim. It is not a prompt standing in for a value the way
134
+ * `[Container: technology]` is: an unnamed container IS a container, and
135
+ * `Container(x, "Container")` is a true statement about a box somebody drew and
136
+ * has not named yet. Blanking it would hand the reader `?` instead — less
137
+ * information, not more honesty. The other two tiers suppress their prompts
138
+ * because "built with a technology called technology" is not true of anything.
139
+ *
140
+ * ## The fallback, and who needs it
141
+ *
142
+ * An element drawn before 28/08/2026 keeps its name in the SHAPE's native inner
143
+ * text, which is where that iteration put it, and it has no title child at all.
144
+ * That is the whole of the compatibility story and it costs one `??`: nothing is
145
+ * migrated, nothing is rewritten, and such an element exports exactly as it
146
+ * always did. The test is EXISTENCE of the tier, not whether it is empty — a
147
+ * component whose title the author deliberately cleared has been cleared, and
148
+ * reaching past it to a shape text that is not there either would say nothing
149
+ * different anyway.
150
+ */
151
+ export function c4StatedName(tiers, shapeText) {
152
+ if (tiers.title)
153
+ return c4TierText(tiers.title);
154
+ return shapeText === null || shapeText === undefined
155
+ ? ''
156
+ : String(shapeText).trim();
157
+ }
158
+ /**
159
+ * The technology a component STATES — `''` when it states none.
160
+ *
161
+ * The one place the creation placeholder is read as "nothing yet". Every tier
162
+ * exists from the moment a component is drawn, so an element nobody has typed on
163
+ * carries a literal `[Container: technology]`; exporting that as a technology
164
+ * would put the word "technology" in the technology slot of a file somebody is
165
+ * about to paste into a renderer.
166
+ *
167
+ * Deliberately NOT done in `technologyOfTypeLine`, which the commit hook also
168
+ * calls: normalising the placeholder to "nothing" would let a focus-and-blur
169
+ * silently rewrite `[Container: technology]` to `[Container]` and eat the
170
+ * stencil's own prompt. Reading and rewriting are different questions, and only
171
+ * the reader gets to be opinionated.
172
+ *
173
+ * The comparison is on the WORD rather than on the whole line, so it holds for
174
+ * every kind and for a placeholder an author moved brackets around.
175
+ */
176
+ export function c4StatedTechnology(tier) {
177
+ const technology = technologyOfTypeLine(c4TierText(tier));
178
+ return technology.toLowerCase() === TYPE_TECHNOLOGY_PLACEHOLDER
179
+ ? ''
180
+ : technology;
181
+ }
182
+ /** The description a component STATES — `''` for the untouched placeholder. */
183
+ export function c4StatedDescription(tier) {
184
+ const description = c4TierText(tier);
185
+ return description.toLowerCase() === DESCRIPTION_PLACEHOLDER
186
+ ? ''
187
+ : description;
188
+ }
@@ -0,0 +1,331 @@
1
+ import type { C4BoundaryVariant, C4NodeKind } from '@formicoidea/labre-core/model';
2
+ /**
3
+ * Visual constants for the C4 pack.
4
+ *
5
+ * Unlike BPMN — a black-and-white notation where the SHAPE carries the meaning
6
+ * and colour is decoration — C4's official stencil is a colour code: the four
7
+ * levels are four blues, from the near-navy of a person down to the pale wash of
8
+ * a component, and anything outside the scope of the diagram is grey. That is
9
+ * the one thing a reader uses to tell a container from a component when both are
10
+ * rounded rectangles with words in them, so the palette below IS the notation
11
+ * and is written down as data rather than left to whoever draws next.
12
+ *
13
+ * Every hex, every size and every radius below is read off the PO's own
14
+ * reference model — the Visio-exported `C4Model_default.svg` stencil — rather
15
+ * than approximated, and the file's units are carried through at ×2 so that a
16
+ * default node is a comfortable size on a canvas. Where a number is a fraction
17
+ * of the node box rather than an absolute, it lives in the renderer beside the
18
+ * path it shapes. Every value here is a creation-time DEFAULT; each one is an
19
+ * editable shape property afterwards, exactly as in BPMN.
20
+ */
21
+ /** The stencil's own unit → model unit factor. Every absolute below is ×2. */
22
+ export declare const STENCIL_SCALE = 2;
23
+ /** One kind's three colours: the body, the line round it, and the words in it. */
24
+ export interface C4NodePaint {
25
+ fill: string;
26
+ /** The border — the fill, taken a step darker. */
27
+ border: string;
28
+ /** The inner text, chosen for contrast against {@link fill}. */
29
+ text: string;
30
+ }
31
+ /**
32
+ * The palette, per kind. TOTAL over {@link C4NodeKind} by its type, so a kind
33
+ * added to the model cannot land without being given a colour.
34
+ *
35
+ * The four levels run light as they go IN: a person is the darkest thing on the
36
+ * page (#08427b), a software system one step lighter (#1168bd), every container
37
+ * lighter again (#438dd5) and a component palest of all (#85bbf0) — which is
38
+ * also the one that takes black text, because white on that wash is unreadable.
39
+ * `database`, `mobile` and `browser` are CONTAINERS and take the container's
40
+ * colour exactly: what makes them different is the silhouette the renderer
41
+ * draws, never the level, and a fourth blue would say otherwise.
42
+ *
43
+ * Every border is the stencil's own darker shade of its fill, lifted verbatim
44
+ * from the reference model's stylesheet (`.st1`, `.st5`, `.st6`, `.st7`,
45
+ * `.st22`) rather than darkened by eye — which is what makes the two decorated
46
+ * containers work at all: `mobile` and `browser` paint their BEZEL in the border
47
+ * colour and their SCREEN in the fill, so the pair has to be the stencil's pair.
48
+ */
49
+ export declare const NODE_PALETTE: Record<C4NodeKind, C4NodePaint>;
50
+ /** Border weight of every node — one line (stencil `stroke-width:1`), ×2. */
51
+ export declare const NODE_STROKE_WIDTH = 2;
52
+ /**
53
+ * Corner radius of each kind's OUTER body, in model units.
54
+ *
55
+ * A table rather than one number, because the stencil is not uniform and the
56
+ * difference is legible: `system`, `system-ext`, `container` and `component` are
57
+ * plain `<rect>`s with **no `rx` at all** — square corners — while the two
58
+ * decorated containers are rounded, the phone noticeably (`rx="4.252"`) and the
59
+ * browser window barely (`rx="1.4173"`). The three glyph-bodied kinds carry `0`
60
+ * because their native rect paints nothing: a person's shoulders and a
61
+ * cylinder's lid are curves the glyph draws itself, in proportion to the box.
62
+ *
63
+ * This corrects the pack's first pass, which rounded every kind at 10 — a
64
+ * plausible house style, and one the reference model does not draw.
65
+ */
66
+ export declare const NODE_RADIUS: Record<C4NodeKind, number>;
67
+ /**
68
+ * The three text tiers, at the stencil's own sizes (10 / 6 / 8) ×2.
69
+ *
70
+ * All three are creation-time DEFAULTS the author can change afterwards, and all
71
+ * three are now CANVAS TEXT ELEMENTS: since the PO's recette of 28/08/2026 a C4
72
+ * component is a group holding the shape and its three lines of words — the
73
+ * name included — so every tier is ordinary text with its own toolbar rather
74
+ * than something a renderer painted and nobody could type on.
75
+ *
76
+ * The name was the shape's native inner text for one iteration and is not any
77
+ * more, which is the second half of that recette: two kinds of text in one
78
+ * component meant two editors, two toolbars and two sets of rules for the same
79
+ * three lines. Now there is one of each.
80
+ *
81
+ * The ladder 20 / 16 / 12 is the notation and not typography — the type line is
82
+ * smaller than the name on every C4 diagram ever drawn — but it is a ladder an
83
+ * author can climb off, which is the price of letting them write on the picture.
84
+ */
85
+ export declare const TITLE_FONT_SIZE: number;
86
+ export declare const TYPE_FONT_SIZE: number;
87
+ export declare const DESCRIPTION_FONT_SIZE: number;
88
+ /**
89
+ * One line box, as a multiple of its own font size — what the creation site
90
+ * measures a tier's HEIGHT in.
91
+ *
92
+ * The stencil states its tiers as baselines, which is the right unit for a
93
+ * renderer painting into a box and the wrong one for a creation site placing an
94
+ * element: a text element is a rectangle, and where its first baseline lands
95
+ * inside that rectangle is the text renderer's business, not this file's. So the
96
+ * stencil's baseline steps are re-read here as line boxes, which is the same
97
+ * geometry counted from the other end.
98
+ */
99
+ export declare const TIER_LINE_HEIGHT = 1.2;
100
+ /**
101
+ * How many lines each of the two wrapping tiers opens with.
102
+ *
103
+ * The title gets TWO, and that is what grew the element (see {@link NODE_BOX}):
104
+ * "Internet Banking System" is 23 characters, and 23 characters at 20px do not
105
+ * fit across 187 units of usable width. A one-line title box would have meant
106
+ * every real system name spilling out of its own tier on the day it was typed —
107
+ * which is precisely the cramped stack the PO's recette was about.
108
+ *
109
+ * The description gets two as well, which is what the stencil's own sentences
110
+ * run to. Neither is a limit: a longer text wraps inside the tier's width and
111
+ * grows the box downward, and the group grows with it, so a component keeps
112
+ * containing its own words.
113
+ */
114
+ export declare const TITLE_LINES = 2;
115
+ export declare const DESCRIPTION_LINES = 2;
116
+ /**
117
+ * The vertical rhythm of the stack, in model units.
118
+ *
119
+ * Absolutes rather than multiples of a font size, and deliberately so: what the
120
+ * eye reads here is the SPACE between three blocks of different sizes, and a gap
121
+ * expressed as 0.7em of whichever tier happens to be below it changes meaning
122
+ * every time somebody resizes one of them. These three numbers are the layout.
123
+ *
124
+ * The two gaps are different on purpose. The name and its type line are ONE
125
+ * heading — `Web Application` / `[Container: Java]` is a single statement over
126
+ * two lines — so they sit close. The description is a different statement, and
127
+ * the wider gap under the type line is the stencil's own blank line: it is what
128
+ * keeps a sentence from reading as a fourth tier of the heading.
129
+ *
130
+ * {@link TIER_MARGIN} is equal top and bottom, which is what makes the stack sit
131
+ * in its box rather than in the top of it.
132
+ */
133
+ export declare const TIER_MARGIN = 24;
134
+ export declare const TITLE_TYPE_GAP = 8;
135
+ export declare const TYPE_DESCRIPTION_GAP = 16;
136
+ /** Side inset the three text tiers sit within, as a fraction of the node width. */
137
+ export declare const TIER_SIDE_INSET = 0.06;
138
+ /**
139
+ * The height of the three tiers and the two gaps between them — the number the
140
+ * default element size is DERIVED from rather than fitted to.
141
+ *
142
+ * 48 + 8 + 14.4 + 16 + 38.4 = 124.8. Written as the sum it is so that changing
143
+ * a font size or a gap moves the box with it: a rhythm and a footprint that can
144
+ * disagree is a rhythm that will.
145
+ */
146
+ export declare const TIER_STACK_HEIGHT: number;
147
+ /**
148
+ * The sentence a fresh description prompts the author with.
149
+ *
150
+ * The stencil's own placeholder, and a PROMPT rather than a value: every tier of
151
+ * a C4 component exists from the moment it is drawn (PO arbitration,
152
+ * 28/08/2026), so the author meets three lines of stencil rather than a box and
153
+ * two invisible slots somebody has to tell them about. The exporter compares
154
+ * against it to decide that nothing has been stated yet — see
155
+ * `C4_TYPE_PLACEHOLDER` in `type-line.ts` for the same call on the other tier.
156
+ */
157
+ export declare const DESCRIPTION_PLACEHOLDER = "description";
158
+ /**
159
+ * How far the person's head stands clear ABOVE its body, in model units.
160
+ *
161
+ * The stencil's own `47.767` at ×2, solved off the silhouette path (`mID 1`):
162
+ * its head arc is drawn with `large-arc-flag=1` about a centre 21.26 units above
163
+ * the body's top edge, with `ry=26.504`, so the head clears the body by the sum
164
+ * of the two. Independent of how tall the BODY is, which is what lets the body
165
+ * grow with the text rhythm below without moving the head.
166
+ */
167
+ export declare const PERSON_HEAD_CLEARANCE: number;
168
+ /**
169
+ * Default node sizes (model units) per kind.
170
+ *
171
+ * ## One footprint, and one exception the stencil itself draws
172
+ *
173
+ * The reference model gives every element the SAME box — `106.3 × 74.409`, a
174
+ * `v:textRect` repeated verbatim on the system, the container, the component,
175
+ * the database, the phone and the browser window. Seven of the nine kinds take
176
+ * one footprint, and a row of C4 elements lining up without anybody arranging
177
+ * them is not a convenience, it is what makes a level readable.
178
+ *
179
+ * ## Why the box is taller than the stencil's, and by exactly how much
180
+ *
181
+ * The WIDTH is the stencil's, untouched: `106.3 × 2 = 212.6`. Widening it would
182
+ * change every glyph with it — a person's head radius is derived from the width
183
+ * — and the reference proportions are the one thing the recette of 27/08 was
184
+ * about.
185
+ *
186
+ * The HEIGHT is derived from the words instead, which is the PO's call of
187
+ * 28/08/2026: grow the shapes if that is what it takes to have room to write.
188
+ * `74.409 × 2 = 148.8` was the stencil's textRect for a box holding a name it
189
+ * could paint in a single line at whatever size it liked. This one holds three
190
+ * REAL text elements, at fixed sizes, with margins and gaps a reader can see —
191
+ * and the title alone needs two lines, because a system name is routinely longer
192
+ * than 187 units of usable width at 20px. So the height is
193
+ * {@link TIER_STACK_HEIGHT} plus a margin at each end: **212.6 × 172.8**, up
194
+ * from 212.6 × 148.8.
195
+ *
196
+ * Derived rather than chosen, so the box can never disagree with what it holds:
197
+ * change a tier's size or a gap and the footprint follows.
198
+ *
199
+ * ## The person
200
+ *
201
+ * `person` and `person-ext` are the exception, and it is the FILE's exception,
202
+ * not a preference. Their silhouette is one path (`mID 1`) whose head arc is
203
+ * drawn about a centre above the body's top edge, so the head stands
204
+ * {@link PERSON_HEAD_CLEARANCE} clear of a body that is itself the standard box.
205
+ * The stencil's own sheet shows it: the person's group is translated further
206
+ * down the page than the system beside it, precisely to make room. The body
207
+ * grows with everything else, so the person is now **212.6 × 268.3**.
208
+ *
209
+ * Forcing a person into the boxed footprint was considered and rejected: the
210
+ * head is a CIRCLE (`rx 26.362`, `ry 26.504`) and stays one only at the
211
+ * silhouette's own aspect ratio — squeezed into a box far wider than it is tall
212
+ * it becomes a flat ellipse, which is the one thing about a C4 person everybody
213
+ * recognises and the one thing that would then be wrong.
214
+ */
215
+ export declare const NODE_BOX: {
216
+ readonly w: number;
217
+ readonly h: number;
218
+ };
219
+ /** The person's full silhouette — the standard body, plus the head above it. */
220
+ export declare const PERSON_BOX: {
221
+ readonly w: number;
222
+ readonly h: number;
223
+ };
224
+ /**
225
+ * Where the person's body top edge sits, as a fraction of the whole silhouette.
226
+ *
227
+ * Derived from the two boxes rather than restated as the stencil's own ratio, so
228
+ * that growing the body cannot leave this pointing at the middle of the head.
229
+ * The renderer derives the same edge from the head RADIUS instead
230
+ * (`PERSON.bodyTopPerHead`), which is the form that survives an element dragged
231
+ * to an aspect ratio the head has to be clamped at; the two agree at the default
232
+ * size. This one is what the creation site needs: it has a box and no glyph, and
233
+ * it has to know where the words can go.
234
+ */
235
+ export declare const PERSON_BODY_TOP: number;
236
+ export declare const NODE_SIZE: Record<C4NodeKind, {
237
+ w: number;
238
+ h: number;
239
+ }>;
240
+ /**
241
+ * Default inner text per kind.
242
+ *
243
+ * Every kind carries one, unlike BPMN — where an event's meaning IS its glyph
244
+ * and the spec puts its name outside the shape. Here the box is the same box at
245
+ * three of the four levels, so a C4 element with nothing written in it says
246
+ * nothing at all: the words are the artefact.
247
+ */
248
+ export declare const NODE_LABEL: Record<C4NodeKind, string>;
249
+ export declare const FONT_FAMILY = "Inter, sans-serif";
250
+ /** The card — the same white every framework background paints. */
251
+ export declare const BOARD_CARD_FILL = "#ffffff";
252
+ export declare const BOARD_CARD_BORDER = "#d5d9e0";
253
+ export declare const BOARD_BORDER_WIDTH = 1.5;
254
+ export declare const BOARD_CORNER_RADIUS = 12;
255
+ export declare const BOARD_TITLE_FONT_SIZE = 20;
256
+ export declare const BOARD_TITLE_COLOR = "#262626";
257
+ /**
258
+ * The size a fresh board is created at, and the room its furniture takes.
259
+ *
260
+ * The Context Map board's own numbers: a C4 diagram is the same kind of object —
261
+ * a sheet you spread out and add boxes to as the system is discovered — so it
262
+ * starts at the same size and grows the same way. The top margin is deeper than
263
+ * the other three because that is where the title is written.
264
+ */
265
+ export declare const BOARD_REF_WIDTH = 1400;
266
+ export declare const BOARD_REF_HEIGHT = 900;
267
+ export declare const BOARD_MARGIN = 24;
268
+ export declare const BOARD_TITLE_MARGIN = 56;
269
+ /**
270
+ * The boundary's frame and its name, at the stencil's own values ×2 (`.st20`
271
+ * and `.st8` in the reference model).
272
+ *
273
+ * All four numbers changed with the PO's recette of 27/08/2026, and all four
274
+ * were house style before it: the frame was a mid grey at weight 2 with rounded
275
+ * corners, and the stencil draws `#444444` at weight 1 with SQUARE ones. The
276
+ * name is black rather than grey and a size larger, which is what it takes to
277
+ * read a boundary's name over the diagram it is drawn on top of.
278
+ */
279
+ export declare const BOUNDARY_STROKE = "#444444";
280
+ export declare const BOUNDARY_WIDTH: number;
281
+ export declare const BOUNDARY_CORNER_RADIUS = 0;
282
+ /** The dash, in model units: the stencil's `stroke-dasharray:7.5,4.5`, ×2. */
283
+ export declare const BOUNDARY_DASH: readonly [number, number];
284
+ export declare const BOUNDARY_NAME_FONT_SIZE: number;
285
+ export declare const BOUNDARY_NAME_COLOR = "#000000";
286
+ /**
287
+ * The bracket line under a boundary's name — `[Software System]`, `[Container]`
288
+ * — at the stencil's own 6px ×2, on the same 1.917em baseline step it uses.
289
+ *
290
+ * Vocabulary, not user text: the words come from the declaration's `labelKey`
291
+ * and are translatable through the host's catalogue, which is also what keeps
292
+ * them out of the in-place editor. A boundary's NAME is the author's; what kind
293
+ * of boundary it is, is the notation's.
294
+ */
295
+ export declare const BOUNDARY_TYPE_FONT_SIZE: number;
296
+ export declare const BOUNDARY_TYPE_STEP: number;
297
+ /** How far above the bottom edge of the plot the name's baseline sits. */
298
+ export declare const BOUNDARY_NAME_INSET = 8;
299
+ export declare const BOUNDARY_REF_WIDTH = 520;
300
+ export declare const BOUNDARY_REF_HEIGHT = 360;
301
+ /**
302
+ * The boundary's inset. Small and equal on all four sides: unlike a board, a
303
+ * boundary has no furniture to make room for — the name is written INSIDE the
304
+ * bottom-left corner of the plot, over the diagram, exactly as C4 draws it.
305
+ */
306
+ export declare const BOUNDARY_MARGIN = 12;
307
+ /**
308
+ * The wording a fresh boundary is named with, per variant.
309
+ *
310
+ * The variant changes the DEFAULT NAME and nothing else: both are the same
311
+ * dashed rectangle, and C4 tells them apart by what is written under the corner.
312
+ * Read by the creation site (which writes `name`), never by the renderer — the
313
+ * declaration draws whatever the user's own `name` says, so a boundary renamed
314
+ * on the canvas keeps its words whatever its variant.
315
+ */
316
+ export declare const BOUNDARY_LABEL: Record<C4BoundaryVariant, string>;
317
+ /**
318
+ * Relationship connector preset — the dashed arrow C4 draws between elements.
319
+ *
320
+ * DASHED and not solid, which is the stencil's own choice and worth keeping:
321
+ * every line on a C4 diagram is a relationship, so the dash is not a
322
+ * distinction between two kinds of line but the house style of the one kind
323
+ * there is. Grey rather than black for the same reason the boundary is: the
324
+ * boxes are the statement, the arrows are the grammar between them.
325
+ *
326
+ * `#444444` at weight 1.5 is the stencil's `.st15` (`stroke:#444444`,
327
+ * `stroke-width:0.75`) at ×2, and it is the SAME grey the boundary frame is
328
+ * drawn in — one neutral for everything that is not an element.
329
+ */
330
+ export declare const RELATIONSHIP_STROKE = "#444444";
331
+ export declare const RELATIONSHIP_WIDTH: number;