@principal-ai/subsystems-react 0.37.5 → 0.37.7
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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.d.ts +3 -0
- package/dist/stories/Subsystem/C4Graph/c4Fixture.d.ts.map +1 -0
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js +2 -0
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -0
- package/dist/subsystem/C4Graph.d.ts +22 -0
- package/dist/subsystem/C4Graph.d.ts.map +1 -0
- package/dist/subsystem/C4Graph.js +305 -0
- package/dist/subsystem/C4Graph.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +8 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +7 -3
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/WalkthroughsPanel.d.ts +2 -0
- package/dist/subsystem/WalkthroughsPanel.d.ts.map +1 -1
- package/dist/subsystem/WalkthroughsPanel.js +114 -48
- package/dist/subsystem/WalkthroughsPanel.js.map +1 -1
- package/dist/subsystem/model.d.ts +2 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +6 -6
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +2 -1
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/toC4.d.ts +95 -0
- package/dist/subsystem/toC4.d.ts.map +1 -0
- package/dist/subsystem/toC4.js +196 -0
- package/dist/subsystem/toC4.js.map +1 -0
- package/dist/subsystem/walkthroughBrief.d.ts +18 -0
- package/dist/subsystem/walkthroughBrief.d.ts.map +1 -0
- package/dist/subsystem/walkthroughBrief.js +34 -0
- package/dist/subsystem/walkthroughBrief.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +6 -1
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +104 -0
- package/src/stories/Subsystem/C4Graph/c4Fixture.ts +6 -0
- package/src/subsystem/C4Graph.tsx +465 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +15 -3
- package/src/subsystem/WalkthroughsPanel.test.tsx +146 -0
- package/src/subsystem/WalkthroughsPanel.tsx +163 -60
- package/src/subsystem/model.ts +8 -6
- package/src/subsystem/nodes.tsx +2 -1
- package/src/subsystem/toC4.test.ts +121 -0
- package/src/subsystem/toC4.ts +277 -0
- package/src/subsystem/walkthroughBrief.test.ts +57 -0
- package/src/subsystem/walkthroughBrief.ts +40 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toC4 — project a subsystem-model (or a composition of them) onto C4 levels.
|
|
3
|
+
*
|
|
4
|
+
* The stored document is flat: `components[]` carry `process` (runtime unit),
|
|
5
|
+
* `purl` (repo/package), `construct`, `role`, `module`, plus `relations[]`
|
|
6
|
+
* (static) and `walkthroughs[]` (dynamic). C4 wants a hierarchy above the
|
|
7
|
+
* component — system → container → component — with externals and actors
|
|
8
|
+
* outside it. This module derives that hierarchy without inventing data:
|
|
9
|
+
*
|
|
10
|
+
* system ← the repo key (purlRepoKey of the first grounded component)
|
|
11
|
+
* container ← component.process (runtime/deployment boundary)
|
|
12
|
+
* component ← a grounded component
|
|
13
|
+
* external ← construct: 'external'
|
|
14
|
+
* actor ← construct: 'custom_entity' (their authored actor/entity)
|
|
15
|
+
*
|
|
16
|
+
* Edges are rolled up to the owner of each endpoint at the chosen view, so the
|
|
17
|
+
* container view shows container↔container collaborations and the component
|
|
18
|
+
* view shows component↔component ones. Intra-owner edges (from === to) drop.
|
|
19
|
+
*
|
|
20
|
+
* Pure over the document so it is unit-testable and reusable by any renderer.
|
|
21
|
+
*/
|
|
22
|
+
const EXTERNAL_PURL = 'external';
|
|
23
|
+
const UNASSIGNED = '(unassigned)';
|
|
24
|
+
/** A code component (not an external, not an actor/entity). */
|
|
25
|
+
export function isGroundedComponent(c) {
|
|
26
|
+
return c.construct !== 'external' && c.construct !== 'custom_entity';
|
|
27
|
+
}
|
|
28
|
+
/** `owner/name` from a purl, trimmed of scheme and fragment. */
|
|
29
|
+
export function labelFromPurl(purl) {
|
|
30
|
+
const base = purl.replace(/^external:/, '').split('#')[0]?.trim() ?? '';
|
|
31
|
+
const parts = base.split('/').filter(Boolean);
|
|
32
|
+
return parts.slice(-2).join('/') || base || purl;
|
|
33
|
+
}
|
|
34
|
+
/** The repo key the system is derived from: first grounded component's purl. */
|
|
35
|
+
export function deriveRepoKey(doc) {
|
|
36
|
+
for (const c of doc.components) {
|
|
37
|
+
const p = (c.purl ?? '').split('#')[0]?.trim();
|
|
38
|
+
if (p && p !== EXTERNAL_PURL && !p.startsWith('external:'))
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Stable identity for an external: its real purl, else the model-local alias. */
|
|
44
|
+
function externalKey(c) {
|
|
45
|
+
const purl = (c.purl ?? '').trim();
|
|
46
|
+
if (purl && purl !== EXTERNAL_PURL && purl !== 'external:proposed')
|
|
47
|
+
return purl;
|
|
48
|
+
return `alias:${c.alias}`;
|
|
49
|
+
}
|
|
50
|
+
function containerKey(c) {
|
|
51
|
+
return c.process?.trim() || UNASSIGNED;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Derive the C4 projection of a (possibly composed) subsystem document.
|
|
55
|
+
*
|
|
56
|
+
* @param doc - a portable document, or a composed one with canonical aliases.
|
|
57
|
+
* @param options - view + optional system naming + attribution.
|
|
58
|
+
*/
|
|
59
|
+
export function toC4(doc, options = {}) {
|
|
60
|
+
const view = options.view ?? 'container';
|
|
61
|
+
const repoKey = options.repoKey ?? deriveRepoKey(doc) ?? 'system';
|
|
62
|
+
const systemId = `system:${repoKey}`;
|
|
63
|
+
const system = {
|
|
64
|
+
id: systemId,
|
|
65
|
+
kind: 'system',
|
|
66
|
+
label: options.systemLabel ?? labelFromPurl(repoKey),
|
|
67
|
+
key: repoKey,
|
|
68
|
+
members: [],
|
|
69
|
+
constructs: [],
|
|
70
|
+
isStore: false,
|
|
71
|
+
};
|
|
72
|
+
const nodes = new Map();
|
|
73
|
+
const ownerOf = new Map();
|
|
74
|
+
const containerNodeId = (key) => `container:${key}`;
|
|
75
|
+
const componentNodeId = (alias) => `component:${alias}`;
|
|
76
|
+
for (const c of doc.components) {
|
|
77
|
+
let id;
|
|
78
|
+
let kind;
|
|
79
|
+
let label;
|
|
80
|
+
let parentId;
|
|
81
|
+
let key;
|
|
82
|
+
if (c.construct === 'external') {
|
|
83
|
+
key = externalKey(c);
|
|
84
|
+
id = `external:${key}`;
|
|
85
|
+
kind = 'external';
|
|
86
|
+
label = key.startsWith('alias:') ? c.name : labelFromPurl(key);
|
|
87
|
+
}
|
|
88
|
+
else if (c.construct === 'custom_entity') {
|
|
89
|
+
key = c.alias;
|
|
90
|
+
id = `actor:${c.alias}`;
|
|
91
|
+
kind = 'actor';
|
|
92
|
+
label = c.name;
|
|
93
|
+
}
|
|
94
|
+
else if (view === 'component') {
|
|
95
|
+
key = containerKey(c);
|
|
96
|
+
id = componentNodeId(c.alias);
|
|
97
|
+
kind = 'component';
|
|
98
|
+
label = c.name;
|
|
99
|
+
parentId = containerNodeId(key);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
key = containerKey(c);
|
|
103
|
+
id = containerNodeId(key);
|
|
104
|
+
kind = 'container';
|
|
105
|
+
label = key === UNASSIGNED ? 'Unassigned' : key;
|
|
106
|
+
parentId = systemId;
|
|
107
|
+
}
|
|
108
|
+
ownerOf.set(c.alias, id);
|
|
109
|
+
let node = nodes.get(id);
|
|
110
|
+
if (!node) {
|
|
111
|
+
node = {
|
|
112
|
+
id,
|
|
113
|
+
kind,
|
|
114
|
+
label,
|
|
115
|
+
parentId,
|
|
116
|
+
key,
|
|
117
|
+
members: [],
|
|
118
|
+
constructs: [],
|
|
119
|
+
isStore: false,
|
|
120
|
+
...(kind === 'component' ? { component: c } : {}),
|
|
121
|
+
};
|
|
122
|
+
nodes.set(id, node);
|
|
123
|
+
}
|
|
124
|
+
node.members.push(c.alias);
|
|
125
|
+
if (!node.constructs.includes(c.construct))
|
|
126
|
+
node.constructs.push(c.construct);
|
|
127
|
+
if (c.construct === 'store')
|
|
128
|
+
node.isStore = true;
|
|
129
|
+
const models = options.modelsByAlias?.[c.alias];
|
|
130
|
+
if (models && models.length > 0) {
|
|
131
|
+
node.models = [...new Set([...(node.models ?? []), ...models])];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// --- Groups (compound frames) ------------------------------------------
|
|
135
|
+
const groups = [];
|
|
136
|
+
if (view === 'container') {
|
|
137
|
+
const containerIds = [...nodes.values()].filter((n) => n.kind === 'container').map((n) => n.id);
|
|
138
|
+
if (containerIds.length > 0) {
|
|
139
|
+
groups.push({ id: systemId, kind: 'system', label: system.label, memberIds: containerIds });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const byContainer = new Map();
|
|
144
|
+
for (const n of nodes.values()) {
|
|
145
|
+
if (n.kind !== 'component' || !n.parentId)
|
|
146
|
+
continue;
|
|
147
|
+
const list = byContainer.get(n.parentId) ?? [];
|
|
148
|
+
list.push(n.id);
|
|
149
|
+
byContainer.set(n.parentId, list);
|
|
150
|
+
}
|
|
151
|
+
const containerIds = [];
|
|
152
|
+
for (const [id, memberIds] of byContainer) {
|
|
153
|
+
const label = id.slice('container:'.length);
|
|
154
|
+
groups.push({ id, kind: 'container', label: label === UNASSIGNED ? 'Unassigned' : label, parentId: systemId, memberIds });
|
|
155
|
+
containerIds.push(id);
|
|
156
|
+
}
|
|
157
|
+
if (containerIds.length > 0) {
|
|
158
|
+
groups.push({ id: systemId, kind: 'system', label: system.label, memberIds: containerIds });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// --- Edges: roll each endpoint up to its owner at this view -------------
|
|
162
|
+
const relEdges = new Map();
|
|
163
|
+
const flowEdges = new Map();
|
|
164
|
+
const addEdge = (bucket, source, target, kind, mechanism) => {
|
|
165
|
+
if (!source || !target || source === target)
|
|
166
|
+
return;
|
|
167
|
+
const key = `${source}\0${target}`;
|
|
168
|
+
let edge = bucket.get(key);
|
|
169
|
+
if (!edge) {
|
|
170
|
+
edge = { id: `${kind}:${key}`, source, target, kind, label: '', mechanisms: [], count: 0 };
|
|
171
|
+
bucket.set(key, edge);
|
|
172
|
+
}
|
|
173
|
+
edge.count += 1;
|
|
174
|
+
if (!edge.mechanisms.includes(mechanism))
|
|
175
|
+
edge.mechanisms.push(mechanism);
|
|
176
|
+
};
|
|
177
|
+
for (const r of doc.relations ?? []) {
|
|
178
|
+
addEdge(relEdges, ownerOf.get(r.from), ownerOf.get(r.to), 'relationship', r.relationType);
|
|
179
|
+
}
|
|
180
|
+
for (const w of doc.walkthroughs ?? []) {
|
|
181
|
+
for (const s of w.steps ?? []) {
|
|
182
|
+
addEdge(flowEdges, ownerOf.get(s.from), ownerOf.get(s.to), 'flow', s.mechanism);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const edges = [...relEdges.values(), ...flowEdges.values()];
|
|
186
|
+
for (const e of edges)
|
|
187
|
+
e.label = [...e.mechanisms].sort().join(' + ');
|
|
188
|
+
return {
|
|
189
|
+
view,
|
|
190
|
+
system,
|
|
191
|
+
nodes: [...nodes.values()].filter((n) => n.kind !== 'system'),
|
|
192
|
+
groups,
|
|
193
|
+
edges,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=toC4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toC4.js","sourceRoot":"","sources":["../../src/subsystem/toC4.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* walkthroughBrief — build a paste-ready markdown brief for a single
|
|
3
|
+
* walkthrough step so a reviewer can hand one hop to an agent without
|
|
4
|
+
* transcribing it by hand.
|
|
5
|
+
*
|
|
6
|
+
* The unit of handoff is one hop, not the whole flow. A bare hop is ambiguous
|
|
7
|
+
* on its own — the same `from`/`to`/`mechanism` can repeat across flows — so the
|
|
8
|
+
* brief also carries the flow it belongs to and its position within it, plus
|
|
9
|
+
* the exact seam site (`symbol`, `file:line`, `purl`) that resolves the
|
|
10
|
+
* checkout.
|
|
11
|
+
*/
|
|
12
|
+
import type { SubsystemWalkthrough } from './model';
|
|
13
|
+
/**
|
|
14
|
+
* Markdown brief for `walkthrough.steps[stepIndex]`. Returns `''` when the
|
|
15
|
+
* index is out of range so a caller can treat it as "nothing to copy".
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildStepBrief(walkthrough: SubsystemWalkthrough, stepIndex: number): string;
|
|
18
|
+
//# sourceMappingURL=walkthroughBrief.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walkthroughBrief.d.ts","sourceRoot":"","sources":["../../src/subsystem/walkthroughBrief.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,oBAAoB,EACjC,SAAS,EAAE,MAAM,GAChB,MAAM,CAkBR"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* walkthroughBrief — build a paste-ready markdown brief for a single
|
|
3
|
+
* walkthrough step so a reviewer can hand one hop to an agent without
|
|
4
|
+
* transcribing it by hand.
|
|
5
|
+
*
|
|
6
|
+
* The unit of handoff is one hop, not the whole flow. A bare hop is ambiguous
|
|
7
|
+
* on its own — the same `from`/`to`/`mechanism` can repeat across flows — so the
|
|
8
|
+
* brief also carries the flow it belongs to and its position within it, plus
|
|
9
|
+
* the exact seam site (`symbol`, `file:line`, `purl`) that resolves the
|
|
10
|
+
* checkout.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Markdown brief for `walkthrough.steps[stepIndex]`. Returns `''` when the
|
|
14
|
+
* index is out of range so a caller can treat it as "nothing to copy".
|
|
15
|
+
*/
|
|
16
|
+
export function buildStepBrief(walkthrough, stepIndex) {
|
|
17
|
+
const step = walkthrough.steps[stepIndex];
|
|
18
|
+
if (!step)
|
|
19
|
+
return '';
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`Walkthrough: ${walkthrough.title} (${walkthrough.id}) — step ${stepIndex + 1}/${walkthrough.steps.length}`);
|
|
22
|
+
lines.push('');
|
|
23
|
+
lines.push(`- from: \`${step.from}\``);
|
|
24
|
+
lines.push(`- to: \`${step.to}\``);
|
|
25
|
+
lines.push(`- mechanism: \`${step.mechanism}\``);
|
|
26
|
+
lines.push(`- symbol: \`${step.symbol}\``);
|
|
27
|
+
lines.push(`- site: \`${step.file}:${step.line}\``);
|
|
28
|
+
lines.push(`- purl: \`${step.purl}\``);
|
|
29
|
+
if (step.annotation != null && step.annotation.length > 0) {
|
|
30
|
+
lines.push(`- note: ${step.annotation}`);
|
|
31
|
+
}
|
|
32
|
+
return lines.join('\n');
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=walkthroughBrief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walkthroughBrief.js","sourceRoot":"","sources":["../../src/subsystem/walkthroughBrief.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,WAAiC,EACjC,SAAiB;IAEjB,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CACR,gBAAgB,WAAW,CAAC,KAAK,KAAK,WAAW,CAAC,EAAE,YAAY,SAAS,GAAG,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,CAC5G,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACnC,KAAK,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;IACjD,KAAK,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3C,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IACpD,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;IACvC,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;QACzD,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;KAC1C;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -174,8 +174,13 @@ export type {
|
|
|
174
174
|
} from './subsystem/paths';
|
|
175
175
|
export { SubsystemFileTree } from './subsystem/SubsystemFileTree';
|
|
176
176
|
export type { SubsystemFileTreeProps } from './subsystem/SubsystemFileTree';
|
|
177
|
-
export {
|
|
177
|
+
export {
|
|
178
|
+
WalkthroughsPanel,
|
|
179
|
+
WALKTHROUGH_PLAY_PAUSE_MS,
|
|
180
|
+
STEP_COPY_FEEDBACK_MS,
|
|
181
|
+
} from './subsystem/WalkthroughsPanel';
|
|
178
182
|
export type { WalkthroughsPanelProps } from './subsystem/WalkthroughsPanel';
|
|
183
|
+
export { buildStepBrief } from './subsystem/walkthroughBrief';
|
|
179
184
|
export { GraphLayoutCover } from './subsystem/GraphLayoutCover';
|
|
180
185
|
export type { GraphLayoutCoverProps } from './subsystem/GraphLayoutCover';
|
|
181
186
|
export {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import '@xyflow/react/dist/style.css';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { C4Graph } from '../../../subsystem/C4Graph';
|
|
6
|
+
import { toC4, type C4View } from '../../../subsystem/toC4';
|
|
7
|
+
import { c4FixtureModel } from './c4Fixture';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Subsystem/C4Graph/C4Container',
|
|
11
|
+
component: C4Graph,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'fullscreen',
|
|
14
|
+
},
|
|
15
|
+
tags: ['autodocs'],
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story) => (
|
|
18
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
19
|
+
<Story />
|
|
20
|
+
</ThemeProvider>
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
} satisfies Meta<typeof C4Graph>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// C4 projection of a real composite: 60 stored subsystem models over one repo,
|
|
30
|
+
// merged by the host, then derived into system → container/component boxes with
|
|
31
|
+
// externals + actors outside. Toggle the C4 level; click a box to see the
|
|
32
|
+
// source components it rolled up.
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
function C4Demo() {
|
|
36
|
+
const [view, setView] = useState<C4View>('container');
|
|
37
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
38
|
+
const model = useMemo(() => toC4(c4FixtureModel, { view, systemLabel: 'Subsystem Modeling' }), [view]);
|
|
39
|
+
|
|
40
|
+
const counts = {
|
|
41
|
+
containers: model.nodes.filter((n) => n.kind === 'container').length,
|
|
42
|
+
components: model.nodes.filter((n) => n.kind === 'component').length,
|
|
43
|
+
externals: model.nodes.filter((n) => n.kind === 'external').length,
|
|
44
|
+
actors: model.nodes.filter((n) => n.kind === 'actor').length,
|
|
45
|
+
relationships: model.edges.filter((e) => e.kind === 'relationship').length,
|
|
46
|
+
flows: model.edges.filter((e) => e.kind === 'flow').length,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const button = (label: string, value: C4View) => (
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
onClick={() => setView(value)}
|
|
53
|
+
style={{
|
|
54
|
+
fontFamily: 'monospace',
|
|
55
|
+
fontSize: 12,
|
|
56
|
+
padding: '2px 10px',
|
|
57
|
+
borderRadius: 4,
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
border: '1px solid #555',
|
|
60
|
+
background: view === value ? '#1D9E75' : 'transparent',
|
|
61
|
+
color: view === value ? '#020B12' : '#ddd',
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{label}
|
|
65
|
+
</button>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
70
|
+
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
|
|
71
|
+
<C4Graph model={model} title="Subsystem Modeling" onSelectNode={setSelected} />
|
|
72
|
+
</div>
|
|
73
|
+
<div
|
|
74
|
+
style={{
|
|
75
|
+
padding: '6px 12px',
|
|
76
|
+
display: 'flex',
|
|
77
|
+
alignItems: 'center',
|
|
78
|
+
gap: 16,
|
|
79
|
+
fontFamily: 'monospace',
|
|
80
|
+
fontSize: 12,
|
|
81
|
+
color: '#aaa',
|
|
82
|
+
borderTop: '1px solid #333',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<span style={{ display: 'flex', gap: 6 }}>
|
|
86
|
+
view: {button('container', 'container')} {button('component', 'component')}
|
|
87
|
+
</span>
|
|
88
|
+
<span>
|
|
89
|
+
{counts.containers} containers · {counts.components} components · {counts.externals} externals ·{' '}
|
|
90
|
+
{counts.actors} actors
|
|
91
|
+
</span>
|
|
92
|
+
<span>
|
|
93
|
+
{counts.relationships} relationship edges · {counts.flows} flow edges
|
|
94
|
+
</span>
|
|
95
|
+
<span>selected: {selected ?? '(none)'}</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const C4: Story = {
|
|
102
|
+
args: { model: toC4(c4FixtureModel, { view: 'container', systemLabel: 'Subsystem Modeling' }) },
|
|
103
|
+
render: () => <C4Demo />,
|
|
104
|
+
};
|