@principal-ai/subsystems-react 0.23.4 → 0.24.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 +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +3 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +2 -2
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +30 -0
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -0
- package/dist/subsystem/ConstructsCatalog.js +349 -0
- package/dist/subsystem/ConstructsCatalog.js.map +1 -0
- package/dist/subsystem/DiagnosticToggle.d.ts +46 -0
- package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -0
- package/dist/subsystem/DiagnosticToggle.js +95 -0
- package/dist/subsystem/DiagnosticToggle.js.map +1 -0
- package/dist/subsystem/IssueList.d.ts +102 -0
- package/dist/subsystem/IssueList.d.ts.map +1 -0
- package/dist/subsystem/IssueList.js +282 -0
- package/dist/subsystem/IssueList.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +40 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +103 -65
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +32 -2
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +47 -0
- package/dist/subsystem/model.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +42 -0
- package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +287 -0
- package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +138 -0
- package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +159 -0
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +130 -0
- package/src/subsystem/ComponentDeclaration.tsx +4 -1
- package/src/subsystem/ConstructsCatalog.tsx +644 -0
- package/src/subsystem/DiagnosticToggle.tsx +160 -0
- package/src/subsystem/IssueList.tsx +488 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +156 -42
- package/src/subsystem/model.test.ts +34 -0
- package/src/subsystem/model.ts +72 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React, { 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 { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
|
|
6
|
+
import type { SubsystemEdgeView } from '../../../subsystem/model';
|
|
7
|
+
import { components, relations, walkthroughFromHops } from './fixtures';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Subsystem/ComponentGraph/EdgeViews',
|
|
11
|
+
component: SubsystemComponentGraph,
|
|
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 SubsystemComponentGraph>;
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
type Story = StoryObj<typeof meta>;
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// A graph that carries BOTH vocabularies between overlapping node pairs:
|
|
30
|
+
// svc --imports--> reader (topology)
|
|
31
|
+
// svc --calls--> reader (runtime hop)
|
|
32
|
+
// store --imports--> db (topology)
|
|
33
|
+
// store --calls--> db (runtime hop)
|
|
34
|
+
// so switching the label view visibly changes which labels appear.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const labelViewComponents = components([
|
|
37
|
+
['api', 'ApiHandler', 'function', 'src/api/handler.ts', 'pkg:github/principal-ai/agent-monitoring', 'HTTP entry point', 'handleRequest'],
|
|
38
|
+
['svc', 'SessionService', 'class', 'src/session/SessionService.ts', 'pkg:github/principal-ai/agent-monitoring', 'owns session persistence', 'SessionService'],
|
|
39
|
+
['reader', 'SessionReader', 'class', 'src/session/SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes raw records', 'SessionReader'],
|
|
40
|
+
['store', 'SessionStore', 'class', 'src/session/SessionStore.ts', 'pkg:github/principal-ai/agent-monitoring', 'retained session state', 'SessionStore'],
|
|
41
|
+
['event', 'SessionEvent', 'interface', 'src/types/SessionEvent.ts', 'pkg:github/principal-ai/agent-monitoring', 'output event shape', 'SessionEvent'],
|
|
42
|
+
['db', 'PostgresStore', 'external', 'src/db/PostgresStore.ts', 'pkg:github/principal-ai/agent-monitoring', 'external persistence driver', 'PostgresStore'],
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const labelViewRelations = relations([
|
|
46
|
+
['svc', 'reader', 'imports'],
|
|
47
|
+
['svc', 'event', 'references'],
|
|
48
|
+
['store', 'db', 'imports'],
|
|
49
|
+
['reader', 'event', 'references'],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const labelViewWalkthroughs = [
|
|
53
|
+
walkthroughFromHops('tl-capture', 'Capture session', [
|
|
54
|
+
['api', 'svc', 'calls', 'src/api/handler.ts', 42],
|
|
55
|
+
['svc', 'store', 'writes', 'src/session/SessionService.ts', 88],
|
|
56
|
+
['store', 'db', 'calls', 'src/session/SessionStore.ts', 120],
|
|
57
|
+
['svc', 'event', 'produces', 'src/session/SessionService.ts', 95],
|
|
58
|
+
]),
|
|
59
|
+
walkthroughFromHops('tl-normalize', 'Normalize records', [
|
|
60
|
+
['svc', 'reader', 'calls', 'src/session/SessionService.ts', 61],
|
|
61
|
+
['reader', 'event', 'produces', 'src/session/SessionReader.ts', 77],
|
|
62
|
+
]),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const VIEWS: { value: SubsystemEdgeView; label: string }[] = [
|
|
66
|
+
{ value: 'relations', label: 'Relations' },
|
|
67
|
+
{ value: 'walkthroughs', label: 'Walkthroughs' },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
function EdgeViewDemo() {
|
|
71
|
+
const [view, setView] = useState<SubsystemEdgeView>('relations');
|
|
72
|
+
const [showEdgeLabels, setShowEdgeLabels] = useState(true);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
display: 'flex',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
gap: 12,
|
|
81
|
+
padding: '8px 12px',
|
|
82
|
+
borderBottom: '1px solid #333',
|
|
83
|
+
fontFamily: 'monospace',
|
|
84
|
+
fontSize: 12,
|
|
85
|
+
color: '#ddd',
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<span style={{ color: '#888' }}>edge view</span>
|
|
89
|
+
<div style={{ display: 'flex', gap: 4 }}>
|
|
90
|
+
{VIEWS.map((v) => (
|
|
91
|
+
<button
|
|
92
|
+
key={v.value}
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => setView(v.value)}
|
|
95
|
+
style={{
|
|
96
|
+
padding: '3px 10px',
|
|
97
|
+
borderRadius: 4,
|
|
98
|
+
border: '1px solid #444',
|
|
99
|
+
background: view === v.value ? '#0893d2' : 'transparent',
|
|
100
|
+
color: view === v.value ? '#fff' : '#bbb',
|
|
101
|
+
cursor: 'pointer',
|
|
102
|
+
font: 'inherit',
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{v.label}
|
|
106
|
+
</button>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
<label style={{ display: 'flex', alignItems: 'center', gap: 6, marginLeft: 'auto' }}>
|
|
110
|
+
<input
|
|
111
|
+
type="checkbox"
|
|
112
|
+
checked={showEdgeLabels}
|
|
113
|
+
onChange={(e) => setShowEdgeLabels(e.target.checked)}
|
|
114
|
+
/>
|
|
115
|
+
showEdgeLabels
|
|
116
|
+
</label>
|
|
117
|
+
</div>
|
|
118
|
+
<div style={{ flex: 1, minHeight: 0 }}>
|
|
119
|
+
<SubsystemComponentGraph
|
|
120
|
+
components={labelViewComponents}
|
|
121
|
+
relations={labelViewRelations}
|
|
122
|
+
walkthroughs={labelViewWalkthroughs}
|
|
123
|
+
title="edge views"
|
|
124
|
+
description="The relation and walkthrough vocabularies are disjoint and never shown together. Toggle the view: **Relations** draws `svc --imports--> reader`; **Walkthroughs** draws `svc --calls--> reader`. The model is the same either way."
|
|
125
|
+
showEdgeLabels={showEdgeLabels}
|
|
126
|
+
edgeView={view}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Interactive toggle between the `relations` and `walkthroughs` edge views. */
|
|
134
|
+
export const ToggleEdgeView: Story = {
|
|
135
|
+
render: () => <EdgeViewDemo />,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** Storybook-controls variant of the same toggle. */
|
|
139
|
+
export const ViewControl: Story = {
|
|
140
|
+
render: (args) => (
|
|
141
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
142
|
+
<SubsystemComponentGraph
|
|
143
|
+
components={labelViewComponents}
|
|
144
|
+
relations={labelViewRelations}
|
|
145
|
+
walkthroughs={labelViewWalkthroughs}
|
|
146
|
+
showEdgeLabels={args.showEdgeLabels}
|
|
147
|
+
edgeView={args.edgeView}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
),
|
|
151
|
+
args: {
|
|
152
|
+
showEdgeLabels: true,
|
|
153
|
+
edgeView: 'relations',
|
|
154
|
+
},
|
|
155
|
+
argTypes: {
|
|
156
|
+
showEdgeLabels: { control: 'boolean' },
|
|
157
|
+
edgeView: { control: 'inline-radio', options: ['relations', 'walkthroughs'] },
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import '@xyflow/react/dist/style.css';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
5
|
+
import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
|
|
6
|
+
import { SubsystemIssueList, type SubsystemIssue } from '../../../subsystem/IssueList';
|
|
7
|
+
import { components, graphSpecFromEdges } from './fixtures';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Subsystem/ComponentGraph/Issues',
|
|
11
|
+
component: SubsystemComponentGraph,
|
|
12
|
+
parameters: {
|
|
13
|
+
layout: 'fullscreen',
|
|
14
|
+
},
|
|
15
|
+
decorators: [
|
|
16
|
+
(Story) => (
|
|
17
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
18
|
+
<Story />
|
|
19
|
+
</ThemeProvider>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
} satisfies Meta<typeof SubsystemComponentGraph>;
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof meta>;
|
|
26
|
+
|
|
27
|
+
const ISSUES: SubsystemIssue[] = [
|
|
28
|
+
{
|
|
29
|
+
id: 'i1',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
kind: 'missing_file',
|
|
32
|
+
message: 'File src/checkout/cartStore.ts is not present on disk (repo pkg:github/you/your-app).',
|
|
33
|
+
target: { kind: 'component', id: 'store', label: 'cartStore' },
|
|
34
|
+
fix: { label: 'Use graphify file' },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'i2',
|
|
38
|
+
severity: 'error',
|
|
39
|
+
kind: 'construct_mismatch',
|
|
40
|
+
message: 'Claimed function but graphify infers class.',
|
|
41
|
+
target: { kind: 'component', id: 'entry', label: 'checkoutApi' },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'i3',
|
|
45
|
+
severity: 'info',
|
|
46
|
+
kind: 'signature_unconfirmed',
|
|
47
|
+
message: 'Named parameter types are not present in the graphify cache.',
|
|
48
|
+
target: { kind: 'component', id: 'entry', label: 'checkoutApi' },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'i4',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
kind: 'topology_broken_endpoint',
|
|
54
|
+
message: 'Relation endpoint "store" does not resolve to a component.',
|
|
55
|
+
target: { kind: 'relation', id: 'r1', label: 'checkoutApi → cartStore', detail: 'writes' },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'i5',
|
|
59
|
+
severity: 'warn',
|
|
60
|
+
kind: 'boundary_module_file_mismatch',
|
|
61
|
+
message: 'Module "src/checkout" contains a file outside its declared root.',
|
|
62
|
+
target: { kind: 'module', id: 'src/checkout', label: 'src/checkout' },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'i6',
|
|
66
|
+
severity: 'info',
|
|
67
|
+
kind: 'walkthrough',
|
|
68
|
+
message: 'Step 2 references a line that moved (was :42).',
|
|
69
|
+
target: { kind: 'walkthrough', id: 'flow-1', label: 'Checkout', detail: 'step 2' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'i7',
|
|
73
|
+
severity: 'warn',
|
|
74
|
+
kind: 'boundary_process_nest_disagree',
|
|
75
|
+
message: 'Component is nested in process "api" but Graphify places it outside.',
|
|
76
|
+
target: { kind: 'module', id: 'api', label: 'api' },
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const graphComponents = components([
|
|
81
|
+
['entry', 'checkoutApi', 'function', 'src/checkout/api.ts', 'pkg:github/you/your-app', 'Handles cart requests.', 'checkoutApi'],
|
|
82
|
+
['store', 'cartStore', 'store', 'src/checkout/cartStore.ts', 'pkg:github/you/your-app', 'Retained cart state.', 'cartStore'],
|
|
83
|
+
['stripe', 'Stripe', 'external', '', 'external', undefined, undefined],
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const graphEdges = graphSpecFromEdges([
|
|
87
|
+
['entry', 'store', 'writes'],
|
|
88
|
+
['entry', 'stripe', 'calls'],
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
/** The catalogue, grouped by verification layer. */
|
|
92
|
+
export const List: Story = {
|
|
93
|
+
render: () => (
|
|
94
|
+
<div style={{ display: 'flex', flexDirection: 'column', width: 380, height: '100vh', borderRight: '1px solid #333' }}>
|
|
95
|
+
<SubsystemIssueList
|
|
96
|
+
issues={ISSUES}
|
|
97
|
+
onSelectIssue={() => {}}
|
|
98
|
+
onApplyFix={() => {}}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** All four layers clean — the happy state. */
|
|
105
|
+
export const AllGood: Story = {
|
|
106
|
+
render: () => (
|
|
107
|
+
<div style={{ display: 'flex', flexDirection: 'column', width: 380, height: '100vh', borderRight: '1px solid #333' }}>
|
|
108
|
+
<SubsystemIssueList issues={[]} />
|
|
109
|
+
</div>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** In place: diagnostics active, so the sidebar bottom panel is the issue list. */
|
|
114
|
+
export const GraphWithIssues: Story = {
|
|
115
|
+
render: () => (
|
|
116
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
117
|
+
<SubsystemComponentGraph
|
|
118
|
+
components={graphComponents}
|
|
119
|
+
relations={graphEdges.relations}
|
|
120
|
+
walkthroughs={graphEdges.walkthroughs}
|
|
121
|
+
title="Checkout"
|
|
122
|
+
description="A small e-commerce checkout subsystem."
|
|
123
|
+
diagnostic={{ status: 'issues', issueCount: 5, onOpen: () => {} }}
|
|
124
|
+
issues={ISSUES}
|
|
125
|
+
onSelectIssue={() => {}}
|
|
126
|
+
onApplyIssueFix={() => {}}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
};
|
|
@@ -118,6 +118,8 @@ export interface ComponentDeclarationProps {
|
|
|
118
118
|
onVerify?: (componentId: string) => void;
|
|
119
119
|
/** Live verification status for the selected component. */
|
|
120
120
|
verification?: ComponentVerificationState | null;
|
|
121
|
+
/** Start with the file path row visible (catalog / source-first views). */
|
|
122
|
+
defaultShowFile?: boolean;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
function verificationSummary(
|
|
@@ -256,11 +258,12 @@ export function ComponentDeclaration({
|
|
|
256
258
|
maxWidth,
|
|
257
259
|
onVerify,
|
|
258
260
|
verification,
|
|
261
|
+
defaultShowFile = false,
|
|
259
262
|
}: ComponentDeclarationProps) {
|
|
260
263
|
const { theme, mode } = useTheme();
|
|
261
264
|
const pierreSyntaxTheme = resolvePierreSyntaxThemeName(mode);
|
|
262
265
|
const [fileHovered, setFileHovered] = useState(false);
|
|
263
|
-
const [showFile, setShowFile] = useState(
|
|
266
|
+
const [showFile, setShowFile] = useState(defaultShowFile);
|
|
264
267
|
const [showPurpose, setShowPurpose] = useState(false);
|
|
265
268
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
266
269
|
const color = componentColor(component, pierreSyntaxTheme);
|