@formicoidea/labre-framework-c4 0.36.0 → 0.38.1
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/reading.d.ts +8 -0
- package/dist/reading.js +85 -0
- package/dist/templates.d.ts +2 -0
- package/dist/templates.js +68 -0
- package/dist/translations.js +6 -1
- package/dist/view.d.ts +3 -2
- package/dist/view.js +16 -3
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -14,3 +14,4 @@ export { C4_ELEMENT_MATRIX, C4_RELATIONSHIP_MATRIX, C4_RULES, } from './rules.js
|
|
|
14
14
|
export { c4CommandIcons, c4Commands } from './commands.js';
|
|
15
15
|
export { c4TranslationEntries } from './translations.js';
|
|
16
16
|
export { C4_AUTO_LEGEND } from './legend.js';
|
|
17
|
+
export { C4_COMPONENT_READING, C4_CONTAINER_READING, C4_PERSON_READING, C4_READINGS, C4_SYSTEM_READING, } from './reading.js';
|
package/dist/index.js
CHANGED
|
@@ -54,3 +54,4 @@ export { c4TranslationEntries } from './translations.js';
|
|
|
54
54
|
// the same reason the two background declarations are: it answers "what does
|
|
55
55
|
// this framework document about itself" without a canvas.
|
|
56
56
|
export { C4_AUTO_LEGEND } from './legend.js';
|
|
57
|
+
export { C4_COMPONENT_READING, C4_CONTAINER_READING, C4_PERSON_READING, C4_READINGS, C4_SYSTEM_READING, } from './reading.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReadingProfile } from '@formicoidea/labre-core/blocks/surface';
|
|
2
|
+
export declare const C4_PERSON_READING: ReadingProfile;
|
|
3
|
+
export declare const C4_SYSTEM_READING: ReadingProfile;
|
|
4
|
+
/** Covers `c4:database`, the one specialisation in the pack. */
|
|
5
|
+
export declare const C4_CONTAINER_READING: ReadingProfile;
|
|
6
|
+
export declare const C4_COMPONENT_READING: ReadingProfile;
|
|
7
|
+
/** Every C4 profile, in the order the view extension registers them. */
|
|
8
|
+
export declare const C4_READINGS: readonly ReadingProfile[];
|
package/dist/reading.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { C4_ROLE, C4_ROLES } from './roles.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a C4 diagram lets the tool read of an element (MF3, the reversed
|
|
4
|
+
* reading).
|
|
5
|
+
*
|
|
6
|
+
* DATA, like the rules and the profiles beside it: the engine
|
|
7
|
+
* (`@labre/affine-block-surface`) knows how to read a role and a typed edge — it
|
|
8
|
+
* knows nothing about C4. Everything below is already stated in `roles.ts` and
|
|
9
|
+
* is merely POINTED AT here.
|
|
10
|
+
*
|
|
11
|
+
* Registered from the FLAG-GATED view extension: reading a diagram is tooling,
|
|
12
|
+
* so a board whose C4 flag is off keeps every element it has and simply stops
|
|
13
|
+
* being read (ADR 0009).
|
|
14
|
+
*
|
|
15
|
+
* ## FOUR profiles, because the four levels are deliberately FLAT
|
|
16
|
+
*
|
|
17
|
+
* `roles.ts` argues it at length: the relation between the levels is
|
|
18
|
+
* COMPOSITION ("a container is part of a system"), not specialisation, and
|
|
19
|
+
* `roleIsA` means the second. Filing them in a chain to get one `appliesTo`
|
|
20
|
+
* would make "every rule about a system also falls on every container" true —
|
|
21
|
+
* the opposite of what C4 says — and minting a `c4:element` parent would be
|
|
22
|
+
* inventing an id the notation does not have (ADR 0007: an id is forever).
|
|
23
|
+
*
|
|
24
|
+
* So there is one profile per level, each with an id of its own (the DI keys on
|
|
25
|
+
* it and throws on a duplicate). `c4:database` needs none: it is the one real
|
|
26
|
+
* specialisation in the pack, a kind of container, and is read through the
|
|
27
|
+
* container's profile for free.
|
|
28
|
+
*
|
|
29
|
+
* The BOARD and the BOUNDARY (and its two children) are frames and are not read.
|
|
30
|
+
*
|
|
31
|
+
* ## The name lives on a separate element
|
|
32
|
+
*
|
|
33
|
+
* A C4 element is a composite — a shape, and three tiers of canvas text grouped
|
|
34
|
+
* with it — so every profile points `labelRole` at `c4:title`. Without it the
|
|
35
|
+
* panel would print an id at a human, and the relation lines would name the
|
|
36
|
+
* other end by id too.
|
|
37
|
+
*
|
|
38
|
+
* ## No nature, no phase
|
|
39
|
+
*
|
|
40
|
+
* C4 ships no type-3 tag pack: the levels ARE the roles, and the middle tier of
|
|
41
|
+
* the label (`c4:type-line`) already says the technology in the author's own
|
|
42
|
+
* words. And a board declares which of the three diagrams it draws, not a set of
|
|
43
|
+
* zones a position could be read against.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* The two words a relationship is read with, from the subject's end.
|
|
47
|
+
*
|
|
48
|
+
* ADR 0010 tier 1 and this role's own `direction`: the verb is "uses", so the
|
|
49
|
+
* SOURCE is the user. An edge leaving the subject names what it uses; one
|
|
50
|
+
* arriving names what uses it.
|
|
51
|
+
*/
|
|
52
|
+
const RELATIONSHIP = {
|
|
53
|
+
edgeRole: C4_ROLE.relationship,
|
|
54
|
+
sides: {
|
|
55
|
+
consumer: {
|
|
56
|
+
labelKey: 'com.labre.c4.reading.relations.consumer',
|
|
57
|
+
labelFallback: 'Used by',
|
|
58
|
+
},
|
|
59
|
+
supplier: {
|
|
60
|
+
labelKey: 'com.labre.c4.reading.relations.supplier',
|
|
61
|
+
labelFallback: 'Uses',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
/** One level, as a profile. The four below differ by `id` and `appliesTo` only. */
|
|
66
|
+
const level = (id, appliesTo) => ({
|
|
67
|
+
id,
|
|
68
|
+
framework: 'c4',
|
|
69
|
+
roles: C4_ROLES,
|
|
70
|
+
appliesTo,
|
|
71
|
+
labelRole: C4_ROLE.title,
|
|
72
|
+
relation: RELATIONSHIP,
|
|
73
|
+
});
|
|
74
|
+
export const C4_PERSON_READING = level('c4-person', C4_ROLE.person);
|
|
75
|
+
export const C4_SYSTEM_READING = level('c4-system', C4_ROLE.system);
|
|
76
|
+
/** Covers `c4:database`, the one specialisation in the pack. */
|
|
77
|
+
export const C4_CONTAINER_READING = level('c4-container', C4_ROLE.container);
|
|
78
|
+
export const C4_COMPONENT_READING = level('c4-component', C4_ROLE.component);
|
|
79
|
+
/** Every C4 profile, in the order the view extension registers them. */
|
|
80
|
+
export const C4_READINGS = [
|
|
81
|
+
C4_PERSON_READING,
|
|
82
|
+
C4_SYSTEM_READING,
|
|
83
|
+
C4_CONTAINER_READING,
|
|
84
|
+
C4_COMPONENT_READING,
|
|
85
|
+
];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { templateFromCommand, } from '@formicoidea/labre-core/gfx/template';
|
|
2
|
+
import { c4Commands } from './commands.js';
|
|
3
|
+
import { BOARD_BAND_FILL, BOARD_CARD_BORDER, BOARD_CARD_FILL, BOUNDARY_STROKE, NODE_PALETTE, } from './consts.js';
|
|
4
|
+
/**
|
|
5
|
+
* The C4 palette — DERIVED from the toolbox, one template per command.
|
|
6
|
+
*
|
|
7
|
+
* C4 was the ONE framework the Templates panel had no category for: the pack
|
|
8
|
+
* landed on 28/08/2026 with thirteen commands and nobody wrote the palette
|
|
9
|
+
* (audit of 09/09/2026, § 4.3). Writing it by hand was never an option — a C4
|
|
10
|
+
* component is five elements (a bodyless shape, three text tiers and the group
|
|
11
|
+
* that makes them one thing), created in painting order, and a hand-written
|
|
12
|
+
* restatement of that would have drifted from `actions.ts` the way every other
|
|
13
|
+
* framework's palette already had.
|
|
14
|
+
*
|
|
15
|
+
* So every entry below IS its command, run once against a recording surface, and
|
|
16
|
+
* `__tests__/templates-parity.unit.spec.ts` re-runs each one and compares. There
|
|
17
|
+
* is nothing hand-authored here to fall out of date: the day `createC4Node`
|
|
18
|
+
* changes, the palette changes with it.
|
|
19
|
+
*
|
|
20
|
+
* `relationshipTool` has no template on purpose — it ARMS the connector tool and
|
|
21
|
+
* draws nothing, so there is no artefact to record; `exportMermaid` is not an
|
|
22
|
+
* artefact command at all.
|
|
23
|
+
*/
|
|
24
|
+
/** The command a derived template is the picture of. Throws rather than skips. */
|
|
25
|
+
function byId(id) {
|
|
26
|
+
const command = c4Commands.find(entry => entry.id === id);
|
|
27
|
+
if (!command)
|
|
28
|
+
throw new Error(`[c4] templates: no command "${id}"`);
|
|
29
|
+
return command;
|
|
30
|
+
}
|
|
31
|
+
const ATTRS = 'width="100%" height="100%" viewBox="0 0 135 80" xmlns="http://www.w3.org/2000/svg"';
|
|
32
|
+
/** The two written lines every element carries, drawn as the words they are. */
|
|
33
|
+
const words = (color, y) => `<g fill="${color}" opacity="0.85"><rect x="44" y="${y}" width="47" height="4" rx="2"/><rect x="52" y="${y + 8}" width="31" height="3" rx="1.5"/></g>`;
|
|
34
|
+
/** A kind's body, in its own stencil colours — the plain box the levels share. */
|
|
35
|
+
const boxPreview = (kind, extra = '') => {
|
|
36
|
+
const p = NODE_PALETTE[kind];
|
|
37
|
+
return `<svg ${ATTRS} fill="none"><rect x="30" y="20" width="75" height="40" fill="${p.fill}" stroke="${p.border}" stroke-width="2"/>${words(p.text, 32)}${extra}</svg>`;
|
|
38
|
+
};
|
|
39
|
+
/** The silhouette: a head standing clear of the same body. */
|
|
40
|
+
const personPreview = (kind) => {
|
|
41
|
+
const p = NODE_PALETTE[kind];
|
|
42
|
+
return `<svg ${ATTRS} fill="none"><circle cx="67" cy="18" r="10" fill="${p.fill}" stroke="${p.border}" stroke-width="2"/><rect x="34" y="32" width="67" height="32" rx="14" fill="${p.fill}" stroke="${p.border}" stroke-width="2"/>${words(p.text, 42)}</svg>`;
|
|
43
|
+
};
|
|
44
|
+
/** A dashed frame, and — one level in — a second frame inside it. */
|
|
45
|
+
const boundaryPreview = (inner = '') => `<svg ${ATTRS} fill="none"><rect x="14" y="12" width="107" height="56" stroke="${BOUNDARY_STROKE}" stroke-width="2" stroke-dasharray="7 4.5"/>${inner}</svg>`;
|
|
46
|
+
const CONTAINER = NODE_PALETTE.container;
|
|
47
|
+
export const c4TemplateCategory = {
|
|
48
|
+
name: 'C4',
|
|
49
|
+
templates: [
|
|
50
|
+
templateFromCommand(byId('c4.addBoard'), `<svg ${ATTRS} fill="none"><rect x="10" y="10" width="115" height="60" rx="6" fill="${BOARD_CARD_FILL}" stroke="${BOARD_CARD_BORDER}" stroke-width="1.5"/><path d="M10 26 H125" stroke="${BOARD_CARD_BORDER}" stroke-width="1.5"/><rect x="11" y="11" width="113" height="15" fill="${BOARD_BAND_FILL}"/><rect x="20" y="15" width="40" height="6" rx="3" fill="#262626" opacity="0.55"/><rect x="22" y="36" width="34" height="20" rx="2" fill="${NODE_PALETTE.system.fill}"/><rect x="79" y="36" width="34" height="20" rx="2" fill="${CONTAINER.fill}"/><path d="M58 46 H75" stroke="#444444" stroke-width="1.6" stroke-dasharray="4 3"/></svg>`),
|
|
51
|
+
templateFromCommand(byId('c4.addPerson'), personPreview('person')),
|
|
52
|
+
templateFromCommand(byId('c4.addPersonExt'), personPreview('person-ext')),
|
|
53
|
+
templateFromCommand(byId('c4.addSystem'), boxPreview('system')),
|
|
54
|
+
templateFromCommand(byId('c4.addSystemExt'), boxPreview('system-ext')),
|
|
55
|
+
templateFromCommand(byId('c4.addContainer'), boxPreview('container')),
|
|
56
|
+
// The UML component: the same box, with two tabs down its leading edge.
|
|
57
|
+
templateFromCommand(byId('c4.addComponent'), boxPreview('component', `<g fill="${NODE_PALETTE.component.fill}" stroke="${NODE_PALETTE.component.border}" stroke-width="2"><rect x="22" y="28" width="16" height="8"/><rect x="22" y="44" width="16" height="8"/></g>`)),
|
|
58
|
+
// A cylinder, and the stencil's own container blue: a database is a
|
|
59
|
+
// container drawn as a data store, never a fourth level.
|
|
60
|
+
templateFromCommand(byId('c4.addDatabase'), `<svg ${ATTRS} fill="none"><path d="M36 22 V58 C36 62 50 65 67 65 C84 65 99 62 99 58 V22 Z" fill="${CONTAINER.fill}" stroke="${CONTAINER.border}" stroke-width="2"/><ellipse cx="67.5" cy="22" rx="31.5" ry="7" fill="${CONTAINER.fill}" stroke="${CONTAINER.border}" stroke-width="2"/>${words(CONTAINER.text, 38)}</svg>`),
|
|
61
|
+
// The two decorated containers paint their BEZEL in the border colour and
|
|
62
|
+
// their SCREEN in the fill, which is the stencil's own pair (`consts.ts`).
|
|
63
|
+
templateFromCommand(byId('c4.addMobile'), `<svg ${ATTRS} fill="none"><rect x="49" y="6" width="37" height="68" rx="7" fill="${CONTAINER.border}"/><rect x="54" y="15" width="27" height="50" fill="${CONTAINER.fill}"/><rect x="61" y="10" width="13" height="2.5" rx="1.25" fill="${CONTAINER.fill}"/></svg>`),
|
|
64
|
+
templateFromCommand(byId('c4.addBrowser'), `<svg ${ATTRS} fill="none"><rect x="20" y="14" width="95" height="52" rx="4" fill="${CONTAINER.border}"/><rect x="24" y="28" width="87" height="34" fill="${CONTAINER.fill}"/><g fill="${CONTAINER.fill}"><circle cx="29" cy="21" r="2.4"/><circle cx="37" cy="21" r="2.4"/><circle cx="45" cy="21" r="2.4"/></g></svg>`),
|
|
65
|
+
templateFromCommand(byId('c4.addSystemBoundary'), boundaryPreview()),
|
|
66
|
+
templateFromCommand(byId('c4.addContainerBoundary'), boundaryPreview(`<rect x="34" y="26" width="67" height="30" stroke="${BOUNDARY_STROKE}" stroke-width="1.6" stroke-dasharray="5 3.5"/>`)),
|
|
67
|
+
],
|
|
68
|
+
};
|
package/dist/translations.js
CHANGED
|
@@ -3,6 +3,7 @@ import { C4_BOARD_BACKGROUND, C4_BOUNDARY_BACKGROUND } from './background.js';
|
|
|
3
3
|
import { c4Commands } from './commands.js';
|
|
4
4
|
import { C4_BOARD_LEVEL_MENU } from './levels.js';
|
|
5
5
|
import { C4_PROFILES } from './profiles.js';
|
|
6
|
+
import { C4_READINGS } from './reading.js';
|
|
6
7
|
import { C4_ROLES } from './roles.js';
|
|
7
8
|
import { C4_RULES } from './rules.js';
|
|
8
9
|
/**
|
|
@@ -45,4 +46,8 @@ commandCategoryTranslationEntries(c4Commands), collectTranslationKeys('role', C4
|
|
|
45
46
|
// declaration, so walking the rules reaches keys those two lists already
|
|
46
47
|
// named. `mergeTranslationEntries` keeps the FIRST occurrence, which is what
|
|
47
48
|
// makes each key report the source it actually comes from.
|
|
48
|
-
collectTranslationKeys('rule', C4_RULES), collectTranslationKeys('profile', C4_PROFILES)
|
|
49
|
+
collectTranslationKeys('rule', C4_RULES), collectTranslationKeys('profile', C4_PROFILES),
|
|
50
|
+
// LAST, for the reason the rules are placed after the roles: a reading
|
|
51
|
+
// profile carries the framework's own `roles` too, so walking it reaches
|
|
52
|
+
// keys the lists above already named, and the first occurrence wins.
|
|
53
|
+
collectTranslationKeys('reading', C4_READINGS));
|
package/dist/view.d.ts
CHANGED
|
@@ -16,8 +16,9 @@ export declare class C4RenderViewExtension extends ViewExtensionProvider {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* C4 creation tooling — flag-gated (`c4`): the senior toolbar button, its
|
|
19
|
-
* sub-menu, the thirteen commands behind them,
|
|
20
|
-
* component's "Change type" dropdown, and the
|
|
19
|
+
* sub-menu, the thirteen commands behind them, its templates category, the
|
|
20
|
+
* board's legend button, the component's "Change type" dropdown, and the
|
|
21
|
+
* validation rules and profiles.
|
|
21
22
|
*
|
|
22
23
|
* All of it is tooling in the sense `docs/adr/0009` means: a diagram drawn while
|
|
23
24
|
* the flag was on keeps painting, stays selectable and keeps its contextual
|
package/dist/view.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { FrameworkBackgroundInteractionExtension, InterchangeExtension, morphToolbarConfig, ValidationProfileExtension, ValidationRuleExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
1
|
+
import { FrameworkBackgroundInteractionExtension, InterchangeExtension, morphToolbarConfig, ReadingProfileExtension, ValidationProfileExtension, ValidationRuleExtension, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { ViewExtensionProvider, } from '@formicoidea/labre-core/ext-loader';
|
|
3
|
+
import { extendTemplateCategory } from '@formicoidea/labre-core/gfx/template';
|
|
3
4
|
import { ToolbarModuleExtension, toolbarModuleKey, } from '@formicoidea/labre-core/shared/services';
|
|
4
5
|
import { BlockFlavourIdentifier, CommandExtension } from '@formicoidea/labre-core/std';
|
|
5
6
|
import { RoleVocabularyExtension } from '@formicoidea/labre-core/std/gfx';
|
|
@@ -14,8 +15,10 @@ import { C4NodeRendererExtension } from './node/node-renderer.js';
|
|
|
14
15
|
import { C4NodeView } from './node/node-view.js';
|
|
15
16
|
import { C4TypeLineWatcher } from './node/type-line-watcher.js';
|
|
16
17
|
import { C4_PROFILES } from './profiles.js';
|
|
18
|
+
import { C4_READINGS } from './reading.js';
|
|
17
19
|
import { C4_ROLES } from './roles.js';
|
|
18
20
|
import { C4_RULES } from './rules.js';
|
|
21
|
+
import { c4TemplateCategory } from './templates.js';
|
|
19
22
|
import { c4BoardToolbarExtension, c4BoardToolingToolbarExtension, } from './toolbar/config.js';
|
|
20
23
|
import { c4SeniorTool } from './toolbar/senior-tool.js';
|
|
21
24
|
/**
|
|
@@ -70,8 +73,9 @@ export class C4RenderViewExtension extends ViewExtensionProvider {
|
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
72
75
|
* C4 creation tooling — flag-gated (`c4`): the senior toolbar button, its
|
|
73
|
-
* sub-menu, the thirteen commands behind them,
|
|
74
|
-
* component's "Change type" dropdown, and the
|
|
76
|
+
* sub-menu, the thirteen commands behind them, its templates category, the
|
|
77
|
+
* board's legend button, the component's "Change type" dropdown, and the
|
|
78
|
+
* validation rules and profiles.
|
|
75
79
|
*
|
|
76
80
|
* All of it is tooling in the sense `docs/adr/0009` means: a diagram drawn while
|
|
77
81
|
* the flag was on keeps painting, stays selectable and keeps its contextual
|
|
@@ -89,6 +93,7 @@ export class C4ViewExtension extends ViewExtensionProvider {
|
|
|
89
93
|
super.effect();
|
|
90
94
|
// Defines the senior button and its menu — tooling-only custom elements.
|
|
91
95
|
effects();
|
|
96
|
+
extendTemplateCategory(c4TemplateCategory);
|
|
92
97
|
}
|
|
93
98
|
setup(context) {
|
|
94
99
|
super.setup(context);
|
|
@@ -99,6 +104,14 @@ export class C4ViewExtension extends ViewExtensionProvider {
|
|
|
99
104
|
// Tooling like the rest of this class: with the flag off there is nothing
|
|
100
105
|
// to export WITH, while a stored diagram keeps painting (`docs/adr/0009`).
|
|
101
106
|
context.register(InterchangeExtension(C4_INTERCHANGE));
|
|
107
|
+
// The reversed reading (MF3): what the diagram says about an element, on
|
|
108
|
+
// demand. Four profiles because the four levels are deliberately FLAT —
|
|
109
|
+
// composition is not specialisation, and `reading.ts` says why a common
|
|
110
|
+
// parent would be a lie. The CLICK that triggers it is registered once by
|
|
111
|
+
// the surface and gated by the presence of a profile.
|
|
112
|
+
for (const reading of C4_READINGS) {
|
|
113
|
+
context.register(ReadingProfileExtension(reading));
|
|
114
|
+
}
|
|
102
115
|
context.register(c4SeniorTool);
|
|
103
116
|
context.register(CommandExtension(c4Commands, c4CommandIcons));
|
|
104
117
|
// The flag-gated half of the selected BOARD's row, through the `custom:`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formicoidea/labre-framework-c4",
|
|
3
3
|
"description": "Labre c4 framework for @formicoidea/labre-core.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.38.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "lajola",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@formicoidea/labre-core": "0.
|
|
35
|
-
"@formicoidea/labre-ddd-shared": "0.
|
|
34
|
+
"@formicoidea/labre-core": "0.38.1",
|
|
35
|
+
"@formicoidea/labre-ddd-shared": "0.38.1",
|
|
36
36
|
"lit": "^3.2.0"
|
|
37
37
|
}
|
|
38
38
|
}
|