@principal-ai/principal-view-react 0.16.55 → 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 +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- 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/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/pierre/pierreSyntaxTheme.d.ts +11 -0
- package/dist/pierre/pierreSyntaxTheme.d.ts.map +1 -0
- package/dist/pierre/pierreSyntaxTheme.js +20 -0
- package/dist/pierre/pierreSyntaxTheme.js.map +1 -0
- package/dist/pierre/syntaxTokens.d.ts +11 -0
- package/dist/pierre/syntaxTokens.d.ts.map +1 -0
- package/dist/pierre/syntaxTokens.js +51 -0
- package/dist/pierre/syntaxTokens.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 +47 -24
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +23 -2
- 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 +57 -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.d.ts +4 -3
- package/dist/subsystem/tokenizeComponent.d.ts.map +1 -1
- package/dist/subsystem/tokenizeComponent.js +7 -12
- package/dist/subsystem/tokenizeComponent.js.map +1 -1
- package/dist/subsystem/tokenizeFormatted.d.ts +3 -6
- package/dist/subsystem/tokenizeFormatted.d.ts.map +1 -1
- package/dist/subsystem/tokenizeFormatted.js +4 -75
- package/dist/subsystem/tokenizeFormatted.js.map +1 -1
- package/package.json +3 -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 +5 -3
- package/src/pierre/constructColors.test.ts +52 -0
- package/src/pierre/constructColors.ts +112 -0
- package/src/pierre/index.ts +5 -0
- package/src/pierre/pierreSyntaxTheme.ts +23 -0
- package/src/pierre/syntaxTokens.test.ts +35 -0
- package/src/pierre/syntaxTokens.ts +60 -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 +64 -26
- package/src/subsystem/SubsystemComponentGraph.tsx +26 -6
- package/src/subsystem/formatDeclaration.ts +57 -11
- package/src/subsystem/model.test.ts +38 -6
- package/src/subsystem/model.ts +110 -31
- package/src/subsystem/nodes.tsx +103 -27
- package/src/subsystem/tokenizeComponent.ts +12 -12
- package/src/subsystem/tokenizeFormatted.ts +9 -82
- 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.
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
import { useEffect, useRef, useState, type ReactNode } from 'react';
|
|
10
10
|
import { AlignLeft, FileText, ShieldCheck } from 'lucide-react';
|
|
11
11
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
12
|
+
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
12
13
|
import {
|
|
13
|
-
KIND_COLOR,
|
|
14
14
|
formatPurl,
|
|
15
15
|
type SubsystemComponent,
|
|
16
16
|
type SubsystemDeclTokenKind,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import type { SubsystemOpenFileOptions } from './declarationRef';
|
|
19
19
|
import { parseSourceLocation } from './declarationRef';
|
|
20
20
|
import { tokenizeComponent } from './tokenizeComponent';
|
|
21
|
+
import { constructColorsFromPierreTheme } from '../pierre/constructColors';
|
|
21
22
|
|
|
22
23
|
/** Live / result state for the declaration-panel Verify control. */
|
|
23
24
|
export type ComponentVerificationPhase =
|
|
@@ -48,7 +49,7 @@ export interface ComponentVerificationState {
|
|
|
48
49
|
source_location?: string;
|
|
49
50
|
candidates?: Array<{ nodeId: string; label: string; source_file?: string }>;
|
|
50
51
|
};
|
|
51
|
-
|
|
52
|
+
construct?: {
|
|
52
53
|
claimed: string;
|
|
53
54
|
inferred: string;
|
|
54
55
|
match: boolean;
|
|
@@ -162,11 +163,11 @@ function verificationSummary(
|
|
|
162
163
|
? ` · declaration ${declFresh}${v.declaration?.ref ? ` L${v.declaration.ref.startLine}` : ''}`
|
|
163
164
|
: '';
|
|
164
165
|
const anchorBit = `exact → ${v.anchor?.label ?? v.anchor?.nodeId ?? 'node'}${loc}${declBit}`;
|
|
165
|
-
if (v.
|
|
166
|
+
if (v.construct && !v.construct.match) {
|
|
166
167
|
const why =
|
|
167
|
-
v.
|
|
168
|
-
? `
|
|
169
|
-
: `
|
|
168
|
+
v.construct.inferred === 'unknown'
|
|
169
|
+
? `construct unknown (claimed ${v.construct.claimed})`
|
|
170
|
+
: `construct mismatch: claimed ${v.construct.claimed}, inferred ${v.construct.inferred}`;
|
|
170
171
|
return {
|
|
171
172
|
text: [...bits, anchorBit, why].join(' · '),
|
|
172
173
|
color: '#e5534b',
|
|
@@ -181,7 +182,7 @@ function verificationSummary(
|
|
|
181
182
|
text: [
|
|
182
183
|
...bits,
|
|
183
184
|
anchorBit,
|
|
184
|
-
v.
|
|
185
|
+
v.construct?.match ? `construct ${v.construct.inferred}` : undefined,
|
|
185
186
|
`signature mismatch: params [${cParams}]≠[${iParams}] return [${cRet}]≠[${iRet}]`,
|
|
186
187
|
]
|
|
187
188
|
.filter(Boolean)
|
|
@@ -189,8 +190,8 @@ function verificationSummary(
|
|
|
189
190
|
color: '#e5534b',
|
|
190
191
|
};
|
|
191
192
|
}
|
|
192
|
-
const
|
|
193
|
-
v.
|
|
193
|
+
const constructBit =
|
|
194
|
+
v.construct?.match ? `construct ${v.construct.inferred}` : undefined;
|
|
194
195
|
const inlineBit =
|
|
195
196
|
v.signature?.inlineParameters
|
|
196
197
|
? `${v.signature.inlineParameters} inline param${v.signature.inlineParameters === 1 ? '' : 's'} verified`
|
|
@@ -209,7 +210,7 @@ function verificationSummary(
|
|
|
209
210
|
text: [
|
|
210
211
|
...bits,
|
|
211
212
|
anchorBit,
|
|
212
|
-
...(
|
|
213
|
+
...(constructBit ? [constructBit] : []),
|
|
213
214
|
...(inlineBit ? [inlineBit] : []),
|
|
214
215
|
v.signature.skipCode
|
|
215
216
|
? `params/return not fully checked — ${skipPhrase[v.signature.skipCode] ?? v.signature.skipCode}`
|
|
@@ -221,7 +222,7 @@ function verificationSummary(
|
|
|
221
222
|
const sigBit =
|
|
222
223
|
v.signature && v.signature.match ? 'params/return ok' : undefined;
|
|
223
224
|
return {
|
|
224
|
-
text: [...bits, anchorBit, ...(
|
|
225
|
+
text: [...bits, anchorBit, ...(constructBit ? [constructBit] : []), ...(sigBit ? [sigBit] : [])].join(' · '),
|
|
225
226
|
color: ok,
|
|
226
227
|
};
|
|
227
228
|
}
|
|
@@ -256,12 +257,13 @@ export function ComponentDeclaration({
|
|
|
256
257
|
onVerify,
|
|
257
258
|
verification,
|
|
258
259
|
}: ComponentDeclarationProps) {
|
|
259
|
-
const { theme } = useTheme();
|
|
260
|
+
const { theme, mode } = useTheme();
|
|
261
|
+
const pierreSyntaxTheme = resolvePierreSyntaxThemeName(mode);
|
|
260
262
|
const [fileHovered, setFileHovered] = useState(false);
|
|
261
263
|
const [showFile, setShowFile] = useState(false);
|
|
262
264
|
const [showPurpose, setShowPurpose] = useState(false);
|
|
263
265
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
264
|
-
const color =
|
|
266
|
+
const color = constructColorsFromPierreTheme(pierreSyntaxTheme)[component.construct];
|
|
265
267
|
const okColor = '#3d9a5f';
|
|
266
268
|
const warnColor = theme.colors.textSecondary;
|
|
267
269
|
|
|
@@ -274,6 +276,8 @@ export function ComponentDeclaration({
|
|
|
274
276
|
string: theme.colors.success,
|
|
275
277
|
newline: '',
|
|
276
278
|
};
|
|
279
|
+
/** Fallback when wire tokens omit Shiki colors. */
|
|
280
|
+
const declarationTextColor = muted;
|
|
277
281
|
|
|
278
282
|
const declarationStartLine =
|
|
279
283
|
component.declarationRef?.startLine ??
|
|
@@ -305,10 +309,26 @@ export function ComponentDeclaration({
|
|
|
305
309
|
</DetailLink>
|
|
306
310
|
) : null;
|
|
307
311
|
|
|
308
|
-
const line = (
|
|
312
|
+
const line = (
|
|
313
|
+
children: ReactNode,
|
|
314
|
+
key?: string,
|
|
315
|
+
indent?: boolean | number,
|
|
316
|
+
wrap: 'pre' | 'pre-wrap' | 'normal' = 'pre-wrap',
|
|
317
|
+
) => {
|
|
309
318
|
const paddingLeft = typeof indent === 'number' ? indent : indent ? 16 : 0;
|
|
310
319
|
return (
|
|
311
|
-
<div
|
|
320
|
+
<div
|
|
321
|
+
key={key}
|
|
322
|
+
style={{
|
|
323
|
+
display: 'flex',
|
|
324
|
+
alignItems: wrap === 'normal' ? 'flex-start' : 'center',
|
|
325
|
+
whiteSpace: wrap,
|
|
326
|
+
overflowWrap: 'anywhere',
|
|
327
|
+
minWidth: 0,
|
|
328
|
+
minHeight: 18,
|
|
329
|
+
...(paddingLeft ? { paddingLeft } : {}),
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
312
332
|
{children}
|
|
313
333
|
</div>
|
|
314
334
|
);
|
|
@@ -327,7 +347,8 @@ export function ComponentDeclaration({
|
|
|
327
347
|
fontFamily: theme.fonts.body,
|
|
328
348
|
fontSize: theme.fontSizes[1],
|
|
329
349
|
lineHeight: 1.5,
|
|
330
|
-
|
|
350
|
+
minWidth: 0,
|
|
351
|
+
flex: 1,
|
|
331
352
|
} as const;
|
|
332
353
|
|
|
333
354
|
const lines: ReactNode[] = [];
|
|
@@ -540,13 +561,13 @@ export function ComponentDeclaration({
|
|
|
540
561
|
return;
|
|
541
562
|
}
|
|
542
563
|
let cancelled = false;
|
|
543
|
-
tokenizeComponent(component, printWidth).then((t) => {
|
|
564
|
+
tokenizeComponent(component, printWidth, pierreSyntaxTheme).then((t) => {
|
|
544
565
|
if (!cancelled) setTokens(t);
|
|
545
566
|
});
|
|
546
567
|
return () => {
|
|
547
568
|
cancelled = true;
|
|
548
569
|
};
|
|
549
|
-
}, [component, printWidth]);
|
|
570
|
+
}, [component, printWidth, pierreSyntaxTheme]);
|
|
550
571
|
const declLines: ReactNode[][] = [[]];
|
|
551
572
|
let di = 0;
|
|
552
573
|
for (const tok of tokens) {
|
|
@@ -556,7 +577,10 @@ export function ComponentDeclaration({
|
|
|
556
577
|
continue;
|
|
557
578
|
}
|
|
558
579
|
declLines[di].push(
|
|
559
|
-
<span
|
|
580
|
+
<span
|
|
581
|
+
key={`${di}-${declLines[di].length}`}
|
|
582
|
+
style={{ color: tok.color ?? tokenColor[tok.kind] ?? declarationTextColor }}
|
|
583
|
+
>
|
|
560
584
|
{tok.text}
|
|
561
585
|
</span>,
|
|
562
586
|
);
|
|
@@ -564,21 +588,33 @@ export function ComponentDeclaration({
|
|
|
564
588
|
for (let li = 0; li < declLines.length; li++) {
|
|
565
589
|
if (declLines[li].length === 0) continue;
|
|
566
590
|
lines.push(
|
|
567
|
-
<div
|
|
591
|
+
<div
|
|
592
|
+
key={`decl-${li}`}
|
|
593
|
+
style={{
|
|
594
|
+
display: 'flex',
|
|
595
|
+
alignItems: 'center',
|
|
596
|
+
whiteSpace: 'pre-wrap',
|
|
597
|
+
overflowWrap: 'anywhere',
|
|
598
|
+
minWidth: 0,
|
|
599
|
+
minHeight: 18,
|
|
600
|
+
}}
|
|
601
|
+
>
|
|
568
602
|
{declLines[li]}
|
|
569
603
|
</div>,
|
|
570
604
|
);
|
|
571
605
|
}
|
|
572
606
|
|
|
573
607
|
if (showPurpose && component.purpose) {
|
|
574
|
-
lines.push(line(<div style={{ height: 6 }} />, 'purpose-gap'));
|
|
608
|
+
lines.push(line(<div style={{ height: 6 }} />, 'purpose-gap', false, 'normal'));
|
|
575
609
|
component.purpose.split('\n').forEach((purposeLine, i) =>
|
|
576
610
|
lines.push(
|
|
577
611
|
line(
|
|
578
|
-
<span style={
|
|
612
|
+
<span style={purposeStyle} key={`purpose${i}`}>
|
|
579
613
|
{purposeLine.trim()}
|
|
580
614
|
</span>,
|
|
581
615
|
`purpose${i}`,
|
|
616
|
+
false,
|
|
617
|
+
'normal',
|
|
582
618
|
),
|
|
583
619
|
),
|
|
584
620
|
);
|
|
@@ -588,14 +624,16 @@ export function ComponentDeclaration({
|
|
|
588
624
|
<div
|
|
589
625
|
ref={containerRef}
|
|
590
626
|
style={{
|
|
627
|
+
width: '100%',
|
|
628
|
+
minWidth: 0,
|
|
629
|
+
boxSizing: 'border-box',
|
|
591
630
|
background: theme.colors.backgroundSecondary,
|
|
592
631
|
padding: '10px 12px',
|
|
593
632
|
fontFamily: theme.fonts.monospace,
|
|
594
633
|
fontSize: theme.fontSizes[1],
|
|
595
634
|
lineHeight: 1.7,
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
: { overflowX: 'auto' }),
|
|
635
|
+
overflowWrap: 'anywhere',
|
|
636
|
+
...(maxWidth != null ? { maxWidth } : {}),
|
|
599
637
|
}}
|
|
600
638
|
>
|
|
601
639
|
{lines}
|
|
@@ -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(
|
|
@@ -734,7 +754,7 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
|
|
|
734
754
|
top: 10,
|
|
735
755
|
right: 10,
|
|
736
756
|
zIndex: 8,
|
|
737
|
-
|
|
757
|
+
width: 'min(560px, 70%)',
|
|
738
758
|
maxHeight: '46%',
|
|
739
759
|
overflowY: 'auto',
|
|
740
760
|
background: theme.colors.background,
|
|
@@ -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');
|