@principal-ai/principal-view-react 0.16.56 → 0.16.57
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/graphify/consolidated.d.ts +16 -1
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/index.d.ts +1 -1
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/graphify/kind.d.ts +1 -1
- package/dist/graphify/kind.d.ts.map +1 -1
- package/dist/graphify/kind.js +7 -1
- package/dist/graphify/kind.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/constructColors.d.ts +24 -0
- package/dist/pierre/constructColors.d.ts.map +1 -0
- package/dist/pierre/constructColors.js +91 -0
- package/dist/pierre/constructColors.js.map +1 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +8 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +116 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -0
- package/dist/subsystem/ComponentDeclaration.d.ts +2 -2
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +12 -11
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +22 -1
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
- package/dist/subsystem/formatDeclaration.js +49 -10
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +55 -12
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +54 -24
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +4 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +70 -21
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/tokenizeComponent.js +1 -1
- package/dist/subsystem/tokenizeComponent.js.map +1 -1
- package/package.json +1 -1
- package/src/graphify/consolidated.ts +18 -1
- package/src/graphify/index.ts +1 -0
- package/src/graphify/kind.ts +7 -1
- package/src/index.ts +3 -2
- package/src/pierre/constructColors.test.ts +52 -0
- package/src/pierre/constructColors.ts +112 -0
- package/src/stories/{BoundaryNodes.stories.tsx → Audit/BoundaryNodes.stories.tsx} +1 -1
- package/src/stories/{CanvasEdgeTypes.stories.tsx → Audit/CanvasEdgeTypes.stories.tsx} +1 -1
- package/src/stories/{CanvasNodeTypes.stories.tsx → Audit/CanvasNodeTypes.stories.tsx} +1 -1
- package/src/stories/{ColorPriority.stories.tsx → Audit/ColorPriority.stories.tsx} +1 -1
- package/src/stories/{DiamondBadges.stories.tsx → Audit/DiamondBadges.stories.tsx} +1 -1
- package/src/stories/{NodeDimensionsTesting.stories.tsx → Audit/NodeDimensionsTesting.stories.tsx} +2 -2
- package/src/stories/{NodeFieldsAudit.stories.tsx → Audit/NodeFieldsAudit.stories.tsx} +1 -1
- package/src/stories/{NodeFontSizeTesting.stories.tsx → Audit/NodeFontSizeTesting.stories.tsx} +2 -2
- package/src/stories/{NodeShapes.stories.tsx → Audit/NodeShapes.stories.tsx} +1 -1
- package/src/stories/{WideNodesTesting.stories.tsx → Audit/WideNodesTesting.stories.tsx} +2 -2
- package/src/stories/{dashboard → Components}/DashboardRenderer.stories.tsx +2 -2
- package/src/stories/{GraphRenderer.stories.tsx → Components/GraphRenderer.stories.tsx} +2 -2
- package/src/stories/{MultiCanvasRenderer.stories.tsx → Components/MultiCanvasRenderer.stories.tsx} +2 -2
- package/src/stories/{SequenceDiagram.stories.tsx → Components/SequenceDiagram.stories.tsx} +2 -2
- package/src/stories/{StateView.stories.tsx → Components/StateView.stories.tsx} +2 -2
- package/src/stories/{TraceViewer.stories.tsx → Components/TraceViewer.stories.tsx} +3 -3
- package/src/stories/{WorkflowSequenceDiagram.stories.tsx → Components/WorkflowSequenceDiagram.stories.tsx} +1 -1
- package/src/stories/{AnimationWorkshop.stories.tsx → Features/AnimationWorkshop.stories.tsx} +5 -5
- package/src/stories/{ChangeTypeVisual.stories.tsx → Features/ChangeTypeVisual.stories.tsx} +2 -2
- package/src/stories/{EventDrivenAnimations.stories.tsx → Features/EventDrivenAnimations.stories.tsx} +2 -2
- package/src/stories/{MultiConfig.stories.tsx → Features/MultiConfig.stories.tsx} +2 -2
- package/src/stories/{ElkEdgeRouting.stories.tsx → Layout/ElkEdgeRouting.stories.tsx} +2 -2
- package/src/stories/{GraphOrientation.stories.tsx → Layout/GraphOrientation.stories.tsx} +3 -3
- package/src/stories/{MultiDirectionalConnections.stories.tsx → Layout/MultiDirectionalConnections.stories.tsx} +2 -2
- package/src/stories/{NodeDefinitionComparison.stories.tsx → Nodes/NodeDefinitionComparison.stories.tsx} +2 -2
- package/src/stories/{NodeNameFallbacks.stories.tsx → Nodes/NodeNameFallbacks.stories.tsx} +2 -2
- package/src/stories/{NumericColors.stories.tsx → Nodes/NumericColors.stories.tsx} +2 -2
- package/src/stories/{EventNameDisplay.stories.tsx → OTEL/EventNameDisplay.stories.tsx} +2 -2
- package/src/stories/{EventNameFallback.stories.tsx → OTEL/EventNameFallback.stories.tsx} +2 -2
- package/src/stories/{OtelComponents.stories.tsx → OTEL/OtelComponents.stories.tsx} +3 -3
- package/src/stories/{OtelNodeTypesPrototype.stories.tsx → OTEL/OtelNodeTypesPrototype.stories.tsx} +2 -2
- package/src/stories/{RawEventCards.stories.tsx → OTEL/RawEventCards.stories.tsx} +3 -3
- package/src/stories/{SpanBadges.stories.tsx → OTEL/SpanBadges.stories.tsx} +1 -1
- package/src/stories/{ValidationEventsCanvas.stories.tsx → OTEL/ValidationEventsCanvas.stories.tsx} +3 -3
- package/src/stories/{BacklogMdCanvases.stories.tsx → RealWorld/BacklogMdCanvases.stories.tsx} +2 -2
- package/src/stories/{FileCitySequence.stories.tsx → RealWorld/FileCitySequence.stories.tsx} +4 -4
- package/src/stories/{RealCanvasFiles.stories.tsx → RealWorld/RealCanvasFiles.stories.tsx} +7 -7
- package/src/stories/{ComponentDeclarationAudit.stories.tsx → Subsystem/ComponentDeclarationAudit.stories.tsx} +19 -20
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +365 -0
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +137 -0
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +238 -0
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +315 -0
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +959 -0
- package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +646 -0
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +129 -0
- package/src/subsystem/ComponentDeclaration.tsx +13 -13
- package/src/subsystem/SubsystemComponentGraph.tsx +25 -5
- package/src/subsystem/formatDeclaration.ts +57 -11
- package/src/subsystem/model.test.ts +38 -6
- package/src/subsystem/model.ts +108 -31
- package/src/subsystem/nodes.tsx +103 -27
- package/src/subsystem/tokenizeComponent.ts +1 -1
- package/src/stories/SubsystemComponentGraph.stories.tsx +0 -1225
- /package/src/stories/{dashboard/sample-dashboards → data}/activity-feed-analytics.dashboard.json +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SubsystemComponent,
|
|
3
|
+
SubsystemComponentEdge,
|
|
4
|
+
} from '../../../subsystem/model';
|
|
5
|
+
import type { GraphifyComponentDetail } from '../../../graphify';
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Build a subsystem graph from a compact spec - helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
export function components(
|
|
11
|
+
spec: Array<[id: string, name: string, construct: SubsystemComponent['construct'], file: string, purl: string, purpose?: string, symbol?: string, detail?: GraphifyComponentDetail]>,
|
|
12
|
+
): SubsystemComponent[] {
|
|
13
|
+
return spec.map(([id, name, construct, file, purl, purpose, symbol, detail]) => ({
|
|
14
|
+
id,
|
|
15
|
+
name,
|
|
16
|
+
construct,
|
|
17
|
+
file,
|
|
18
|
+
purl,
|
|
19
|
+
purpose,
|
|
20
|
+
symbol,
|
|
21
|
+
detail,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function edges(
|
|
26
|
+
spec: Array<[from: string, to: string, mechanism: SubsystemComponentEdge['mechanism'], refs?: string[]]>,
|
|
27
|
+
): SubsystemComponentEdge[] {
|
|
28
|
+
return spec.map(([from, to, mechanism, refs], i) => ({
|
|
29
|
+
id: `e${i}`,
|
|
30
|
+
from,
|
|
31
|
+
to,
|
|
32
|
+
mechanism,
|
|
33
|
+
refs,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const readerDetail: GraphifyComponentDetail = {
|
|
38
|
+
kind: 'class',
|
|
39
|
+
methods: [
|
|
40
|
+
{ nodeId: 'm1', name: 'normalize', returnType: 'SessionEvent[]' },
|
|
41
|
+
{ nodeId: 'm2', name: 'readSession', parameters: [{ type: 'string' }], returnType: 'SessionRecord' },
|
|
42
|
+
{ nodeId: 'm3', name: 'toUniversalEvents', returnType: 'UniversalEvent[]' },
|
|
43
|
+
],
|
|
44
|
+
properties: [{ name: 'sessionId', type: 'string' }],
|
|
45
|
+
extends: [],
|
|
46
|
+
implements: ['SessionReaderLike'],
|
|
47
|
+
instantiations: [{ nodeId: 'caller1', name: 'capture-session' }],
|
|
48
|
+
references: [{ nodeId: 'ref1', name: 'supported-agents', context: 'type' }],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Single package (no consumers) - the investigate-and-pin pattern.
|
|
53
|
+
// Conveys the idea via LAYERS: readers (input) → accumulator (processing).
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export const investigateOnlyComponents: SubsystemComponent[] = [
|
|
56
|
+
{
|
|
57
|
+
id: 'v1',
|
|
58
|
+
name: 'V1EventBridgeProcessor',
|
|
59
|
+
construct: 'class',
|
|
60
|
+
file: 'src/event-processing/V1EventBridge.ts',
|
|
61
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
62
|
+
purpose: 'normalizes V1 DB rows into universal events',
|
|
63
|
+
symbol: 'V1EventBridgeProcessor',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'v2',
|
|
67
|
+
name: 'V2EventBridgeProcessor',
|
|
68
|
+
construct: 'class',
|
|
69
|
+
file: 'src/event-processing/V2EventBridge.ts',
|
|
70
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
71
|
+
purpose: 'normalizes V2 durable events into universal events',
|
|
72
|
+
symbol: 'V2EventBridgeProcessor',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'input',
|
|
76
|
+
name: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
77
|
+
construct: 'interface',
|
|
78
|
+
file: 'types/RepoNormalizedUniversalAgentSessionEvent.ts',
|
|
79
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
80
|
+
purpose: 'the subsystem\u2019s input type — a repo-normalized universal event the readers produce and the accumulator consumes',
|
|
81
|
+
symbol: 'RepoNormalizedUniversalAgentSessionEvent',
|
|
82
|
+
detail: {
|
|
83
|
+
kind: 'type',
|
|
84
|
+
properties: [
|
|
85
|
+
{ name: 'eventType', type: 'NormalizedEventType' },
|
|
86
|
+
{ name: 'sessionId', type: 'string' },
|
|
87
|
+
{ name: 'timestamp', type: 'number' },
|
|
88
|
+
],
|
|
89
|
+
usedBy: [{ nodeId: 'acc', name: 'accumulateToAgentSessionEvents', context: 'parameter_type' }],
|
|
90
|
+
implementors: [],
|
|
91
|
+
} satisfies GraphifyComponentDetail,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'acc',
|
|
95
|
+
name: 'accumulateToAgentSessionEvents',
|
|
96
|
+
construct: 'function',
|
|
97
|
+
file: 'src/event-processing/accumulator.ts',
|
|
98
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
99
|
+
purpose: 'accumulates normalized events into agent session events',
|
|
100
|
+
symbol: 'accumulateToAgentSessionEvents',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'out',
|
|
104
|
+
name: 'AgentSessionEvent',
|
|
105
|
+
construct: 'interface',
|
|
106
|
+
file: 'src/event-processing/accumulator.ts',
|
|
107
|
+
purl: 'pkg:github/principal-ai/agent-monitoring',
|
|
108
|
+
purpose: 'the subsystem\u2019s output type — an accumulated agent-session event',
|
|
109
|
+
symbol: 'AgentSessionEvent',
|
|
110
|
+
detail: {
|
|
111
|
+
kind: 'type',
|
|
112
|
+
properties: [
|
|
113
|
+
{ name: 'sessionName', type: 'string' },
|
|
114
|
+
{ name: 'operation', type: 'AgentSessionEventOperation' },
|
|
115
|
+
{ name: 'description', type: 'string' },
|
|
116
|
+
],
|
|
117
|
+
usedBy: [{ nodeId: 'acc', name: 'accumulateToAgentSessionEvents', context: 'return_type' }],
|
|
118
|
+
implementors: [],
|
|
119
|
+
} satisfies GraphifyComponentDetail,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
export const investigateOnlyEdges: SubsystemComponentEdge[] = edges([
|
|
124
|
+
// Concept-level data flow — the LLM's semantic intent.
|
|
125
|
+
['v1', 'input', 'produces'],
|
|
126
|
+
['v2', 'input', 'produces'],
|
|
127
|
+
['input', 'acc', 'feeds'],
|
|
128
|
+
['acc', 'out', 'produces'],
|
|
129
|
+
]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ComponentDeclaration — the selected subsystem component rendered as it would
|
|
3
|
-
* appear in code: a file-path comment + doc comment, then the
|
|
3
|
+
* appear in code: a file-path comment + doc comment, then the construct's
|
|
4
4
|
* declaration (`class X extends Y { ... }`, `function f(): T`, ...) with its
|
|
5
5
|
* members as signatures. Graphify relationship drill-downs appear as trailing
|
|
6
6
|
* comments. File content lives in the bottom FileDrawer, not here.
|
|
@@ -11,7 +11,6 @@ import { AlignLeft, FileText, ShieldCheck } from 'lucide-react';
|
|
|
11
11
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
12
12
|
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
13
13
|
import {
|
|
14
|
-
KIND_COLOR,
|
|
15
14
|
formatPurl,
|
|
16
15
|
type SubsystemComponent,
|
|
17
16
|
type SubsystemDeclTokenKind,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
import type { SubsystemOpenFileOptions } from './declarationRef';
|
|
20
19
|
import { parseSourceLocation } from './declarationRef';
|
|
21
20
|
import { tokenizeComponent } from './tokenizeComponent';
|
|
21
|
+
import { constructColorsFromPierreTheme } from '../pierre/constructColors';
|
|
22
22
|
|
|
23
23
|
/** Live / result state for the declaration-panel Verify control. */
|
|
24
24
|
export type ComponentVerificationPhase =
|
|
@@ -49,7 +49,7 @@ export interface ComponentVerificationState {
|
|
|
49
49
|
source_location?: string;
|
|
50
50
|
candidates?: Array<{ nodeId: string; label: string; source_file?: string }>;
|
|
51
51
|
};
|
|
52
|
-
|
|
52
|
+
construct?: {
|
|
53
53
|
claimed: string;
|
|
54
54
|
inferred: string;
|
|
55
55
|
match: boolean;
|
|
@@ -163,11 +163,11 @@ function verificationSummary(
|
|
|
163
163
|
? ` · declaration ${declFresh}${v.declaration?.ref ? ` L${v.declaration.ref.startLine}` : ''}`
|
|
164
164
|
: '';
|
|
165
165
|
const anchorBit = `exact → ${v.anchor?.label ?? v.anchor?.nodeId ?? 'node'}${loc}${declBit}`;
|
|
166
|
-
if (v.
|
|
166
|
+
if (v.construct && !v.construct.match) {
|
|
167
167
|
const why =
|
|
168
|
-
v.
|
|
169
|
-
? `
|
|
170
|
-
: `
|
|
168
|
+
v.construct.inferred === 'unknown'
|
|
169
|
+
? `construct unknown (claimed ${v.construct.claimed})`
|
|
170
|
+
: `construct mismatch: claimed ${v.construct.claimed}, inferred ${v.construct.inferred}`;
|
|
171
171
|
return {
|
|
172
172
|
text: [...bits, anchorBit, why].join(' · '),
|
|
173
173
|
color: '#e5534b',
|
|
@@ -182,7 +182,7 @@ function verificationSummary(
|
|
|
182
182
|
text: [
|
|
183
183
|
...bits,
|
|
184
184
|
anchorBit,
|
|
185
|
-
v.
|
|
185
|
+
v.construct?.match ? `construct ${v.construct.inferred}` : undefined,
|
|
186
186
|
`signature mismatch: params [${cParams}]≠[${iParams}] return [${cRet}]≠[${iRet}]`,
|
|
187
187
|
]
|
|
188
188
|
.filter(Boolean)
|
|
@@ -190,8 +190,8 @@ function verificationSummary(
|
|
|
190
190
|
color: '#e5534b',
|
|
191
191
|
};
|
|
192
192
|
}
|
|
193
|
-
const
|
|
194
|
-
v.
|
|
193
|
+
const constructBit =
|
|
194
|
+
v.construct?.match ? `construct ${v.construct.inferred}` : undefined;
|
|
195
195
|
const inlineBit =
|
|
196
196
|
v.signature?.inlineParameters
|
|
197
197
|
? `${v.signature.inlineParameters} inline param${v.signature.inlineParameters === 1 ? '' : 's'} verified`
|
|
@@ -210,7 +210,7 @@ function verificationSummary(
|
|
|
210
210
|
text: [
|
|
211
211
|
...bits,
|
|
212
212
|
anchorBit,
|
|
213
|
-
...(
|
|
213
|
+
...(constructBit ? [constructBit] : []),
|
|
214
214
|
...(inlineBit ? [inlineBit] : []),
|
|
215
215
|
v.signature.skipCode
|
|
216
216
|
? `params/return not fully checked — ${skipPhrase[v.signature.skipCode] ?? v.signature.skipCode}`
|
|
@@ -222,7 +222,7 @@ function verificationSummary(
|
|
|
222
222
|
const sigBit =
|
|
223
223
|
v.signature && v.signature.match ? 'params/return ok' : undefined;
|
|
224
224
|
return {
|
|
225
|
-
text: [...bits, anchorBit, ...(
|
|
225
|
+
text: [...bits, anchorBit, ...(constructBit ? [constructBit] : []), ...(sigBit ? [sigBit] : [])].join(' · '),
|
|
226
226
|
color: ok,
|
|
227
227
|
};
|
|
228
228
|
}
|
|
@@ -263,7 +263,7 @@ export function ComponentDeclaration({
|
|
|
263
263
|
const [showFile, setShowFile] = useState(false);
|
|
264
264
|
const [showPurpose, setShowPurpose] = useState(false);
|
|
265
265
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
266
|
-
const color =
|
|
266
|
+
const color = constructColorsFromPierreTheme(pierreSyntaxTheme)[component.construct];
|
|
267
267
|
const okColor = '#3d9a5f';
|
|
268
268
|
const warnColor = theme.colors.textSecondary;
|
|
269
269
|
|
|
@@ -181,11 +181,19 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
181
181
|
measuredDimsRef.current = new Map();
|
|
182
182
|
prevMeasuredSigRef.current = '';
|
|
183
183
|
const doc = { components: componentsRef.current, edges: edgesRef.current };
|
|
184
|
-
void buildSubsystemGraph(doc, { maxNodeWidth, showEdgeLabels })
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
void buildSubsystemGraph(doc, { maxNodeWidth, showEdgeLabels })
|
|
185
|
+
.then(({ nodes, edges: e }) => {
|
|
186
|
+
if (!alive) return;
|
|
187
|
+
setBuilt({ nodes, edges: e as Edge[] });
|
|
188
|
+
setLayoutReady(false);
|
|
189
|
+
})
|
|
190
|
+
.catch((err) => {
|
|
191
|
+
console.warn('[subsystem-graph] initial layout failed:', err);
|
|
192
|
+
if (!alive) return;
|
|
193
|
+
// Reveal the cover even on failure so the UI is not stuck forever.
|
|
194
|
+
setBuilt({ nodes: [], edges: [] });
|
|
195
|
+
setLayoutReady(true);
|
|
196
|
+
});
|
|
189
197
|
return () => { alive = false; };
|
|
190
198
|
}, [layoutKey, maxNodeWidth, showEdgeLabels]);
|
|
191
199
|
|
|
@@ -426,6 +434,18 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
426
434
|
[onFileSelect],
|
|
427
435
|
);
|
|
428
436
|
|
|
437
|
+
// Filename-badge clicks on nodes open the drawer through the same path as
|
|
438
|
+
// the declaration panel's file link (toggle + tree sync, no start line).
|
|
439
|
+
useEffect(() => {
|
|
440
|
+
SUBSYSTEM_CALLBACKS.onOpenFile = (componentId: string) => {
|
|
441
|
+
const comp = components.find((c) => c.id === componentId);
|
|
442
|
+
if (comp?.file) onOpenDeclarationFile(comp.file);
|
|
443
|
+
};
|
|
444
|
+
return () => {
|
|
445
|
+
SUBSYSTEM_CALLBACKS.onOpenFile = undefined;
|
|
446
|
+
};
|
|
447
|
+
}, [components, onOpenDeclarationFile]);
|
|
448
|
+
|
|
429
449
|
// Detail-panel links: related-name clicks select the matching component —
|
|
430
450
|
// resolved by id, name, or symbol (call labels may carry a trailing `()`).
|
|
431
451
|
const resolveRelatedComponent = useCallback(
|
|
@@ -7,32 +7,46 @@
|
|
|
7
7
|
* handles all formatting.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { SubsystemComponent } from './model';
|
|
10
|
+
import type { SubsystemComponent, SubsystemComponentConstruct } from './model';
|
|
11
11
|
import type { GraphifyComponentDetail } from '../graphify';
|
|
12
12
|
|
|
13
|
+
const TYPE_FAMILY_CONSTRUCTS: ReadonlySet<string> = new Set([
|
|
14
|
+
'interface',
|
|
15
|
+
'type_alias',
|
|
16
|
+
'enum',
|
|
17
|
+
]);
|
|
18
|
+
|
|
13
19
|
export function generateDeclarationString(component: SubsystemComponent): string {
|
|
14
20
|
const detail = component.detail;
|
|
15
|
-
|
|
21
|
+
// Type-family constructs own their rendering even when the detail payload
|
|
22
|
+
// is the shared `type` shape — the construct says which keyword is honest.
|
|
23
|
+
const construct = TYPE_FAMILY_CONSTRUCTS.has(component.construct)
|
|
24
|
+
? component.construct
|
|
25
|
+
: detail?.kind ?? component.construct;
|
|
16
26
|
const rawName = component.symbol || component.name || 'untitled';
|
|
17
27
|
// Strip class/object prefix from dotted symbols (e.g. "SessionReader.normalize" → "normalize").
|
|
18
28
|
const name = rawName.includes('.') ? rawName.split('.').pop()! : rawName;
|
|
19
29
|
|
|
20
|
-
switch (
|
|
30
|
+
switch (construct) {
|
|
21
31
|
case 'class':
|
|
22
32
|
return generateClass(name, detail);
|
|
23
33
|
case 'function':
|
|
24
34
|
return generateFunction(name, detail);
|
|
25
35
|
case 'method':
|
|
26
36
|
return generateMethod(name, detail);
|
|
27
|
-
case '
|
|
28
|
-
|
|
37
|
+
case 'interface':
|
|
38
|
+
case 'type_alias':
|
|
39
|
+
case 'enum':
|
|
40
|
+
return generateType(name, component.construct, detail);
|
|
29
41
|
case 'module':
|
|
30
42
|
return generateModule(detail);
|
|
43
|
+
case 'store':
|
|
44
|
+
return generateStore(name, detail);
|
|
31
45
|
case 'external':
|
|
32
46
|
// Not valid TypeScript — caller should handle formatting.
|
|
33
47
|
return `external '${detail?.kind === 'external' ? detail.label : name}'`;
|
|
34
48
|
default:
|
|
35
|
-
return `${
|
|
49
|
+
return `${construct} ${name}`;
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
|
|
@@ -48,7 +62,7 @@ function formatParams(params: { name?: string; type: string }[]): string {
|
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
// ---------------------------------------------------------------------------
|
|
51
|
-
// Per-
|
|
65
|
+
// Per-construct generators
|
|
52
66
|
// ---------------------------------------------------------------------------
|
|
53
67
|
|
|
54
68
|
function generateClass(name: string, detail?: GraphifyComponentDetail): string {
|
|
@@ -98,16 +112,31 @@ function generateMethod(name: string, detail?: GraphifyComponentDetail): string
|
|
|
98
112
|
return `class ${hostClass} {\n ${name}(${params})${ret};\n}`;
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
function generateType(
|
|
115
|
+
function generateType(
|
|
116
|
+
name: string,
|
|
117
|
+
construct: SubsystemComponentConstruct,
|
|
118
|
+
detail?: GraphifyComponentDetail,
|
|
119
|
+
): string {
|
|
120
|
+
// The type-family constructs render their declaration keyword honestly —
|
|
121
|
+
// the construct itself says interface / type (alias) / enum / variable.
|
|
102
122
|
const tpe = detail?.kind === 'type' ? detail : undefined;
|
|
103
123
|
const props = (tpe?.properties ?? [])
|
|
104
124
|
.map((p) => ` ${p.name}${p.type ? `: ${p.type}` : ''};`)
|
|
105
125
|
.join('\n');
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
switch (construct) {
|
|
128
|
+
case 'enum':
|
|
129
|
+
return `enum ${name} { ${(tpe?.properties ?? []).map((p) => p.name).join(', ')} }`;
|
|
130
|
+
case 'type_alias':
|
|
131
|
+
return props
|
|
132
|
+
? `type ${name} = {\n${props}\n};`
|
|
133
|
+
: `type ${name} = unknown;`;
|
|
134
|
+
default:
|
|
135
|
+
if (props) {
|
|
136
|
+
return `interface ${name} {\n${props}\n}`;
|
|
137
|
+
}
|
|
138
|
+
return `interface ${name} {}`;
|
|
109
139
|
}
|
|
110
|
-
return `interface ${name} {}`;
|
|
111
140
|
}
|
|
112
141
|
|
|
113
142
|
function generateModule(detail?: GraphifyComponentDetail): string {
|
|
@@ -126,3 +155,20 @@ function generateModule(detail?: GraphifyComponentDetail): string {
|
|
|
126
155
|
|
|
127
156
|
return parts.join('\n') || `module {}`;
|
|
128
157
|
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A store renders as its retained state — ambient `declare const` lines for
|
|
161
|
+
* the state members, never a class/method stub. The node's name labels the
|
|
162
|
+
* block; the access mechanism lives in separate accessor nodes.
|
|
163
|
+
*/
|
|
164
|
+
function generateStore(name: string, detail?: GraphifyComponentDetail): string {
|
|
165
|
+
const store = detail?.kind === 'store' ? detail : undefined;
|
|
166
|
+
const props = (store?.properties ?? [])
|
|
167
|
+
.map((p) => `declare const ${p.name}${p.type ? `: ${p.type}` : ''};`)
|
|
168
|
+
.join('\n');
|
|
169
|
+
|
|
170
|
+
if (!props) {
|
|
171
|
+
return `// store: ${name} — no captured state members`;
|
|
172
|
+
}
|
|
173
|
+
return `// store: ${name}\n${props}`;
|
|
174
|
+
}
|
|
@@ -36,11 +36,30 @@ describe('subsystem graph model', () => {
|
|
|
36
36
|
expect(converted[0].target).toBe('reader');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
test('buildSubsystemGraph tolerates external components with no file', async () => {
|
|
40
|
+
const withExternal: SubsystemComponent[] = [
|
|
41
|
+
...comps,
|
|
42
|
+
{
|
|
43
|
+
id: "proposed-watcher",
|
|
44
|
+
name: "watchDir",
|
|
45
|
+
kind: "external",
|
|
46
|
+
// Intentionally omit file/purl — agents often leave these off for externals.
|
|
47
|
+
file: undefined as unknown as string,
|
|
48
|
+
purl: undefined as unknown as string,
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const { nodes } = await buildSubsystemGraph({
|
|
52
|
+
components: withExternal,
|
|
53
|
+
edges: [{ id: 'e3', from: 'reader', to: 'proposed-watcher', mechanism: 'feeds' }],
|
|
54
|
+
});
|
|
55
|
+
expect(nodes.find((n) => n.id === 'proposed-watcher')).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
39
58
|
test('buildSubsystemGraph creates external stub nodes for non-component targets', async () => {
|
|
40
59
|
const { nodes, edges: gEdges } = await buildSubsystemGraph({ components: comps, edges });
|
|
41
60
|
const external = nodes.find((n) => n.id === 'external:host');
|
|
42
61
|
expect(external).toBeDefined();
|
|
43
|
-
expect(external!.data.component.
|
|
62
|
+
expect(external!.data.component.construct).toBe('external');
|
|
44
63
|
const crossEdge = gEdges.find((e) => e.target === 'external:host');
|
|
45
64
|
expect(crossEdge).toBeDefined();
|
|
46
65
|
});
|
|
@@ -62,14 +81,27 @@ describe('subsystem graph model', () => {
|
|
|
62
81
|
});
|
|
63
82
|
|
|
64
83
|
test('deriveNameFromSymbol is consistent per kind', () => {
|
|
65
|
-
//
|
|
66
|
-
expect(deriveNameFromSymbol('SessionReader', 'class')).toBe('SessionReader');
|
|
67
|
-
expect(deriveNameFromSymbol('SessionRecord', '
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
// brace-bodied constructs wear {}; module uses the symbol as-is.
|
|
85
|
+
expect(deriveNameFromSymbol('SessionReader', 'class')).toBe('SessionReader {}');
|
|
86
|
+
expect(deriveNameFromSymbol('SessionRecord', 'type_alias')).toBe('SessionRecord {}');
|
|
87
|
+
expect(deriveNameFromSymbol('transcript', 'module')).toBe('transcript');
|
|
88
|
+
// falls back to existing name when no symbol (still brace-decorated).
|
|
89
|
+
expect(deriveNameFromSymbol(undefined, 'class', 'SessionReader')).toBe('SessionReader {}');
|
|
70
90
|
expect(deriveNameFromSymbol('', 'external', 'trail-viewer-host')).toBe('trail-viewer-host');
|
|
71
91
|
});
|
|
72
92
|
|
|
93
|
+
test('executable constructs wear () on the node', () => {
|
|
94
|
+
expect(deriveNameFromSymbol('createSubsystemGraph', 'function')).toBe('createSubsystemGraph()');
|
|
95
|
+
// methods keep the dotted ownership symbol and wear the parens
|
|
96
|
+
expect(deriveNameFromSymbol('SessionCache.put', 'method')).toBe('SessionCache.put()');
|
|
97
|
+
// already-parenthesized labels don't double up
|
|
98
|
+
expect(deriveNameFromSymbol('run()', 'function')).toBe('run()');
|
|
99
|
+
// data-shaped constructs stay bare
|
|
100
|
+
expect(deriveNameFromSymbol('ROOT', 'store')).toBe('ROOT');
|
|
101
|
+
// brace bodies don't double up
|
|
102
|
+
expect(deriveNameFromSymbol('Foo {}', 'class')).toBe('Foo {}');
|
|
103
|
+
});
|
|
104
|
+
|
|
73
105
|
test('deriveNameFromSymbol falls back to file basename for modules', () => {
|
|
74
106
|
expect(deriveNameFromSymbol(undefined, 'module', undefined, 'transcript.ts')).toBe('transcript');
|
|
75
107
|
expect(deriveNameFromSymbol(undefined, 'module', undefined, 'src/event-processors/index.ts')).toBe('index');
|