@principal-ai/principal-view-react 0.16.60 → 0.16.62
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 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreThroughlineCodeView.d.ts +19 -0
- package/dist/pierre/PierreThroughlineCodeView.d.ts.map +1 -0
- package/dist/pierre/PierreThroughlineCodeView.js +139 -0
- package/dist/pierre/PierreThroughlineCodeView.js.map +1 -0
- package/dist/pierre/constructColors.d.ts.map +1 -1
- package/dist/pierre/constructColors.js +1 -4
- package/dist/pierre/constructColors.js.map +1 -1
- package/dist/pierre/index.d.ts +2 -0
- package/dist/pierre/index.d.ts.map +1 -1
- package/dist/pierre/index.js +1 -0
- package/dist/pierre/index.js.map +1 -1
- package/dist/subsystem/FileDrawer.d.ts +8 -7
- package/dist/subsystem/FileDrawer.d.ts.map +1 -1
- package/dist/subsystem/FileDrawer.js +9 -9
- package/dist/subsystem/FileDrawer.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +17 -5
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +76 -21
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +64 -10
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +53 -8
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +16 -8
- package/dist/subsystem/nodes.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +15 -2
- package/src/pierre/PierreThroughlineCodeView.tsx +210 -0
- package/src/pierre/constructColors.ts +1 -4
- package/src/pierre/index.ts +2 -0
- package/src/stories/Pierre/CodeView.stories.tsx +196 -0
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +62 -4
- package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +216 -0
- package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +38 -3
- package/src/subsystem/FileDrawer.tsx +11 -10
- package/src/subsystem/SubsystemComponentGraph.tsx +121 -34
- package/src/subsystem/model.test.ts +44 -0
- package/src/subsystem/model.ts +105 -16
- package/src/subsystem/nodes.tsx +24 -12
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo: Pierre CodeView with snippets from different files.
|
|
3
|
+
*
|
|
4
|
+
* Throughlines want many short windows across files in one scroll —
|
|
5
|
+
* CodeView is the Pierre primitive for that (virtualized list of file items).
|
|
6
|
+
*/
|
|
7
|
+
import React, { useMemo, useRef } from 'react';
|
|
8
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
9
|
+
import {
|
|
10
|
+
CodeView,
|
|
11
|
+
type CodeViewHandle,
|
|
12
|
+
type CodeViewItem,
|
|
13
|
+
} from '@pierre/diffs/react';
|
|
14
|
+
import { ThemeProvider, defaultEditorTheme, useTheme } from '@principal-ade/industry-theme';
|
|
15
|
+
import { sliceSnippetWindow } from '../../pierre/sliceSnippet';
|
|
16
|
+
import { resolvePierreSyntaxThemeName } from '../../pierre/pierreSyntaxTheme';
|
|
17
|
+
import componentDeclarationSource from '../../subsystem/ComponentDeclaration.tsx?raw';
|
|
18
|
+
import resolveSource from '../../graphify/resolve.ts?raw';
|
|
19
|
+
import modelSource from '../../subsystem/model.ts?raw';
|
|
20
|
+
|
|
21
|
+
type SnippetSpec = {
|
|
22
|
+
id: string;
|
|
23
|
+
path: string;
|
|
24
|
+
contents: string;
|
|
25
|
+
startLine: number;
|
|
26
|
+
endLine: number;
|
|
27
|
+
focusLine?: number;
|
|
28
|
+
label: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const SNIPPETS: SnippetSpec[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'step-1',
|
|
34
|
+
path: 'packages/react/src/subsystem/ComponentDeclaration.tsx',
|
|
35
|
+
contents: componentDeclarationSource,
|
|
36
|
+
startLine: 252,
|
|
37
|
+
endLine: 270,
|
|
38
|
+
focusLine: 252,
|
|
39
|
+
label: '1 · ComponentDeclaration export',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'step-2',
|
|
43
|
+
path: 'packages/react/src/graphify/resolve.ts',
|
|
44
|
+
contents: resolveSource,
|
|
45
|
+
startLine: 1,
|
|
46
|
+
endLine: 40,
|
|
47
|
+
focusLine: 1,
|
|
48
|
+
label: '2 · graphify resolve',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'step-3',
|
|
52
|
+
path: 'packages/react/src/subsystem/model.ts',
|
|
53
|
+
contents: modelSource,
|
|
54
|
+
startLine: 216,
|
|
55
|
+
endLine: 250,
|
|
56
|
+
focusLine: 216,
|
|
57
|
+
label: '3 · Throughline types',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const meta = {
|
|
62
|
+
title: 'Pierre/CodeView',
|
|
63
|
+
parameters: {
|
|
64
|
+
layout: 'fullscreen',
|
|
65
|
+
},
|
|
66
|
+
tags: ['autodocs'],
|
|
67
|
+
decorators: [
|
|
68
|
+
(Story) => (
|
|
69
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
70
|
+
<Story />
|
|
71
|
+
</ThemeProvider>
|
|
72
|
+
),
|
|
73
|
+
],
|
|
74
|
+
} satisfies Meta;
|
|
75
|
+
|
|
76
|
+
export default meta;
|
|
77
|
+
type Story = StoryObj;
|
|
78
|
+
|
|
79
|
+
function MultiFileSnippetsDemo() {
|
|
80
|
+
const { theme, mode } = useTheme();
|
|
81
|
+
const viewRef = useRef<CodeViewHandle>(null);
|
|
82
|
+
|
|
83
|
+
const items = useMemo((): CodeViewItem[] => {
|
|
84
|
+
return SNIPPETS.map((spec) => {
|
|
85
|
+
const slice = sliceSnippetWindow(
|
|
86
|
+
spec.contents,
|
|
87
|
+
spec.startLine,
|
|
88
|
+
spec.endLine,
|
|
89
|
+
2,
|
|
90
|
+
spec.focusLine ?? spec.startLine,
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
id: spec.id,
|
|
94
|
+
type: 'file' as const,
|
|
95
|
+
file: {
|
|
96
|
+
name: spec.path,
|
|
97
|
+
contents: slice.contents,
|
|
98
|
+
cacheKey: `${spec.id}:${slice.sliceStart}-${slice.sliceEnd}`,
|
|
99
|
+
},
|
|
100
|
+
annotations:
|
|
101
|
+
slice.focusOffset != null
|
|
102
|
+
? [{ lineNumber: slice.focusOffset }]
|
|
103
|
+
: undefined,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
const options = useMemo(
|
|
109
|
+
() => ({
|
|
110
|
+
theme: {
|
|
111
|
+
dark: resolvePierreSyntaxThemeName('dark'),
|
|
112
|
+
light: resolvePierreSyntaxThemeName('light'),
|
|
113
|
+
} as const,
|
|
114
|
+
stickyHeaders: true,
|
|
115
|
+
layout: { paddingTop: 12, paddingBottom: 24, gap: 16 },
|
|
116
|
+
...(mode === 'light' || mode === 'dark' ? { themeType: mode as 'light' | 'dark' } : {}),
|
|
117
|
+
}),
|
|
118
|
+
[mode],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
style={{
|
|
124
|
+
display: 'flex',
|
|
125
|
+
height: '100vh',
|
|
126
|
+
background: theme.colors.background,
|
|
127
|
+
color: theme.colors.text,
|
|
128
|
+
fontFamily: theme.fonts.sans,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<aside
|
|
132
|
+
style={{
|
|
133
|
+
width: 260,
|
|
134
|
+
flexShrink: 0,
|
|
135
|
+
borderRight: `1px solid ${theme.colors.border ?? '#333'}`,
|
|
136
|
+
padding: 16,
|
|
137
|
+
display: 'flex',
|
|
138
|
+
flexDirection: 'column',
|
|
139
|
+
gap: 8,
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>
|
|
143
|
+
Throughline steps
|
|
144
|
+
</div>
|
|
145
|
+
<div style={{ fontSize: 12, color: theme.colors.textSecondary, marginBottom: 8 }}>
|
|
146
|
+
Pierre <code>CodeView</code> — one scroll, snippets from different files.
|
|
147
|
+
</div>
|
|
148
|
+
{SNIPPETS.map((spec) => (
|
|
149
|
+
<button
|
|
150
|
+
key={spec.id}
|
|
151
|
+
type="button"
|
|
152
|
+
onClick={() => viewRef.current?.scrollTo({ type: 'item', id: spec.id })}
|
|
153
|
+
style={{
|
|
154
|
+
textAlign: 'left',
|
|
155
|
+
padding: '8px 10px',
|
|
156
|
+
borderRadius: 6,
|
|
157
|
+
border: `1px solid ${theme.colors.border ?? '#333'}`,
|
|
158
|
+
background: theme.colors.surface ?? 'transparent',
|
|
159
|
+
color: theme.colors.text,
|
|
160
|
+
cursor: 'pointer',
|
|
161
|
+
fontSize: 12,
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
{spec.label}
|
|
165
|
+
<div
|
|
166
|
+
style={{
|
|
167
|
+
marginTop: 4,
|
|
168
|
+
fontFamily: theme.fonts.monospace,
|
|
169
|
+
fontSize: 10,
|
|
170
|
+
color: theme.colors.textSecondary,
|
|
171
|
+
overflow: 'hidden',
|
|
172
|
+
textOverflow: 'ellipsis',
|
|
173
|
+
whiteSpace: 'nowrap',
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{spec.path.split('/').pop()}:{spec.startLine}
|
|
177
|
+
</div>
|
|
178
|
+
</button>
|
|
179
|
+
))}
|
|
180
|
+
</aside>
|
|
181
|
+
<div style={{ flex: 1, minWidth: 0, minHeight: 0 }}>
|
|
182
|
+
<CodeView
|
|
183
|
+
ref={viewRef}
|
|
184
|
+
items={items}
|
|
185
|
+
options={options}
|
|
186
|
+
style={{ height: '100%', overflow: 'auto' }}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const MultiFileSnippets: Story = {
|
|
194
|
+
name: 'Multi-file snippets',
|
|
195
|
+
render: () => <MultiFileSnippetsDemo />,
|
|
196
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
2
|
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';
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
SubsystemComponentEdge,
|
|
9
9
|
SubsystemThroughline,
|
|
10
10
|
} from '../../../subsystem/model';
|
|
11
|
+
import { PierreThroughlineCodeView } from '../../../pierre';
|
|
12
|
+
import type { ThroughlineViewerContext } from '../../../subsystem/SubsystemComponentGraph';
|
|
11
13
|
|
|
12
14
|
const meta = {
|
|
13
15
|
title: 'Subsystem/ComponentGraph/Flows',
|
|
@@ -32,10 +34,53 @@ type Story = StoryObj<typeof meta>;
|
|
|
32
34
|
// Mirror of the retrofitted electron-app drawing graph: the sidebar's Files
|
|
33
35
|
// panel swaps to a Flows panel listing three throughlines (open / save /
|
|
34
36
|
// delete). Clicking a flow row toggles its steps; clicking a step focuses
|
|
35
|
-
// that step's edge
|
|
36
|
-
// dimmed; the rest of the
|
|
37
|
+
// that step's edge, frames it on the canvas, and scrolls the bottom CodeView
|
|
38
|
+
// to that step's snippet. Other opened flows stay dimmed; the rest of the
|
|
39
|
+
// graph is hidden.
|
|
37
40
|
// ---------------------------------------------------------------------------
|
|
38
41
|
|
|
42
|
+
/** Build enough placeholder lines so step `line` values land inside the file. */
|
|
43
|
+
function fakeSource(path: string, markedLines: number[]): string {
|
|
44
|
+
const maxLine = Math.max(80, ...markedLines);
|
|
45
|
+
const marks = new Set(markedLines);
|
|
46
|
+
const lines: string[] = [`// ${path}`, ''];
|
|
47
|
+
for (let i = 3; i <= maxLine; i++) {
|
|
48
|
+
if (marks.has(i)) {
|
|
49
|
+
lines.push(`export function stepAtLine${i}() {`);
|
|
50
|
+
lines.push(` return ${i};`);
|
|
51
|
+
lines.push(`}`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
} else {
|
|
54
|
+
lines.push(`// context line ${i}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return lines.join('\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const storyFiles: Record<string, string> = {
|
|
61
|
+
'src/panels/DrawingsLeftPanel.tsx': fakeSource('src/panels/DrawingsLeftPanel.tsx', [
|
|
62
|
+
56, 85,
|
|
63
|
+
]),
|
|
64
|
+
'src/hooks/useDrawingsHost.ts': fakeSource('src/hooks/useDrawingsHost.ts', [
|
|
65
|
+
45, 64, 66,
|
|
66
|
+
]),
|
|
67
|
+
'src/workspace/WorkspaceShell.tsx': fakeSource('src/workspace/WorkspaceShell.tsx', [
|
|
68
|
+
369, 372,
|
|
69
|
+
]),
|
|
70
|
+
'src/storage/drawingsStorage.ts': fakeSource('src/storage/drawingsStorage.ts', [90]),
|
|
71
|
+
'src/components/DrawingTabContent.tsx': fakeSource('src/components/DrawingTabContent.tsx', [
|
|
72
|
+
83, 112, 121,
|
|
73
|
+
]),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function readStoryFile(path: string): Promise<string> {
|
|
77
|
+
const content = storyFiles[path];
|
|
78
|
+
if (content == null) {
|
|
79
|
+
return Promise.reject(new Error(`No story fixture for ${path}`));
|
|
80
|
+
}
|
|
81
|
+
return Promise.resolve(content);
|
|
82
|
+
}
|
|
83
|
+
|
|
39
84
|
const drawingComponents: SubsystemComponent[] = [
|
|
40
85
|
{
|
|
41
86
|
id: 'panel',
|
|
@@ -148,6 +193,18 @@ const drawingThroughlines: SubsystemThroughline[] = [
|
|
|
148
193
|
];
|
|
149
194
|
|
|
150
195
|
function FlowsDemo() {
|
|
196
|
+
const renderThroughlineViewer = useCallback(
|
|
197
|
+
({ throughline, stepIndex }: ThroughlineViewerContext) => (
|
|
198
|
+
<PierreThroughlineCodeView
|
|
199
|
+
throughline={throughline}
|
|
200
|
+
stepIndex={stepIndex}
|
|
201
|
+
readFile={readStoryFile}
|
|
202
|
+
contextLines={4}
|
|
203
|
+
/>
|
|
204
|
+
),
|
|
205
|
+
[],
|
|
206
|
+
);
|
|
207
|
+
|
|
151
208
|
return (
|
|
152
209
|
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
153
210
|
<SubsystemComponentGraph
|
|
@@ -155,7 +212,8 @@ function FlowsDemo() {
|
|
|
155
212
|
edges={drawingEdges}
|
|
156
213
|
throughlines={drawingThroughlines}
|
|
157
214
|
title="drawing-files flow"
|
|
158
|
-
description="Three throughlines over one graph — opening, saving, and deleting a drawing. The sidebar's **Flows** panel lists each step by **symbol
|
|
215
|
+
description="Three throughlines over one graph — opening, saving, and deleting a drawing. The sidebar's **Flows** panel lists each step by **symbol**; clicking a step focuses that edge and scrolls the bottom CodeView to that snippet."
|
|
216
|
+
renderThroughlineViewer={renderThroughlineViewer}
|
|
159
217
|
renderFileViewer={(file, opts) => (
|
|
160
218
|
<div
|
|
161
219
|
style={{
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import '@xyflow/react/dist/style.css';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
|
|
4
|
+
import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
|
|
5
|
+
import type { SubsystemComponent, SubsystemComponentEdge } from '../../../subsystem/model';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Subsystem/ComponentGraph/FrameworkStereotype',
|
|
9
|
+
component: SubsystemComponentGraph,
|
|
10
|
+
parameters: { layout: 'fullscreen' },
|
|
11
|
+
tags: ['autodocs'],
|
|
12
|
+
decorators: [
|
|
13
|
+
(Story) => (
|
|
14
|
+
<ThemeProvider theme={defaultEditorTheme}>
|
|
15
|
+
<Story />
|
|
16
|
+
</ThemeProvider>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
} satisfies Meta<typeof SubsystemComponentGraph>;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof meta>;
|
|
23
|
+
|
|
24
|
+
const purl = 'pkg:github/principal-ai/principal-view-core-library';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Same language construct (`function`) with and without framework stereotypes.
|
|
28
|
+
* Badge text prefers `framework · stereotype` so UI units read as components /
|
|
29
|
+
* hooks instead of plain functions; construct color stays function-indigo.
|
|
30
|
+
*/
|
|
31
|
+
const reactUiComponents: SubsystemComponent[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'create-graph',
|
|
34
|
+
name: 'createSubsystemGraph',
|
|
35
|
+
construct: 'function',
|
|
36
|
+
file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
|
|
37
|
+
purl,
|
|
38
|
+
symbol: 'createSubsystemGraph',
|
|
39
|
+
purpose: 'language-only helper — no framework / stereotype',
|
|
40
|
+
process: 'trail-viewer/host',
|
|
41
|
+
layer: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'http-entry',
|
|
45
|
+
name: 'handleSubsystemGraphRequest',
|
|
46
|
+
construct: 'function',
|
|
47
|
+
file: 'packages/trail-viewer/src/bun/http-server.ts',
|
|
48
|
+
purl,
|
|
49
|
+
symbol: 'handleSubsystemGraphRequest',
|
|
50
|
+
purpose: 'HTTP bridge entry — topology role, still no React stereotype',
|
|
51
|
+
role: 'entry',
|
|
52
|
+
process: 'trail-viewer/host',
|
|
53
|
+
layer: 1,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'sessions-view',
|
|
57
|
+
name: 'AgentSessionsOverviewView',
|
|
58
|
+
construct: 'function',
|
|
59
|
+
file: 'packages/trail-viewer/src/mainview/views/AgentSessions.tsx',
|
|
60
|
+
purl,
|
|
61
|
+
symbol: 'AgentSessionsOverviewView',
|
|
62
|
+
purpose: 'React view — construct stays function; stereotype labels it',
|
|
63
|
+
framework: 'react',
|
|
64
|
+
stereotype: 'component',
|
|
65
|
+
process: 'trail-viewer/renderer',
|
|
66
|
+
layer: 2,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'analysis-view',
|
|
70
|
+
name: 'AnalysisView',
|
|
71
|
+
construct: 'function',
|
|
72
|
+
file: 'packages/trail-viewer/src/mainview/views/AnalysisView.tsx',
|
|
73
|
+
purl,
|
|
74
|
+
symbol: 'AnalysisView',
|
|
75
|
+
purpose: 'another React component alongside a hook',
|
|
76
|
+
framework: 'react',
|
|
77
|
+
stereotype: 'component',
|
|
78
|
+
process: 'trail-viewer/renderer',
|
|
79
|
+
layer: 2,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'drawings-host',
|
|
83
|
+
name: 'useDrawingsHost',
|
|
84
|
+
construct: 'function',
|
|
85
|
+
file: 'packages/trail-viewer/src/mainview/hooks/useDrawingsHost.ts',
|
|
86
|
+
purl,
|
|
87
|
+
symbol: 'useDrawingsHost',
|
|
88
|
+
purpose: 'React hook — same construct, different stereotype',
|
|
89
|
+
framework: 'react',
|
|
90
|
+
stereotype: 'hook',
|
|
91
|
+
process: 'trail-viewer/renderer',
|
|
92
|
+
layer: 2,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'graph-doc-type',
|
|
96
|
+
name: 'SubsystemGraphDocument',
|
|
97
|
+
construct: 'type_alias',
|
|
98
|
+
file: 'packages/core/src/types/subsystem-graph.ts',
|
|
99
|
+
purl,
|
|
100
|
+
symbol: 'SubsystemGraphDocument',
|
|
101
|
+
purpose: 'type-only — framework / stereotype stay empty',
|
|
102
|
+
layer: 3,
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
const reactUiEdges: SubsystemComponentEdge[] = [
|
|
107
|
+
{
|
|
108
|
+
id: 'http-to-create',
|
|
109
|
+
from: 'http-entry',
|
|
110
|
+
to: 'create-graph',
|
|
111
|
+
mechanism: 'calls',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'sessions-uses-hook',
|
|
115
|
+
from: 'sessions-view',
|
|
116
|
+
to: 'drawings-host',
|
|
117
|
+
mechanism: 'uses',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'analysis-uses-type',
|
|
121
|
+
from: 'analysis-view',
|
|
122
|
+
to: 'graph-doc-type',
|
|
123
|
+
mechanism: 'references',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'sessions-to-analysis',
|
|
127
|
+
from: 'sessions-view',
|
|
128
|
+
to: 'analysis-view',
|
|
129
|
+
mechanism: 'feeds',
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
export const ReactComponentsAndHooks: Story = {
|
|
134
|
+
render: () => (
|
|
135
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
136
|
+
<SubsystemComponentGraph
|
|
137
|
+
title="Framework + stereotype"
|
|
138
|
+
description="construct stays language-shaped (function / type_alias). framework + stereotype label React units as component / hook without inventing a react_component construct. Empty fields mean language-only."
|
|
139
|
+
components={reactUiComponents}
|
|
140
|
+
edges={reactUiEdges}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Nest-style stereotypes on the same ontology — controller / middleware / guard. */
|
|
147
|
+
const nestComponents: SubsystemComponent[] = [
|
|
148
|
+
{
|
|
149
|
+
id: 'graphs-controller',
|
|
150
|
+
name: 'SubsystemGraphsController',
|
|
151
|
+
construct: 'class',
|
|
152
|
+
file: 'src/graphs/graphs.controller.ts',
|
|
153
|
+
purl: 'pkg:github/acme/api',
|
|
154
|
+
symbol: 'SubsystemGraphsController',
|
|
155
|
+
purpose: 'Nest HTTP controller',
|
|
156
|
+
framework: 'nestjs',
|
|
157
|
+
stereotype: 'controller',
|
|
158
|
+
role: 'entry',
|
|
159
|
+
layer: 1,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'auth-guard',
|
|
163
|
+
name: 'AuthGuard',
|
|
164
|
+
construct: 'class',
|
|
165
|
+
file: 'src/auth/auth.guard.ts',
|
|
166
|
+
purl: 'pkg:github/acme/api',
|
|
167
|
+
symbol: 'AuthGuard',
|
|
168
|
+
purpose: 'request guard',
|
|
169
|
+
framework: 'nestjs',
|
|
170
|
+
stereotype: 'guard',
|
|
171
|
+
layer: 1,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'logging-mw',
|
|
175
|
+
name: 'LoggingMiddleware',
|
|
176
|
+
construct: 'function',
|
|
177
|
+
file: 'src/logging/logging.middleware.ts',
|
|
178
|
+
purl: 'pkg:github/acme/api',
|
|
179
|
+
symbol: 'LoggingMiddleware',
|
|
180
|
+
purpose: 'Express-style middleware function under Nest',
|
|
181
|
+
framework: 'nestjs',
|
|
182
|
+
stereotype: 'middleware',
|
|
183
|
+
layer: 1,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'graphs-service',
|
|
187
|
+
name: 'GraphsService',
|
|
188
|
+
construct: 'class',
|
|
189
|
+
file: 'src/graphs/graphs.service.ts',
|
|
190
|
+
purl: 'pkg:github/acme/api',
|
|
191
|
+
symbol: 'GraphsService',
|
|
192
|
+
purpose: 'injectable service — stereotype without UI',
|
|
193
|
+
framework: 'nestjs',
|
|
194
|
+
stereotype: 'injectable',
|
|
195
|
+
layer: 2,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const nestEdges: SubsystemComponentEdge[] = [
|
|
200
|
+
{ id: 'mw-to-guard', from: 'logging-mw', to: 'auth-guard', mechanism: 'uses' },
|
|
201
|
+
{ id: 'guard-to-ctrl', from: 'auth-guard', to: 'graphs-controller', mechanism: 'uses' },
|
|
202
|
+
{ id: 'ctrl-to-svc', from: 'graphs-controller', to: 'graphs-service', mechanism: 'calls' },
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
export const NestControllerStack: Story = {
|
|
206
|
+
render: () => (
|
|
207
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
208
|
+
<SubsystemComponentGraph
|
|
209
|
+
title="Nest framework stereotypes"
|
|
210
|
+
description="Same optional fields work outside React: class/function constructs plus nestjs controller / guard / middleware / injectable stereotypes."
|
|
211
|
+
components={nestComponents}
|
|
212
|
+
edges={nestEdges}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
),
|
|
216
|
+
};
|
|
@@ -38,8 +38,8 @@ type Story = StoryObj<typeof meta>;
|
|
|
38
38
|
// Targeted side-by-side spotlights: each construct's DATA next to its
|
|
39
39
|
// RENDERING — no full graph shell. Three spotlights: nodes, mechanisms
|
|
40
40
|
// (edges), and anatomy (declaration panels). The mapping these make visible:
|
|
41
|
-
// construct:→ node anatomy + color,
|
|
42
|
-
// mechanism → edge color/style.
|
|
41
|
+
// construct:→ node anatomy + color, framework+stereotype → badge label,
|
|
42
|
+
// role → topology glyph, detail → drill-down, mechanism → edge color/style.
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
|
|
45
45
|
/** Data snippet card — the raw object on the left of every row. */
|
|
@@ -209,6 +209,35 @@ const nodeSpotlights: Array<{ label: string; component: SubsystemComponent; note
|
|
|
209
209
|
symbol: 'createSubsystemGraph',
|
|
210
210
|
},
|
|
211
211
|
},
|
|
212
|
+
{
|
|
213
|
+
label: 'framework: react · stereotype: component — still construct: function',
|
|
214
|
+
note: 'badge reads "react · component"; name wears <> ; color stays function',
|
|
215
|
+
component: {
|
|
216
|
+
id: 'analysis-view',
|
|
217
|
+
name: 'AnalysisView',
|
|
218
|
+
construct: 'function',
|
|
219
|
+
file: 'packages/trail-viewer/src/mainview/views/AnalysisView.tsx',
|
|
220
|
+
purl: corePurl,
|
|
221
|
+
symbol: 'AnalysisView',
|
|
222
|
+
framework: 'react',
|
|
223
|
+
stereotype: 'component',
|
|
224
|
+
purpose: 'renders a saved analysis as concept cards',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: 'framework: react · stereotype: hook',
|
|
229
|
+
note: 'same construct, different stereotype — badge "react · hook", keeps ()',
|
|
230
|
+
component: {
|
|
231
|
+
id: 'drawings-host',
|
|
232
|
+
name: 'useDrawingsHost',
|
|
233
|
+
construct: 'function',
|
|
234
|
+
file: 'packages/trail-viewer/src/mainview/hooks/useDrawingsHost.ts',
|
|
235
|
+
purl: corePurl,
|
|
236
|
+
symbol: 'useDrawingsHost',
|
|
237
|
+
framework: 'react',
|
|
238
|
+
stereotype: 'hook',
|
|
239
|
+
},
|
|
240
|
+
},
|
|
212
241
|
{
|
|
213
242
|
label: 'role: entry on construct: function — boundary element',
|
|
214
243
|
note: 'orange overrides construct:color; anatomy inherited from the function',
|
|
@@ -331,7 +360,13 @@ function NodeSpotlightsDemo() {
|
|
|
331
360
|
{nodeSpotlights.map(({ component }) => {
|
|
332
361
|
const on = activeIds.has(component.id);
|
|
333
362
|
const color = constructColors[component.construct];
|
|
334
|
-
const chip = component.
|
|
363
|
+
const chip = component.stereotype
|
|
364
|
+
? component.framework
|
|
365
|
+
? `${component.framework} · ${component.stereotype}`
|
|
366
|
+
: component.stereotype
|
|
367
|
+
: component.role != null
|
|
368
|
+
? `${component.construct} · ${component.role}`
|
|
369
|
+
: component.construct;
|
|
335
370
|
return (
|
|
336
371
|
<button
|
|
337
372
|
key={component.id}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FileDrawer — bottom panel that slides up from the bottom of the graph area
|
|
3
|
-
* to show
|
|
4
|
-
*
|
|
3
|
+
* to show file / throughline code. Opened by sidebar file-tree clicks,
|
|
4
|
+
* declaration links, and throughline step focus; content is injected as
|
|
5
|
+
* children by the graph component.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { useEffect } from 'react';
|
|
@@ -9,22 +10,22 @@ import type { ReactNode } from 'react';
|
|
|
9
10
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
10
11
|
import { X } from 'lucide-react';
|
|
11
12
|
|
|
12
|
-
/** Bottom panel that slides up from the bottom of the graph area
|
|
13
|
-
*
|
|
14
|
-
* alike. Sits in normal flow (canvas shrinks while open, nothing covered)
|
|
13
|
+
/** Bottom panel that slides up from the bottom of the graph area.
|
|
14
|
+
* Sits in normal flow (canvas shrinks while open, nothing covered)
|
|
15
15
|
* and animates via height; stays mounted so open/close animates. */
|
|
16
16
|
export function FileDrawer({
|
|
17
|
-
|
|
17
|
+
title,
|
|
18
18
|
onClose,
|
|
19
19
|
children,
|
|
20
20
|
}: {
|
|
21
|
-
|
|
21
|
+
/** Drawer chrome title; `null` closes the drawer. */
|
|
22
|
+
title: string | null;
|
|
22
23
|
onClose: () => void;
|
|
23
24
|
children?: ReactNode;
|
|
24
25
|
}) {
|
|
25
26
|
const { theme } = useTheme();
|
|
26
27
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
27
|
-
const open =
|
|
28
|
+
const open = title !== null;
|
|
28
29
|
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
if (!open) return;
|
|
@@ -64,7 +65,7 @@ export function FileDrawer({
|
|
|
64
65
|
}}
|
|
65
66
|
>
|
|
66
67
|
<span
|
|
67
|
-
title={
|
|
68
|
+
title={title ?? undefined}
|
|
68
69
|
style={{
|
|
69
70
|
flex: 1,
|
|
70
71
|
minWidth: 0,
|
|
@@ -76,7 +77,7 @@ export function FileDrawer({
|
|
|
76
77
|
color: muted,
|
|
77
78
|
}}
|
|
78
79
|
>
|
|
79
|
-
{
|
|
80
|
+
{title}
|
|
80
81
|
</span>
|
|
81
82
|
<button
|
|
82
83
|
type="button"
|