@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.
Files changed (125) hide show
  1. package/dist/graphify/consolidated.d.ts +16 -1
  2. package/dist/graphify/consolidated.d.ts.map +1 -1
  3. package/dist/graphify/index.d.ts +1 -1
  4. package/dist/graphify/index.d.ts.map +1 -1
  5. package/dist/graphify/index.js.map +1 -1
  6. package/dist/graphify/kind.d.ts +1 -1
  7. package/dist/graphify/kind.d.ts.map +1 -1
  8. package/dist/graphify/kind.js +7 -1
  9. package/dist/graphify/kind.js.map +1 -1
  10. package/dist/index.d.ts +5 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +3 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/pierre/constructColors.d.ts +24 -0
  15. package/dist/pierre/constructColors.d.ts.map +1 -0
  16. package/dist/pierre/constructColors.js +91 -0
  17. package/dist/pierre/constructColors.js.map +1 -0
  18. package/dist/pierre/index.d.ts +2 -0
  19. package/dist/pierre/index.d.ts.map +1 -1
  20. package/dist/pierre/index.js +1 -0
  21. package/dist/pierre/index.js.map +1 -1
  22. package/dist/pierre/pierreSyntaxTheme.d.ts +11 -0
  23. package/dist/pierre/pierreSyntaxTheme.d.ts.map +1 -0
  24. package/dist/pierre/pierreSyntaxTheme.js +20 -0
  25. package/dist/pierre/pierreSyntaxTheme.js.map +1 -0
  26. package/dist/pierre/syntaxTokens.d.ts +11 -0
  27. package/dist/pierre/syntaxTokens.d.ts.map +1 -0
  28. package/dist/pierre/syntaxTokens.js +51 -0
  29. package/dist/pierre/syntaxTokens.js.map +1 -0
  30. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +8 -0
  31. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -0
  32. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +116 -0
  33. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -0
  34. package/dist/subsystem/ComponentDeclaration.d.ts +2 -2
  35. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  36. package/dist/subsystem/ComponentDeclaration.js +47 -24
  37. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  38. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  39. package/dist/subsystem/SubsystemComponentGraph.js +23 -2
  40. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  41. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  42. package/dist/subsystem/formatDeclaration.js +49 -10
  43. package/dist/subsystem/formatDeclaration.js.map +1 -1
  44. package/dist/subsystem/model.d.ts +57 -12
  45. package/dist/subsystem/model.d.ts.map +1 -1
  46. package/dist/subsystem/model.js +54 -24
  47. package/dist/subsystem/model.js.map +1 -1
  48. package/dist/subsystem/nodes.d.ts +4 -1
  49. package/dist/subsystem/nodes.d.ts.map +1 -1
  50. package/dist/subsystem/nodes.js +70 -21
  51. package/dist/subsystem/nodes.js.map +1 -1
  52. package/dist/subsystem/tokenizeComponent.d.ts +4 -3
  53. package/dist/subsystem/tokenizeComponent.d.ts.map +1 -1
  54. package/dist/subsystem/tokenizeComponent.js +7 -12
  55. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  56. package/dist/subsystem/tokenizeFormatted.d.ts +3 -6
  57. package/dist/subsystem/tokenizeFormatted.d.ts.map +1 -1
  58. package/dist/subsystem/tokenizeFormatted.js +4 -75
  59. package/dist/subsystem/tokenizeFormatted.js.map +1 -1
  60. package/package.json +3 -1
  61. package/src/graphify/consolidated.ts +18 -1
  62. package/src/graphify/index.ts +1 -0
  63. package/src/graphify/kind.ts +7 -1
  64. package/src/index.ts +5 -3
  65. package/src/pierre/constructColors.test.ts +52 -0
  66. package/src/pierre/constructColors.ts +112 -0
  67. package/src/pierre/index.ts +5 -0
  68. package/src/pierre/pierreSyntaxTheme.ts +23 -0
  69. package/src/pierre/syntaxTokens.test.ts +35 -0
  70. package/src/pierre/syntaxTokens.ts +60 -0
  71. package/src/stories/{BoundaryNodes.stories.tsx → Audit/BoundaryNodes.stories.tsx} +1 -1
  72. package/src/stories/{CanvasEdgeTypes.stories.tsx → Audit/CanvasEdgeTypes.stories.tsx} +1 -1
  73. package/src/stories/{CanvasNodeTypes.stories.tsx → Audit/CanvasNodeTypes.stories.tsx} +1 -1
  74. package/src/stories/{ColorPriority.stories.tsx → Audit/ColorPriority.stories.tsx} +1 -1
  75. package/src/stories/{DiamondBadges.stories.tsx → Audit/DiamondBadges.stories.tsx} +1 -1
  76. package/src/stories/{NodeDimensionsTesting.stories.tsx → Audit/NodeDimensionsTesting.stories.tsx} +2 -2
  77. package/src/stories/{NodeFieldsAudit.stories.tsx → Audit/NodeFieldsAudit.stories.tsx} +1 -1
  78. package/src/stories/{NodeFontSizeTesting.stories.tsx → Audit/NodeFontSizeTesting.stories.tsx} +2 -2
  79. package/src/stories/{NodeShapes.stories.tsx → Audit/NodeShapes.stories.tsx} +1 -1
  80. package/src/stories/{WideNodesTesting.stories.tsx → Audit/WideNodesTesting.stories.tsx} +2 -2
  81. package/src/stories/{dashboard → Components}/DashboardRenderer.stories.tsx +2 -2
  82. package/src/stories/{GraphRenderer.stories.tsx → Components/GraphRenderer.stories.tsx} +2 -2
  83. package/src/stories/{MultiCanvasRenderer.stories.tsx → Components/MultiCanvasRenderer.stories.tsx} +2 -2
  84. package/src/stories/{SequenceDiagram.stories.tsx → Components/SequenceDiagram.stories.tsx} +2 -2
  85. package/src/stories/{StateView.stories.tsx → Components/StateView.stories.tsx} +2 -2
  86. package/src/stories/{TraceViewer.stories.tsx → Components/TraceViewer.stories.tsx} +3 -3
  87. package/src/stories/{WorkflowSequenceDiagram.stories.tsx → Components/WorkflowSequenceDiagram.stories.tsx} +1 -1
  88. package/src/stories/{AnimationWorkshop.stories.tsx → Features/AnimationWorkshop.stories.tsx} +5 -5
  89. package/src/stories/{ChangeTypeVisual.stories.tsx → Features/ChangeTypeVisual.stories.tsx} +2 -2
  90. package/src/stories/{EventDrivenAnimations.stories.tsx → Features/EventDrivenAnimations.stories.tsx} +2 -2
  91. package/src/stories/{MultiConfig.stories.tsx → Features/MultiConfig.stories.tsx} +2 -2
  92. package/src/stories/{ElkEdgeRouting.stories.tsx → Layout/ElkEdgeRouting.stories.tsx} +2 -2
  93. package/src/stories/{GraphOrientation.stories.tsx → Layout/GraphOrientation.stories.tsx} +3 -3
  94. package/src/stories/{MultiDirectionalConnections.stories.tsx → Layout/MultiDirectionalConnections.stories.tsx} +2 -2
  95. package/src/stories/{NodeDefinitionComparison.stories.tsx → Nodes/NodeDefinitionComparison.stories.tsx} +2 -2
  96. package/src/stories/{NodeNameFallbacks.stories.tsx → Nodes/NodeNameFallbacks.stories.tsx} +2 -2
  97. package/src/stories/{NumericColors.stories.tsx → Nodes/NumericColors.stories.tsx} +2 -2
  98. package/src/stories/{EventNameDisplay.stories.tsx → OTEL/EventNameDisplay.stories.tsx} +2 -2
  99. package/src/stories/{EventNameFallback.stories.tsx → OTEL/EventNameFallback.stories.tsx} +2 -2
  100. package/src/stories/{OtelComponents.stories.tsx → OTEL/OtelComponents.stories.tsx} +3 -3
  101. package/src/stories/{OtelNodeTypesPrototype.stories.tsx → OTEL/OtelNodeTypesPrototype.stories.tsx} +2 -2
  102. package/src/stories/{RawEventCards.stories.tsx → OTEL/RawEventCards.stories.tsx} +3 -3
  103. package/src/stories/{SpanBadges.stories.tsx → OTEL/SpanBadges.stories.tsx} +1 -1
  104. package/src/stories/{ValidationEventsCanvas.stories.tsx → OTEL/ValidationEventsCanvas.stories.tsx} +3 -3
  105. package/src/stories/{BacklogMdCanvases.stories.tsx → RealWorld/BacklogMdCanvases.stories.tsx} +2 -2
  106. package/src/stories/{FileCitySequence.stories.tsx → RealWorld/FileCitySequence.stories.tsx} +4 -4
  107. package/src/stories/{RealCanvasFiles.stories.tsx → RealWorld/RealCanvasFiles.stories.tsx} +7 -7
  108. package/src/stories/{ComponentDeclarationAudit.stories.tsx → Subsystem/ComponentDeclarationAudit.stories.tsx} +19 -20
  109. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +365 -0
  110. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +137 -0
  111. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +238 -0
  112. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +315 -0
  113. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +959 -0
  114. package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +646 -0
  115. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +129 -0
  116. package/src/subsystem/ComponentDeclaration.tsx +64 -26
  117. package/src/subsystem/SubsystemComponentGraph.tsx +26 -6
  118. package/src/subsystem/formatDeclaration.ts +57 -11
  119. package/src/subsystem/model.test.ts +38 -6
  120. package/src/subsystem/model.ts +110 -31
  121. package/src/subsystem/nodes.tsx +103 -27
  122. package/src/subsystem/tokenizeComponent.ts +12 -12
  123. package/src/subsystem/tokenizeFormatted.ts +9 -82
  124. package/src/stories/SubsystemComponentGraph.stories.tsx +0 -1225
  125. /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 kind's
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
- kind?: {
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.kind && !v.kind.match) {
166
+ if (v.construct && !v.construct.match) {
166
167
  const why =
167
- v.kind.inferred === 'unknown'
168
- ? `kind unknown (claimed ${v.kind.claimed})`
169
- : `kind mismatch: claimed ${v.kind.claimed}, inferred ${v.kind.inferred}`;
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.kind?.match ? `kind ${v.kind.inferred}` : undefined,
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 kindBit =
193
- v.kind?.match ? `kind ${v.kind.inferred}` : undefined;
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
- ...(kindBit ? [kindBit] : []),
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, ...(kindBit ? [kindBit] : []), ...(sigBit ? [sigBit] : [])].join(' · '),
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 = KIND_COLOR[component.kind] ?? '#888';
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 = (children: ReactNode, key?: string, indent?: boolean | number) => {
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 key={key} style={{ display: 'flex', alignItems: 'center', whiteSpace: 'pre', minHeight: 18, ...(paddingLeft ? { paddingLeft } : {}) }}>
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
- maxWidth: '52ch',
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 key={`${di}-${declLines[di].length}`} style={{ color: tokenColor[tok.kind] }}>
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 key={`decl-${li}`} style={{ display: 'flex', alignItems: 'center', whiteSpace: 'pre', minHeight: 18 }}>
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={{ ...purposeStyle, display: 'block' }} key={`purpose${i}`}>
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
- ...(maxWidth != null
597
- ? { maxWidth, wordBreak: 'break-word' }
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 }).then(({ nodes, edges: e }) => {
185
- if (!alive) return;
186
- setBuilt({ nodes, edges: e as Edge[] });
187
- setLayoutReady(false);
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
- maxWidth: 'min(560px, 70%)',
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
- const kind = detail?.kind ?? component.kind;
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 (kind) {
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 'type':
28
- return generateType(name, detail);
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 `${kind} ${name}`;
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-kind generators
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(name: string, detail?: GraphifyComponentDetail): string {
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
- if (props) {
108
- return `interface ${name} {\n${props}\n}`;
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.kind).toBe('external');
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
- // class/type/module/function use the symbol as-is.
66
- expect(deriveNameFromSymbol('SessionReader', 'class')).toBe('SessionReader');
67
- expect(deriveNameFromSymbol('SessionRecord', 'type')).toBe('SessionRecord');
68
- // falls back to existing name when no symbol.
69
- expect(deriveNameFromSymbol(undefined, 'class', 'SessionReader')).toBe('SessionReader');
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');