@principal-ai/subsystems-react 0.51.4 → 0.52.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 (64) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/subsystem/C4Graph.d.ts +3 -7
  6. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  7. package/dist/subsystem/C4Graph.js +159 -45
  8. package/dist/subsystem/C4Graph.js.map +1 -1
  9. package/dist/subsystem/C4NodeCard.d.ts +26 -26
  10. package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
  11. package/dist/subsystem/C4NodeCard.js +56 -30
  12. package/dist/subsystem/C4NodeCard.js.map +1 -1
  13. package/dist/subsystem/IssueList.d.ts +1 -1
  14. package/dist/subsystem/IssueList.d.ts.map +1 -1
  15. package/dist/subsystem/IssueList.js +20 -1
  16. package/dist/subsystem/IssueList.js.map +1 -1
  17. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  18. package/dist/subsystem/SubsystemComponentGraph.js +42 -19
  19. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  20. package/dist/subsystem/c4.d.ts +48 -49
  21. package/dist/subsystem/c4.d.ts.map +1 -1
  22. package/dist/subsystem/c4.js +50 -0
  23. package/dist/subsystem/c4.js.map +1 -1
  24. package/dist/subsystem/c4Evidence.d.ts +88 -84
  25. package/dist/subsystem/c4Evidence.d.ts.map +1 -1
  26. package/dist/subsystem/c4Evidence.js +113 -166
  27. package/dist/subsystem/c4Evidence.js.map +1 -1
  28. package/dist/subsystem/nodes.js +5 -5
  29. package/dist/subsystem/nodes.js.map +1 -1
  30. package/dist/subsystem/techIcons/index.d.ts +5 -0
  31. package/dist/subsystem/techIcons/index.d.ts.map +1 -1
  32. package/dist/subsystem/techIcons/index.js +3 -0
  33. package/dist/subsystem/techIcons/index.js.map +1 -1
  34. package/dist/utils/edgeLabel.d.ts +15 -1
  35. package/dist/utils/edgeLabel.d.ts.map +1 -1
  36. package/dist/utils/edgeLabel.js +25 -1
  37. package/dist/utils/edgeLabel.js.map +1 -1
  38. package/dist/utils/elkLayout.d.ts +47 -0
  39. package/dist/utils/elkLayout.d.ts.map +1 -1
  40. package/dist/utils/elkLayout.js +71 -7
  41. package/dist/utils/elkLayout.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/index.ts +8 -6
  44. package/src/stories/Subsystem/C4Graph/C4Component.stories.tsx +198 -0
  45. package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +42 -6
  46. package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +9 -7
  47. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +686 -2
  48. package/src/subsystem/C4Graph.tsx +242 -82
  49. package/src/subsystem/C4NodeCard.test.ts +32 -22
  50. package/src/subsystem/C4NodeCard.tsx +67 -39
  51. package/src/subsystem/IssueList.tsx +23 -0
  52. package/src/subsystem/SubsystemComponentGraph.tsx +38 -16
  53. package/src/subsystem/c4.protocolColor.test.ts +26 -0
  54. package/src/subsystem/c4.ts +76 -52
  55. package/src/subsystem/c4Evidence.test.ts +177 -237
  56. package/src/subsystem/c4Evidence.ts +160 -235
  57. package/src/subsystem/nodes.group.test.tsx +15 -0
  58. package/src/subsystem/nodes.tsx +7 -7
  59. package/src/subsystem/techIcons/index.tsx +8 -0
  60. package/src/utils/edgeLabel.test.ts +21 -2
  61. package/src/utils/edgeLabel.ts +29 -1
  62. package/src/utils/elkLayout.test.ts +16 -0
  63. package/src/utils/elkLayout.ts +119 -7
  64. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +0 -602
@@ -0,0 +1,198 @@
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 type { C4Edge, C4Group, C4Model, C4Element, C4Component } from '../../../subsystem/c4';
7
+
8
+ const meta = {
9
+ title: 'Subsystem/C4Graph/C4Component',
10
+ component: C4Graph,
11
+ parameters: { layout: 'fullscreen' },
12
+ tags: ['autodocs'],
13
+ decorators: [
14
+ (Story) => (
15
+ <ThemeProvider theme={defaultEditorTheme}>
16
+ <Story />
17
+ </ThemeProvider>
18
+ ),
19
+ ],
20
+ } satisfies Meta<typeof C4Graph>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // A C4 *component* diagram, hand-built.
27
+ //
28
+ // No single subsystem model owns these groupings — each one is stitched from
29
+ // constructs that recur across many models (e.g. `auditSubsystemModel` appears
30
+ // in 8). That is exactly why a component is an authored grouping: the evidence
31
+ // lives across models, and a principal agent proposes the union while a person
32
+ // confirms it.
33
+ //
34
+ // Two containers are drawn as frames; their components sit inside.
35
+ // ---------------------------------------------------------------------------
36
+
37
+ const SYSTEM = { id: 'system:acme', label: 'Subsystem Modeling', repoKey: 'pkg:github/acme/subsystem-modeling' };
38
+ const HOST = 'container:studio-host';
39
+ const RENDERER = 'container:studio-renderer';
40
+
41
+ /** A component at level 3, inside a container. */
42
+ function component(
43
+ over: Partial<C4Component> & Pick<C4Component, 'id' | 'label' | 'container'>,
44
+ ): C4Element {
45
+ return {
46
+ kind: 'component',
47
+ members: [],
48
+ constructs: [],
49
+ state: 'accepted',
50
+ parentId: over.container,
51
+ technology: 'Bun + Electrobun',
52
+ ...over,
53
+ };
54
+ }
55
+
56
+ const NODES: C4Element[] = [
57
+ // --- inside the host ------------------------------------------------------
58
+ component({
59
+ id: 'component:audit-verification',
60
+ label: 'Audit & verification',
61
+ container: HOST,
62
+ technology: 'Bun',
63
+ description: 'Checks a subsystem model and reports what is wrong.',
64
+ // The recurring constructs, stitched across ~8 models. See the story notes.
65
+ members: [
66
+ 'auditSubsystemModel', 'verifySubsystemComponent', 'verifyModelFiles',
67
+ 'createRegularAuditScheduler', 'applySubsystemModelAuditFix',
68
+ 'auditTopologyRelations', 'auditBoundaryFields',
69
+ ],
70
+ }),
71
+ component({
72
+ id: 'component:maintain-proposals',
73
+ label: 'Maintain & proposals',
74
+ container: HOST,
75
+ technology: 'Bun',
76
+ description: 'Runs a maintain agent and records the corrections it proposes.',
77
+ members: [
78
+ 'maintainSubsystemModel', 'runMaintainAgent', 'buildMaintainBrief',
79
+ 'createSubsystemModelProposal', 'acceptSubsystemModelProposal',
80
+ 'autoAcceptProposalIfConfident',
81
+ ],
82
+ }),
83
+ component({
84
+ id: 'component:model-store',
85
+ label: 'Model store I/O',
86
+ container: HOST,
87
+ technology: 'Bun fs',
88
+ description: 'Reads and writes model JSON under ~/.principal.',
89
+ members: [
90
+ 'getSubsystemModel', 'listSubsystemModels', 'updateSubsystemModel',
91
+ 'createSubsystemModel', 'readFile', 'writeFile', 'touchSubsystemModelOpened',
92
+ ],
93
+ }),
94
+ component({
95
+ id: 'component:session-pipeline',
96
+ label: 'Session pipeline',
97
+ container: HOST,
98
+ technology: 'Bun',
99
+ description: 'Turns raw OpenCode session rows into universal events.',
100
+ members: ['processSessionEvents', 'opencodeRowsToUniversalEvents', 'buildSessionIndex'],
101
+ }),
102
+
103
+ // --- inside the renderer --------------------------------------------------
104
+ component({
105
+ id: 'component:graph-view',
106
+ label: 'Graph view',
107
+ container: RENDERER,
108
+ technology: 'React + ELK',
109
+ description: 'Renders the component graph and its layout.',
110
+ }),
111
+ component({
112
+ id: 'component:model-views',
113
+ label: 'Model views',
114
+ container: RENDERER,
115
+ technology: 'React',
116
+ description: 'Lists and opens stored subsystem models.',
117
+ }),
118
+ component({
119
+ id: 'component:agent-sessions',
120
+ label: 'Agent sessions view',
121
+ container: RENDERER,
122
+ technology: 'React',
123
+ description: 'Shows maintain-agent sessions and their timelines.',
124
+ }),
125
+ component({
126
+ id: 'component:rpc-bridge',
127
+ label: 'RPC bridge',
128
+ container: RENDERER,
129
+ technology: 'Electrobun RPC',
130
+ description: 'Talks to the host process over the Electrobun bridge.',
131
+ }),
132
+ ];
133
+
134
+ /** The two containers at this level, drawn as frames. */
135
+ const GROUPS: C4Group[] = [
136
+ { id: HOST, kind: 'container', label: 'Subsystem Studio — host', parentId: SYSTEM.id, memberIds: NODES.filter((n) => n.parentId === HOST).map((n) => n.id) },
137
+ { id: RENDERER, kind: 'container', label: 'Subsystem Studio — renderer', parentId: SYSTEM.id, memberIds: NODES.filter((n) => n.parentId === RENDERER).map((n) => n.id) },
138
+ { id: SYSTEM.id, kind: 'system', label: SYSTEM.label, memberIds: [HOST, RENDERER] },
139
+ ];
140
+
141
+ /** A line between components — protocol-labelled, like the container view. */
142
+ function edge(id: string, source: string, target: string, protocol: string): C4Edge {
143
+ return { id, source, target, label: protocol, protocol, mechanisms: [], count: 1 };
144
+ }
145
+
146
+ const EDGES: C4Edge[] = [
147
+ edge('e:views-store', 'component:model-views', 'component:model-store', 'RPC'),
148
+ edge('e:views-audit', 'component:model-views', 'component:audit-verification', 'RPC'),
149
+ edge('e:audit-store', 'component:audit-verification', 'component:model-store', 'fs · JSON'),
150
+ edge('e:proposals-store', 'component:maintain-proposals', 'component:model-store', 'fs · JSON'),
151
+ edge('e:proposals-sessions', 'component:maintain-proposals', 'component:session-pipeline', 'feeds'),
152
+ edge('e:sessions-agent', 'component:agent-sessions', 'component:session-pipeline', 'RPC'),
153
+ edge('e:renderer-host', 'component:rpc-bridge', 'component:model-store', 'RPC'),
154
+ edge('e:views-graph', 'component:model-views', 'component:graph-view', 'calls'),
155
+ ];
156
+
157
+ const componentModel: C4Model = {
158
+ view: 'component',
159
+ system: SYSTEM,
160
+ nodes: NODES,
161
+ groups: GROUPS,
162
+ edges: EDGES,
163
+ };
164
+
165
+ function Demo() {
166
+ const [selected, setSelected] = useState<string | null>(null);
167
+ const model = useMemo(() => componentModel, []);
168
+ const components = model.nodes.filter((n) => n.kind === 'component').length;
169
+
170
+ return (
171
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
172
+ <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
173
+ <C4Graph model={model} title="Subsystem Modeling" onSelectNode={setSelected} />
174
+ </div>
175
+ <div
176
+ style={{
177
+ padding: '6px 12px',
178
+ display: 'flex',
179
+ alignItems: 'center',
180
+ gap: 18,
181
+ fontFamily: 'monospace',
182
+ fontSize: 12,
183
+ color: '#aaa',
184
+ borderTop: '1px solid #333',
185
+ }}
186
+ >
187
+ <span>{components} components in {model.groups.filter((g) => g.kind === 'container').length} containers</span>
188
+ <span>{model.edges.length} lines</span>
189
+ <span>selected: {selected ?? '(none)'}</span>
190
+ <span style={{ color: '#777' }}>each component is a grouping stitched across many models</span>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+
196
+ export const Components: Story = {
197
+ render: () => <Demo />,
198
+ };
@@ -3,7 +3,7 @@ import '@xyflow/react/dist/style.css';
3
3
  import type { Meta, StoryObj } from '@storybook/react';
4
4
  import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
5
5
  import { C4Graph } from '../../../subsystem/C4Graph';
6
- import type { C4Edge, C4Model, C4Node } from '../../../subsystem/c4';
6
+ import type { C4Edge, C4Model, C4Element, C4Container } from '../../../subsystem/c4';
7
7
 
8
8
  const meta = {
9
9
  title: 'Subsystem/C4Graph/C4Container',
@@ -25,7 +25,7 @@ type Story = StoryObj<typeof meta>;
25
25
  // ---------------------------------------------------------------------------
26
26
  // A C4 container diagram, hand-built.
27
27
  //
28
- // Nothing here is derived: the boxes are `C4Node`s an author wrote down, and
28
+ // Nothing here is derived: the boxes are authored `C4Element`s, and
29
29
  // the lines are explicit `C4Edge`s — no trails, no rollup. This is the shape a
30
30
  // caller hands to `C4Graph` once a model has been authored.
31
31
  //
@@ -35,18 +35,19 @@ type Story = StoryObj<typeof meta>;
35
35
 
36
36
  const SYSTEM = { id: 'system:acme', label: 'Subsystem Modeling', repoKey: 'pkg:github/acme/subsystem-modeling' };
37
37
 
38
- function container(over: Partial<C4Node> & Pick<C4Node, 'id' | 'label'>): C4Node {
38
+ function container(over: Partial<C4Container> & Pick<C4Container, 'id' | 'label'>): C4Element {
39
39
  return {
40
40
  kind: 'container',
41
41
  members: [],
42
42
  constructs: [],
43
43
  state: 'accepted',
44
44
  containerKind: 'application',
45
+ technology: '',
45
46
  ...over,
46
47
  };
47
48
  }
48
49
 
49
- const NODES: C4Node[] = [
50
+ const NODES: C4Element[] = [
50
51
  container({
51
52
  id: 'container:studio-host',
52
53
  label: 'Subsystem Studio — host',
@@ -79,7 +80,7 @@ const NODES: C4Node[] = [
79
80
  description: 'Every stored model and the correction queue.',
80
81
  parentId: SYSTEM.id,
81
82
  }),
82
- // An external system drawn inside the boundary — something we depend on.
83
+ // An external system: someone else's system, drawn OUTSIDE the boundary.
83
84
  {
84
85
  kind: 'external-system',
85
86
  id: 'external:opencode',
@@ -89,7 +90,6 @@ const NODES: C4Node[] = [
89
90
  state: 'accepted',
90
91
  members: [],
91
92
  constructs: [],
92
- parentId: SYSTEM.id,
93
93
  },
94
94
  // A person sits outside every system boundary.
95
95
  {
@@ -101,6 +101,42 @@ const NODES: C4Node[] = [
101
101
  members: [],
102
102
  constructs: [],
103
103
  },
104
+ // Components that live inside the host. Not drawn in the container view
105
+ // (`groups` frames only the containers), but carried on the model so the
106
+ // side panel can list them when the host is clicked.
107
+ {
108
+ kind: 'component',
109
+ id: 'component:audit-verification',
110
+ label: 'Audit & verification',
111
+ container: 'container:studio-host',
112
+ parentId: 'container:studio-host',
113
+ technology: 'Bun',
114
+ description: 'Checks a subsystem model and reports what is wrong.',
115
+ state: 'accepted',
116
+ members: ['auditSubsystemModel', 'verifySubsystemComponent', 'verifyModelFiles'],
117
+ },
118
+ {
119
+ kind: 'component',
120
+ id: 'component:maintain-proposals',
121
+ label: 'Maintain & proposals',
122
+ container: 'container:studio-host',
123
+ parentId: 'container:studio-host',
124
+ technology: 'Bun',
125
+ description: 'Runs a maintain agent and records the corrections it proposes.',
126
+ state: 'accepted',
127
+ members: ['maintainSubsystemModel', 'runMaintainAgent', 'createSubsystemModelProposal'],
128
+ },
129
+ {
130
+ kind: 'component',
131
+ id: 'component:model-store',
132
+ label: 'Model store I/O',
133
+ container: 'container:studio-host',
134
+ parentId: 'container:studio-host',
135
+ technology: 'Bun fs',
136
+ description: 'Reads and writes model JSON under ~/.principal.',
137
+ state: 'accepted',
138
+ members: ['getSubsystemModel', 'updateSubsystemModel', 'readFile'],
139
+ },
104
140
  ];
105
141
 
106
142
  const EDGES: C4Edge[] = [
@@ -12,7 +12,7 @@ import {
12
12
  nodeMissing,
13
13
  NODE_W,
14
14
  } from '../../../subsystem/C4NodeCard';
15
- import type { C4Node } from '../../../subsystem/c4';
15
+ import type { C4Element } from '../../../subsystem/c4';
16
16
 
17
17
  // ---------------------------------------------------------------------------
18
18
  // A card gallery: every C4 box variant, outside the graph.
@@ -25,7 +25,7 @@ import type { C4Node } from '../../../subsystem/c4';
25
25
  // is a plain presentational component, so it can be reviewed and diffed
26
26
  // without a layout engine in the way.
27
27
  //
28
- // Every card here is a flattened `C4Node` — the one shape a C4 box draws with,
28
+ // Every card here is an authored `C4Element` — the shape a C4 box draws with,
29
29
  // produces from an authored element, with the per-kind fields already resolved
30
30
  // to the one shape the card draws with.
31
31
  // ---------------------------------------------------------------------------
@@ -48,17 +48,19 @@ type Story = StoryObj<typeof meta>;
48
48
 
49
49
  // --- sample nodes ---------------------------------------------------------
50
50
 
51
- function node(over: Partial<C4Node> & { id: string; label: string }): C4Node {
51
+ function node(over: Record<string, unknown> & { id: string; label: string }): C4Element {
52
52
  return {
53
53
  kind: 'container',
54
+ containerKind: 'application',
55
+ technology: '',
56
+ state: 'proposed',
54
57
  members: [],
55
58
  constructs: [],
56
- state: 'proposed',
57
59
  ...over,
58
- };
60
+ } as C4Element;
59
61
  }
60
62
 
61
- const VARIANTS: Array<{ group: string; note: string; node: C4Node }> = [
63
+ const VARIANTS: Array<{ group: string; note: string; node: C4Element }> = [
62
64
  // `id` on each variant is its caption key, not a component field.
63
65
 
64
66
  // --- confirmation state: the axis that matters most --------------------
@@ -356,7 +358,7 @@ function Gallery() {
356
358
  width: 28,
357
359
  height: Math.round((28 * size.height) / size.width),
358
360
  borderRadius: Math.min(s.radius, 9),
359
- border: `${weight} ${s.dash} ${color}`,
361
+ border: `${weight} ${nodeStyle(node({ id: 'x', label: '', kind }), theme).dash} ${color}`,
360
362
  display: 'inline-block',
361
363
  }}
362
364
  />