@principal-ai/subsystems-react 0.51.2 → 0.51.4
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 +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +12 -1
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +115 -22
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +22 -22
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +56 -77
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +32 -13
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
- package/dist/subsystem/TrailsPanel.js +8 -4
- package/dist/subsystem/TrailsPanel.js.map +1 -1
- package/dist/subsystem/c4.d.ts +24 -86
- package/dist/subsystem/c4.d.ts.map +1 -1
- package/dist/subsystem/c4.js +6 -285
- package/dist/subsystem/c4.js.map +1 -1
- package/dist/subsystem/c4Evidence.js +1 -1
- package/dist/subsystem/c4Evidence.js.map +1 -1
- package/dist/subsystem/model.d.ts +14 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +4 -2
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/techIcons/bun.d.ts +11 -0
- package/dist/subsystem/techIcons/bun.d.ts.map +1 -0
- package/dist/subsystem/techIcons/bun.js +11 -0
- package/dist/subsystem/techIcons/bun.js.map +1 -0
- package/dist/subsystem/techIcons/index.d.ts +36 -0
- package/dist/subsystem/techIcons/index.d.ts.map +1 -0
- package/dist/subsystem/techIcons/index.js +55 -0
- package/dist/subsystem/techIcons/index.js.map +1 -0
- package/dist/subsystem/techIcons/node.d.ts +12 -0
- package/dist/subsystem/techIcons/node.d.ts.map +1 -0
- package/dist/subsystem/techIcons/node.js +39 -0
- package/dist/subsystem/techIcons/node.js.map +1 -0
- package/dist/subsystem/techIcons/react.d.ts +12 -0
- package/dist/subsystem/techIcons/react.d.ts.map +1 -0
- package/dist/subsystem/techIcons/react.js +19 -0
- package/dist/subsystem/techIcons/react.js.map +1 -0
- package/dist/utils/edgeLabel.d.ts +1 -1
- package/dist/utils/edgeLabel.d.ts.map +1 -1
- package/dist/utils/edgeLabel.js +1 -1
- package/dist/utils/edgeLabel.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +18 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +90 -9
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +4 -6
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +212 -0
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +20 -10
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +4 -2
- package/src/stories/Subsystem/ComponentGraph/ProposedTrails.stories.tsx +21 -1
- package/src/subsystem/C4Graph.tsx +171 -19
- package/src/subsystem/C4NodeCard.test.ts +71 -77
- package/src/subsystem/C4NodeCard.tsx +65 -95
- package/src/subsystem/SubsystemComponentGraph.tsx +41 -13
- package/src/subsystem/TrailsPanel.tsx +10 -5
- package/src/subsystem/c4.ts +23 -357
- package/src/subsystem/c4Evidence.test.ts +1 -1
- package/src/subsystem/c4Evidence.ts +1 -1
- package/src/subsystem/model.ts +14 -0
- package/src/subsystem/nodes.tsx +5 -2
- package/src/subsystem/techIcons/bun.ts +10 -0
- package/src/subsystem/techIcons/index.tsx +73 -0
- package/src/subsystem/techIcons/node.ts +38 -0
- package/src/subsystem/techIcons/react.ts +18 -0
- package/src/subsystem/techIcons/techIcons.test.ts +39 -0
- package/src/utils/edgeLabel.ts +1 -1
- package/src/utils/elkLayout.test.ts +34 -0
- package/src/utils/elkLayout.ts +88 -9
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts +0 -493
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts.map +0 -1
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js +0 -329
- package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js.map +0 -1
- package/src/stories/Subsystem/C4Graph/ProposedElements.stories.tsx +0 -293
- package/src/stories/Subsystem/C4Graph/c4ProposedElements.fixture.ts +0 -366
- package/src/subsystem/c4.test.ts +0 -573
|
@@ -1,293 +0,0 @@
|
|
|
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 { buildC4Model, summarizeElements, type C4View } from '../../../subsystem/c4';
|
|
7
|
-
import type { C4Element, C4ElementState } from '../../../subsystem/c4';
|
|
8
|
-
import { mergeElements, suggestMerges } from '../../../subsystem/c4Evidence';
|
|
9
|
-
import {
|
|
10
|
-
c4ProposedElements,
|
|
11
|
-
c4FixtureDocument,
|
|
12
|
-
c4OpenQuestions,
|
|
13
|
-
} from './c4ProposedElements.fixture';
|
|
14
|
-
|
|
15
|
-
const meta = {
|
|
16
|
-
title: 'Subsystem/C4Graph/ProposedElements',
|
|
17
|
-
component: C4Graph,
|
|
18
|
-
parameters: { layout: 'fullscreen' },
|
|
19
|
-
tags: ['autodocs'],
|
|
20
|
-
decorators: [
|
|
21
|
-
(Story) => (
|
|
22
|
-
<ThemeProvider theme={defaultEditorTheme}>
|
|
23
|
-
<Story />
|
|
24
|
-
</ThemeProvider>
|
|
25
|
-
),
|
|
26
|
-
],
|
|
27
|
-
} satisfies Meta<typeof C4Graph>;
|
|
28
|
-
|
|
29
|
-
export default meta;
|
|
30
|
-
type Story = StoryObj<typeof meta>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Proposed C4 elements — authored, not converted.
|
|
34
|
-
*
|
|
35
|
-
* Nothing here is derived from `process`. The boxes exist because someone
|
|
36
|
-
* (here, an agent) claimed they exist, and each claim carries a `rationale`
|
|
37
|
-
* saying why. This story is that set plus the three decisions a person makes:
|
|
38
|
-
*
|
|
39
|
-
* accept the box goes solid — it is part of the architecture
|
|
40
|
-
* reject the box disappears, and so do the lines into it
|
|
41
|
-
* merge two elements fold into one, carrying both member sets
|
|
42
|
-
*
|
|
43
|
-
* Try rejecting `container:in-memory-state`. That is the shape question: those
|
|
44
|
-
* are process-lifetime Sets and Maps, not a container, so the element should
|
|
45
|
-
* not exist at all. Watch "components unclaimed" go up — rejecting an element
|
|
46
|
-
* orphans whatever was in it, and the build says so rather than re-homing it.
|
|
47
|
-
*
|
|
48
|
-
* Four elements are deliberately wrong and say so: `container:shared-ui` is an
|
|
49
|
-
* npm package, `container:legacy-host` is a second app name for the same
|
|
50
|
-
* process, `container:in-memory-state` is process-lifetime state, and
|
|
51
|
-
* `external:opencode-db` is disputed between two models.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Which keys could fold together, found by the same evidence helper an agent
|
|
56
|
-
* would use: two `process` keys ending in the same role are one deployable
|
|
57
|
-
* unit or two, and the corpus cannot say which.
|
|
58
|
-
*/
|
|
59
|
-
function mergeCandidates(elements: readonly C4Element[]) {
|
|
60
|
-
const rollup = elements
|
|
61
|
-
.filter((e) => e.kind === 'container' && e.derivedFrom?.length)
|
|
62
|
-
.flatMap((e) =>
|
|
63
|
-
e.derivedFrom!.map((key) => ({ key, memberCount: e.members.length })),
|
|
64
|
-
);
|
|
65
|
-
return suggestMerges(rollup);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function Demo({ initialAccepted = [] as string[] }: { initialAccepted?: string[] }) {
|
|
69
|
-
const [view, setView] = useState<C4View>('container');
|
|
70
|
-
const [selected, setSelected] = useState<string | null>(null);
|
|
71
|
-
const [state, setState] = useState<Record<string, C4ElementState>>(() =>
|
|
72
|
-
Object.fromEntries(initialAccepted.map((id) => [id, 'accepted' as const])),
|
|
73
|
-
);
|
|
74
|
-
/** Role names the user has folded, e.g. `host`. */
|
|
75
|
-
const [mergedRoles, setMergedRoles] = useState<string[]>([]);
|
|
76
|
-
|
|
77
|
-
const candidates = useMemo(() => mergeCandidates(c4ProposedElements), []);
|
|
78
|
-
|
|
79
|
-
const elements = useMemo<C4Element[]>(() => {
|
|
80
|
-
let set = c4ProposedElements;
|
|
81
|
-
// Merging applies a decision the user already made; `mergeElements` is the
|
|
82
|
-
// same helper an agent would call, so the story cannot drift from the API.
|
|
83
|
-
for (const role of mergedRoles) {
|
|
84
|
-
const candidate = candidates.find((c) => c.role === role);
|
|
85
|
-
if (candidate) set = mergeElements(set, candidate.keys);
|
|
86
|
-
}
|
|
87
|
-
return set.map((e) => ({ ...e, state: state[e.id] ?? e.state }) as C4Element);
|
|
88
|
-
}, [state, mergedRoles, candidates]);
|
|
89
|
-
|
|
90
|
-
const { model, problems, unmapped } = useMemo(
|
|
91
|
-
() =>
|
|
92
|
-
buildC4Model({
|
|
93
|
-
elements,
|
|
94
|
-
document: c4FixtureDocument as never,
|
|
95
|
-
view,
|
|
96
|
-
systemLabel: 'Subsystem Modeling',
|
|
97
|
-
}),
|
|
98
|
-
[elements, view],
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const stats = useMemo(() => summarizeElements(elements), [elements]);
|
|
102
|
-
const decide = (id: string, next: C4ElementState) => setState((p) => ({ ...p, [id]: next }));
|
|
103
|
-
const reset = () => {
|
|
104
|
-
setState({});
|
|
105
|
-
setMergedRoles([]);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/** Merge candidates this element's keys take part in. */
|
|
109
|
-
const roleOf = (id: string) => {
|
|
110
|
-
const element = c4ProposedElements.find((e) => e.id === id);
|
|
111
|
-
return candidates
|
|
112
|
-
.filter((c) => element?.derivedFrom?.some((k) => c.keys.includes(k)))
|
|
113
|
-
.map((c) => c.role);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const button = (label: string, value: C4View) => (
|
|
117
|
-
<button
|
|
118
|
-
type="button"
|
|
119
|
-
onClick={() => setView(value)}
|
|
120
|
-
style={{
|
|
121
|
-
fontFamily: 'monospace',
|
|
122
|
-
fontSize: 12,
|
|
123
|
-
padding: '2px 10px',
|
|
124
|
-
borderRadius: 4,
|
|
125
|
-
cursor: 'pointer',
|
|
126
|
-
border: '1px solid #555',
|
|
127
|
-
background: view === value ? '#1D9E75' : 'transparent',
|
|
128
|
-
color: view === value ? '#020B12' : '#ddd',
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
{label}
|
|
132
|
-
</button>
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const act = (label: string, onClick: () => void) => (
|
|
136
|
-
<button
|
|
137
|
-
type="button"
|
|
138
|
-
onClick={onClick}
|
|
139
|
-
style={{
|
|
140
|
-
fontFamily: 'monospace',
|
|
141
|
-
fontSize: 11,
|
|
142
|
-
padding: '2px 8px',
|
|
143
|
-
borderRadius: 4,
|
|
144
|
-
cursor: 'pointer',
|
|
145
|
-
border: '1px solid #555',
|
|
146
|
-
background: 'transparent',
|
|
147
|
-
color: '#ddd',
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
{label}
|
|
151
|
-
</button>
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div
|
|
156
|
-
style={{
|
|
157
|
-
width: '100%',
|
|
158
|
-
height: '100vh',
|
|
159
|
-
display: 'flex',
|
|
160
|
-
flexDirection: 'column',
|
|
161
|
-
background: '#12141a',
|
|
162
|
-
}}
|
|
163
|
-
>
|
|
164
|
-
<div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
|
|
165
|
-
<C4Graph model={model} title="Subsystem Modeling" onSelectNode={setSelected} />
|
|
166
|
-
</div>
|
|
167
|
-
|
|
168
|
-
<div
|
|
169
|
-
style={{
|
|
170
|
-
maxHeight: 300,
|
|
171
|
-
overflowY: 'auto',
|
|
172
|
-
padding: '8px 12px',
|
|
173
|
-
display: 'flex',
|
|
174
|
-
gap: 18,
|
|
175
|
-
fontFamily: 'monospace',
|
|
176
|
-
fontSize: 12,
|
|
177
|
-
color: '#aaa',
|
|
178
|
-
borderTop: '1px solid #333',
|
|
179
|
-
}}
|
|
180
|
-
>
|
|
181
|
-
<div style={{ minWidth: 200 }}>
|
|
182
|
-
<div style={{ display: 'flex', gap: 6, marginBottom: 8 }}>
|
|
183
|
-
{button('container', 'container')}
|
|
184
|
-
{button('component', 'component')}
|
|
185
|
-
</div>
|
|
186
|
-
<div>
|
|
187
|
-
{stats.containers}c · {stats.components}k · {stats.externals}x · {stats.people}p
|
|
188
|
-
</div>
|
|
189
|
-
<div>
|
|
190
|
-
{stats.accepted} confirmed · {stats.proposed} proposed · {stats.rejected} rejected
|
|
191
|
-
</div>
|
|
192
|
-
<div style={{ color: stats.missingTechnology > 0 ? '#e8a33a' : '#5a5' }}>
|
|
193
|
-
{stats.missingTechnology} missing technology
|
|
194
|
-
</div>
|
|
195
|
-
<div style={{ color: stats.missingDescription > 0 ? '#e8a33a' : '#5a5' }}>
|
|
196
|
-
{stats.missingDescription} missing description
|
|
197
|
-
</div>
|
|
198
|
-
<div>{model.edges.length} lines · {unmapped.length} components unclaimed</div>
|
|
199
|
-
<div style={{ marginTop: 8 }}>{act('reset', reset)}</div>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<div style={{ minWidth: 280, maxWidth: 320 }}>
|
|
203
|
-
<div style={{ color: '#ddd', marginBottom: 4 }}>OPEN QUESTIONS</div>
|
|
204
|
-
{c4OpenQuestions.map((q) => (
|
|
205
|
-
<div key={q.id} style={{ marginBottom: 7, color: '#c96', lineHeight: 1.35, fontSize: 11 }}>
|
|
206
|
-
<div style={{ color: '#9a9aa0' }}>{q.label}</div>
|
|
207
|
-
{q.rationale}
|
|
208
|
-
</div>
|
|
209
|
-
))}
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
|
-
<div style={{ flex: 1, minWidth: 420 }}>
|
|
213
|
-
<div style={{ color: '#ddd', marginBottom: 4 }}>PROPOSED ELEMENTS</div>
|
|
214
|
-
{elements.map((e) => {
|
|
215
|
-
const s = state[e.id] ?? e.state;
|
|
216
|
-
const roles = roleOf(e.id);
|
|
217
|
-
const kindLabel = e.kind === 'container' ? e.containerKind : e.kind;
|
|
218
|
-
return (
|
|
219
|
-
<div
|
|
220
|
-
key={e.id}
|
|
221
|
-
style={{
|
|
222
|
-
display: 'flex',
|
|
223
|
-
gap: 10,
|
|
224
|
-
alignItems: 'flex-start',
|
|
225
|
-
padding: '4px 0',
|
|
226
|
-
borderBottom: '1px solid #222',
|
|
227
|
-
opacity: s === 'rejected' ? 0.45 : 1,
|
|
228
|
-
}}
|
|
229
|
-
>
|
|
230
|
-
<div style={{ flex: 1, minWidth: 0 }}>
|
|
231
|
-
<div style={{ color: '#ddd' }}>
|
|
232
|
-
{e.label}
|
|
233
|
-
<span style={{ color: s === 'accepted' ? '#5a5' : '#e8a33a' }}>
|
|
234
|
-
{' '}
|
|
235
|
-
· {s} · {kindLabel}
|
|
236
|
-
{e.kind === 'component' ? ` → ${e.container.replace('container:', '')}` : ''}
|
|
237
|
-
</span>
|
|
238
|
-
</div>
|
|
239
|
-
<div style={{ color: '#777', fontSize: 11 }}>
|
|
240
|
-
{e.members.length} members
|
|
241
|
-
{'technology' in e ? (e.technology ? ` · ${e.technology}` : ' · no technology') : ''}
|
|
242
|
-
{e.description ? '' : ' · no description'}
|
|
243
|
-
{e.derivedFrom && e.derivedFrom.length > 1
|
|
244
|
-
? ` · consolidated from ${e.derivedFrom.join(' + ')}`
|
|
245
|
-
: ''}
|
|
246
|
-
</div>
|
|
247
|
-
</div>
|
|
248
|
-
<div style={{ display: 'flex', gap: 4 }}>
|
|
249
|
-
{act('accept', () => decide(e.id, 'accepted'))}
|
|
250
|
-
{act('reject', () => decide(e.id, 'rejected'))}
|
|
251
|
-
{roles.map((role) =>
|
|
252
|
-
mergedRoles.includes(role) ? null : (
|
|
253
|
-
<React.Fragment key={role}>
|
|
254
|
-
{act(`merge /${role}`, () => setMergedRoles((p) => [...p, role]))}
|
|
255
|
-
</React.Fragment>
|
|
256
|
-
),
|
|
257
|
-
)}
|
|
258
|
-
</div>
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
})}
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
<div style={{ minWidth: 200, fontSize: 11, color: '#777' }}>
|
|
265
|
-
<div>selected: {selected ?? '(none)'}</div>
|
|
266
|
-
{problems.length > 0 && (
|
|
267
|
-
<div style={{ color: '#e06c6c', marginTop: 6 }}>
|
|
268
|
-
{problems.length} authoring problem{problems.length === 1 ? '' : 's'}:
|
|
269
|
-
{problems.slice(0, 3).map((p) => (
|
|
270
|
-
<div key={p.subject + p.kind}>{p.message}</div>
|
|
271
|
-
))}
|
|
272
|
-
</div>
|
|
273
|
-
)}
|
|
274
|
-
<div style={{ marginTop: 6 }}>solid = confirmed</div>
|
|
275
|
-
<div>dashed = proposed</div>
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export const Proposed: Story = {
|
|
283
|
-
render: () => <Demo />,
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
/** Half-confirmed, so the accepted/proposed difference is visible at a glance. */
|
|
287
|
-
export const PartiallyConfirmed: Story = {
|
|
288
|
-
render: () => (
|
|
289
|
-
<Demo
|
|
290
|
-
initialAccepted={['container:studio-host', 'container:studio-renderer', 'external:gists']}
|
|
291
|
-
/>
|
|
292
|
-
),
|
|
293
|
-
};
|