@principal-ai/subsystems-react 0.51.2 → 0.51.3

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 (73) hide show
  1. package/dist/index.d.ts +5 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/subsystem/C4Graph.d.ts +12 -1
  6. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  7. package/dist/subsystem/C4Graph.js +82 -14
  8. package/dist/subsystem/C4Graph.js.map +1 -1
  9. package/dist/subsystem/C4NodeCard.d.ts +22 -22
  10. package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
  11. package/dist/subsystem/C4NodeCard.js +56 -77
  12. package/dist/subsystem/C4NodeCard.js.map +1 -1
  13. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  14. package/dist/subsystem/SubsystemComponentGraph.js +0 -6
  15. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  16. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  17. package/dist/subsystem/TrailsPanel.js +8 -4
  18. package/dist/subsystem/TrailsPanel.js.map +1 -1
  19. package/dist/subsystem/c4.d.ts +24 -86
  20. package/dist/subsystem/c4.d.ts.map +1 -1
  21. package/dist/subsystem/c4.js +6 -285
  22. package/dist/subsystem/c4.js.map +1 -1
  23. package/dist/subsystem/c4Evidence.js +1 -1
  24. package/dist/subsystem/c4Evidence.js.map +1 -1
  25. package/dist/subsystem/model.d.ts +8 -0
  26. package/dist/subsystem/model.d.ts.map +1 -1
  27. package/dist/subsystem/model.js.map +1 -1
  28. package/dist/subsystem/techIcons/bun.d.ts +11 -0
  29. package/dist/subsystem/techIcons/bun.d.ts.map +1 -0
  30. package/dist/subsystem/techIcons/bun.js +11 -0
  31. package/dist/subsystem/techIcons/bun.js.map +1 -0
  32. package/dist/subsystem/techIcons/index.d.ts +36 -0
  33. package/dist/subsystem/techIcons/index.d.ts.map +1 -0
  34. package/dist/subsystem/techIcons/index.js +55 -0
  35. package/dist/subsystem/techIcons/index.js.map +1 -0
  36. package/dist/subsystem/techIcons/node.d.ts +12 -0
  37. package/dist/subsystem/techIcons/node.d.ts.map +1 -0
  38. package/dist/subsystem/techIcons/node.js +39 -0
  39. package/dist/subsystem/techIcons/node.js.map +1 -0
  40. package/dist/subsystem/techIcons/react.d.ts +12 -0
  41. package/dist/subsystem/techIcons/react.d.ts.map +1 -0
  42. package/dist/subsystem/techIcons/react.js +19 -0
  43. package/dist/subsystem/techIcons/react.js.map +1 -0
  44. package/dist/utils/elkLayout.d.ts.map +1 -1
  45. package/dist/utils/elkLayout.js +16 -5
  46. package/dist/utils/elkLayout.js.map +1 -1
  47. package/package.json +3 -3
  48. package/src/index.ts +4 -6
  49. package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +212 -0
  50. package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +20 -10
  51. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +4 -2
  52. package/src/subsystem/C4Graph.tsx +127 -12
  53. package/src/subsystem/C4NodeCard.test.ts +71 -77
  54. package/src/subsystem/C4NodeCard.tsx +65 -95
  55. package/src/subsystem/SubsystemComponentGraph.tsx +0 -6
  56. package/src/subsystem/TrailsPanel.tsx +10 -5
  57. package/src/subsystem/c4.ts +23 -357
  58. package/src/subsystem/c4Evidence.test.ts +1 -1
  59. package/src/subsystem/c4Evidence.ts +1 -1
  60. package/src/subsystem/model.ts +8 -0
  61. package/src/subsystem/techIcons/bun.ts +10 -0
  62. package/src/subsystem/techIcons/index.tsx +73 -0
  63. package/src/subsystem/techIcons/node.ts +38 -0
  64. package/src/subsystem/techIcons/react.ts +18 -0
  65. package/src/subsystem/techIcons/techIcons.test.ts +39 -0
  66. package/src/utils/elkLayout.ts +16 -5
  67. package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts +0 -493
  68. package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.d.ts.map +0 -1
  69. package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js +0 -329
  70. package/dist/stories/Subsystem/C4Graph/c4ProposedElements.fixture.js.map +0 -1
  71. package/src/stories/Subsystem/C4Graph/ProposedElements.stories.tsx +0 -293
  72. package/src/stories/Subsystem/C4Graph/c4ProposedElements.fixture.ts +0 -366
  73. 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
- };