@principal-ai/subsystems-react 0.21.1 → 0.23.0

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 (92) hide show
  1. package/dist/graphify/consolidated.d.ts +5 -24
  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/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/pierre/PierreFileView.d.ts +3 -1
  10. package/dist/pierre/PierreFileView.d.ts.map +1 -1
  11. package/dist/pierre/PierreFileView.js +13 -3
  12. package/dist/pierre/PierreFileView.js.map +1 -1
  13. package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
  14. package/dist/pierre/PierreSnippetView.js +5 -26
  15. package/dist/pierre/PierreSnippetView.js.map +1 -1
  16. package/dist/pierre/PierreWalkthroughCodeView.d.ts +3 -1
  17. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -1
  18. package/dist/pierre/PierreWalkthroughCodeView.js +65 -8
  19. package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -1
  20. package/dist/pierre/constructColors.d.ts.map +1 -1
  21. package/dist/pierre/constructColors.js +0 -5
  22. package/dist/pierre/constructColors.js.map +1 -1
  23. package/dist/pierre/index.d.ts +1 -1
  24. package/dist/pierre/index.d.ts.map +1 -1
  25. package/dist/pierre/index.js +1 -1
  26. package/dist/pierre/index.js.map +1 -1
  27. package/dist/pierre/scrollAnchor.d.ts +2 -0
  28. package/dist/pierre/scrollAnchor.d.ts.map +1 -1
  29. package/dist/pierre/scrollAnchor.js +7 -0
  30. package/dist/pierre/scrollAnchor.js.map +1 -1
  31. package/dist/pierre/sliceSnippet.d.ts +6 -0
  32. package/dist/pierre/sliceSnippet.d.ts.map +1 -1
  33. package/dist/pierre/sliceSnippet.js +25 -0
  34. package/dist/pierre/sliceSnippet.js.map +1 -1
  35. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  36. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +0 -4
  37. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  38. package/dist/subsystem/FileDrawer.d.ts +7 -2
  39. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  40. package/dist/subsystem/FileDrawer.js +13 -9
  41. package/dist/subsystem/FileDrawer.js.map +1 -1
  42. package/dist/subsystem/SubsystemComponentGraph.d.ts +2 -0
  43. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  44. package/dist/subsystem/SubsystemComponentGraph.js +97 -8
  45. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  46. package/dist/subsystem/declarationRef.d.ts +6 -1
  47. package/dist/subsystem/declarationRef.d.ts.map +1 -1
  48. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  49. package/dist/subsystem/formatDeclaration.js +0 -15
  50. package/dist/subsystem/formatDeclaration.js.map +1 -1
  51. package/dist/subsystem/model.d.ts +61 -22
  52. package/dist/subsystem/model.d.ts.map +1 -1
  53. package/dist/subsystem/model.js +185 -65
  54. package/dist/subsystem/model.js.map +1 -1
  55. package/dist/subsystem/nodes.d.ts +3 -4
  56. package/dist/subsystem/nodes.d.ts.map +1 -1
  57. package/dist/subsystem/nodes.js +5 -6
  58. package/dist/subsystem/nodes.js.map +1 -1
  59. package/dist/utils/elkLayout.d.ts +6 -4
  60. package/dist/utils/elkLayout.d.ts.map +1 -1
  61. package/dist/utils/elkLayout.js +144 -54
  62. package/dist/utils/elkLayout.js.map +1 -1
  63. package/package.json +3 -3
  64. package/src/graphify/consolidated.ts +4 -26
  65. package/src/graphify/index.ts +0 -2
  66. package/src/index.ts +0 -2
  67. package/src/pierre/PierreFileView.tsx +25 -2
  68. package/src/pierre/PierreSnippetView.tsx +5 -31
  69. package/src/pierre/PierreWalkthroughCodeView.tsx +110 -5
  70. package/src/pierre/constructColors.test.ts +1 -2
  71. package/src/pierre/constructColors.ts +0 -5
  72. package/src/pierre/index.ts +1 -1
  73. package/src/pierre/scrollAnchor.ts +13 -0
  74. package/src/pierre/sliceSnippet.test.ts +44 -1
  75. package/src/pierre/sliceSnippet.ts +28 -0
  76. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +0 -31
  77. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -46
  78. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +9 -24
  79. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +2 -2
  80. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +2 -1
  81. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +287 -0
  82. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +1 -1
  83. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +6 -6
  84. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +0 -4
  85. package/src/subsystem/FileDrawer.tsx +19 -7
  86. package/src/subsystem/SubsystemComponentGraph.tsx +153 -7
  87. package/src/subsystem/declarationRef.ts +6 -1
  88. package/src/subsystem/formatDeclaration.ts +0 -19
  89. package/src/subsystem/model.test.ts +190 -9
  90. package/src/subsystem/model.ts +219 -77
  91. package/src/subsystem/nodes.tsx +6 -6
  92. package/src/utils/elkLayout.ts +148 -58
@@ -7,7 +7,14 @@
7
7
  * `PierreSnippetView`).
8
8
  */
9
9
 
10
- import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
10
+ import {
11
+ useCallback,
12
+ useEffect,
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ type ReactElement,
17
+ } from 'react';
11
18
  import {
12
19
  CodeView,
13
20
  type CodeViewHandle,
@@ -16,6 +23,7 @@ import {
16
23
  type DiffLineAnnotation,
17
24
  type LineAnnotation,
18
25
  } from '@pierre/diffs/react';
26
+ import { Maximize2 } from 'lucide-react';
19
27
 
20
28
  /** Mirrors Pierre's CodeViewLineSelection (not re-exported from the React entry). */
21
29
  type CodeViewLineSelection = {
@@ -32,7 +40,11 @@ import {
32
40
  pierreLangForPath,
33
41
  } from './pierreFileLang';
34
42
  import { resolvePierreSyntaxThemeName } from './pierreSyntaxTheme';
35
- import { sliceSnippetWindow, type SnippetSlice } from './sliceSnippet';
43
+ import {
44
+ remapSnippetLineNumbers,
45
+ sliceSnippetWindow,
46
+ type SnippetSlice,
47
+ } from './sliceSnippet';
36
48
 
37
49
  export interface PierreWalkthroughCodeViewProps {
38
50
  walkthrough: SubsystemWalkthrough;
@@ -43,6 +55,8 @@ export interface PierreWalkthroughCodeViewProps {
43
55
  contextLines?: number;
44
56
  /** Override Pierre's container background. */
45
57
  background?: string;
58
+ /** Open the step's full source file (header button or double-click snippet body). */
59
+ onOpenFile?: (path: string) => void;
46
60
  }
47
61
 
48
62
  type FileLoadState =
@@ -54,12 +68,54 @@ function stepItemId(walkthroughId: string, index: number): string {
54
68
  return `${walkthroughId}:${index}`;
55
69
  }
56
70
 
71
+ function OpenFileHeaderButton({
72
+ file,
73
+ onOpenFile,
74
+ }: {
75
+ file: string;
76
+ onOpenFile: (path: string) => void;
77
+ }) {
78
+ const { theme } = useTheme();
79
+ const [hover, setHover] = useState(false);
80
+ return (
81
+ <button
82
+ type="button"
83
+ onClick={(e) => {
84
+ e.stopPropagation();
85
+ onOpenFile(file);
86
+ }}
87
+ onMouseEnter={() => setHover(true)}
88
+ onMouseLeave={() => setHover(false)}
89
+ title={`Open ${file}`}
90
+ aria-label={`Open full file ${file}`}
91
+ style={{
92
+ display: 'inline-flex',
93
+ alignItems: 'center',
94
+ gap: 4,
95
+ padding: '1px 6px',
96
+ border: `1px solid ${theme.colors.border}`,
97
+ borderRadius: 4,
98
+ background: hover ? theme.colors.border : 'transparent',
99
+ color: hover ? theme.colors.text : theme.colors.textSecondary,
100
+ cursor: 'pointer',
101
+ fontFamily: theme.fonts.monospace,
102
+ fontSize: theme.fontSizes[0],
103
+ transition: 'background 120ms ease, color 120ms ease',
104
+ }}
105
+ >
106
+ <Maximize2 size={11} />
107
+ Open file
108
+ </button>
109
+ );
110
+ }
111
+
57
112
  export function PierreWalkthroughCodeView({
58
113
  walkthrough,
59
114
  stepIndex,
60
115
  readFile,
61
116
  contextLines = 8,
62
117
  background,
118
+ onOpenFile,
63
119
  }: PierreWalkthroughCodeViewProps) {
64
120
  const { theme, mode } = useTheme();
65
121
  const viewRef = useRef<CodeViewHandle<undefined>>(null);
@@ -154,6 +210,30 @@ const items = useMemo((): CodeViewItem<WalkthroughStepAnnotation>[] => {
154
210
  };
155
211
  }, [stepIndex, slices, walkthrough.id]);
156
212
 
213
+ const sliceStartByItemId = useMemo(() => {
214
+ const map = new Map<string, number>();
215
+ for (let i = 0; i < slices.length; i++) {
216
+ map.set(stepItemId(walkthrough.id, i), slices[i]!.sliceStart);
217
+ }
218
+ return map;
219
+ }, [slices, walkthrough.id]);
220
+
221
+ const onPostRender = useCallback(
222
+ (
223
+ node: HTMLElement,
224
+ _instance: unknown,
225
+ _phase: unknown,
226
+ context?: { item?: { id?: string } },
227
+ ) => {
228
+ const id = context?.item?.id;
229
+ if (id == null) return;
230
+ const sliceStart = sliceStartByItemId.get(id);
231
+ if (sliceStart == null) return;
232
+ remapSnippetLineNumbers(node, sliceStart);
233
+ },
234
+ [sliceStartByItemId],
235
+ );
236
+
157
237
  const renderHeaderPrefix = useMemo(() => {
158
238
  return (item: CodeViewItem) => {
159
239
  const index = Number.parseInt(item.id.split(':').pop() ?? '', 10);
@@ -186,17 +266,23 @@ const items = useMemo((): CodeViewItem<WalkthroughStepAnnotation>[] => {
186
266
  return (
187
267
  <span
188
268
  style={{
269
+ display: 'inline-flex',
270
+ alignItems: 'center',
271
+ gap: 6,
272
+ marginLeft: 8,
189
273
  fontFamily: theme.fonts.monospace,
190
274
  fontSize: theme.fontSizes[0],
191
275
  color: theme.colors.textSecondary,
192
- marginLeft: 8,
193
276
  }}
194
277
  >
195
278
  L{step.line}
279
+ {onOpenFile && (
280
+ <OpenFileHeaderButton file={step.file} onOpenFile={onOpenFile} />
281
+ )}
196
282
  </span>
197
283
  );
198
284
  };
199
- }, [walkthrough.steps, theme]);
285
+ }, [walkthrough.steps, theme, onOpenFile]);
200
286
 
201
287
  const renderAnnotation = useMemo(() => {
202
288
  return (
@@ -226,6 +312,23 @@ const items = useMemo((): CodeViewItem<WalkthroughStepAnnotation>[] => {
226
312
  };
227
313
  }, [walkthrough.steps, theme]);
228
314
 
315
+ const onLineClick = useCallback(
316
+ (
317
+ props: { event?: { detail?: number } },
318
+ context?: { item?: { id?: string } },
319
+ ) => {
320
+ if (!onOpenFile) return;
321
+ if (props.event?.detail !== 2) return;
322
+ const id = context?.item?.id;
323
+ if (id == null) return;
324
+ const index = Number.parseInt(id.split(':').pop() ?? '', 10);
325
+ const step = walkthrough.steps[index];
326
+ if (!step) return;
327
+ onOpenFile(step.file);
328
+ },
329
+ [onOpenFile, walkthrough.steps],
330
+ );
331
+
229
332
  const options = useMemo((): CodeViewReactOptions => {
230
333
  return {
231
334
  theme: {
@@ -235,10 +338,12 @@ const items = useMemo((): CodeViewItem<WalkthroughStepAnnotation>[] => {
235
338
  stickyHeaders: true,
236
339
  disableFileHeader: false,
237
340
  layout: { paddingTop: 8, paddingBottom: 16, gap: 12 },
341
+ onPostRender,
342
+ ...(onOpenFile ? { onLineClick } : {}),
238
343
  ...(background ? buildPierreOptions(background) : {}),
239
344
  ...(mode === 'light' || mode === 'dark' ? { themeType: mode } : {}),
240
345
  };
241
- }, [background, mode]);
346
+ }, [background, mode, onPostRender, onOpenFile, onLineClick]);
242
347
 
243
348
  useEffect(() => {
244
349
  if (load.status !== 'ready' || stepIndex == null) return;
@@ -13,7 +13,6 @@ const CONSTRUCTS: SubsystemComponentConstruct[] = [
13
13
  'interface',
14
14
  'type_alias',
15
15
  'enum',
16
- 'module',
17
16
  'store',
18
17
  'external',
19
18
  'custom_entity',
@@ -31,7 +30,7 @@ describe('constructColorsFromPierreTheme', () => {
31
30
  }
32
31
  });
33
32
 
34
- test('all ten constructs are distinguishable within a theme', () => {
33
+ test('all nine constructs are distinguishable within a theme', () => {
35
34
  for (const themeName of ['pierre-dark', 'pierre-light'] as const) {
36
35
  const colors = constructColorsFromPierreTheme(themeName);
37
36
  const values = CONSTRUCTS.map((c) => colors[c]);
@@ -89,7 +89,6 @@ export function constructColorsFromPierreTheme(
89
89
  const classColor = pick('entity.name.class');
90
90
  const functionColor = pick('entity.name.function');
91
91
  const typeColor = pick('support.type');
92
- const moduleColor = pick('entity.name.namespace');
93
92
 
94
93
  return {
95
94
  class: classColor,
@@ -101,10 +100,6 @@ export function constructColorsFromPierreTheme(
101
100
  method: towardBackground(functionColor, 0.14),
102
101
  // enum members are constants — the generic constant hue
103
102
  enum: pick('constant'),
104
- // module and store share the constant hue — the module holds the store,
105
- // and the store IS the retained value, so the store keeps the pure
106
- // `variable.other.constant` color and the module takes the shade
107
- module: towardBackground(moduleColor, 0.18),
108
103
  store: pick('variable.other.constant'),
109
104
  external: pick('comment'),
110
105
  // custom entities are actors, not code — the heading/emphasis coral reads
@@ -4,7 +4,7 @@ export { PierreSnippetView } from './PierreSnippetView';
4
4
  export type { PierreSnippetViewProps } from './PierreSnippetView';
5
5
  export { PierreWalkthroughCodeView } from './PierreWalkthroughCodeView';
6
6
  export type { PierreWalkthroughCodeViewProps } from './PierreWalkthroughCodeView';
7
- export { sliceSnippetWindow } from './sliceSnippet';
7
+ export { remapSnippetLineNumbers, sliceSnippetWindow } from './sliceSnippet';
8
8
  export type { SnippetSlice } from './sliceSnippet';
9
9
  export {
10
10
  isPierreCFamilyPath,
@@ -9,3 +9,16 @@ export function scrollAnchorLine(
9
9
  if (focusOffset <= 1) return 1;
10
10
  return Math.max(1, focusOffset - leadingLines);
11
11
  }
12
+
13
+ /** Scroll a Pierre file container so `focusLine` (1-based) sits near the top. */
14
+ export function scrollFocusLineIntoView(
15
+ fileContainer: HTMLElement,
16
+ focusLine: number,
17
+ ): void {
18
+ const root = fileContainer.shadowRoot ?? fileContainer;
19
+ const anchorLine = scrollAnchorLine(focusLine);
20
+ const lineEl = root.querySelector(
21
+ `[data-line="${anchorLine}"]`,
22
+ ) as HTMLElement | null;
23
+ lineEl?.scrollIntoView({ block: 'start', behavior: 'auto' });
24
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { sliceSnippetWindow } from './sliceSnippet';
2
+ import { Window } from 'happy-dom';
3
+ import { remapSnippetLineNumbers, sliceSnippetWindow } from './sliceSnippet';
3
4
 
4
5
  describe('sliceSnippetWindow', () => {
5
6
  const content = ['line1', 'line2', 'line3', 'line4', 'line5'].join('\n');
@@ -19,3 +20,45 @@ describe('sliceSnippetWindow', () => {
19
20
  expect(s.focusOffset).toBe(95);
20
21
  });
21
22
  });
23
+
24
+ describe('remapSnippetLineNumbers', () => {
25
+ function gutter(doc: Document, lineIndex: number, label: string): HTMLElement {
26
+ const el = doc.createElement('div');
27
+ el.setAttribute('data-column-number', '');
28
+ el.dataset.lineIndex = String(lineIndex);
29
+ const span = doc.createElement('span');
30
+ span.setAttribute('data-line-number-content', '');
31
+ span.textContent = label;
32
+ el.appendChild(span);
33
+ return el;
34
+ }
35
+
36
+ test('rewrites gutters to absolute file lines', () => {
37
+ const win = new Window();
38
+ const doc = win.document;
39
+ const root = doc.createElement('div');
40
+ root.appendChild(gutter(doc, 0, '1'));
41
+ root.appendChild(gutter(doc, 1, '2'));
42
+ root.appendChild(gutter(doc, 2, '3'));
43
+
44
+ remapSnippetLineNumbers(root as unknown as HTMLElement, 250);
45
+
46
+ const labels = [...root.querySelectorAll('[data-line-number-content]')].map(
47
+ (n) => n.textContent,
48
+ );
49
+ expect(labels).toEqual(['250', '251', '252']);
50
+ });
51
+
52
+ test('no-ops when slice already starts at line 1', () => {
53
+ const win = new Window();
54
+ const doc = win.document;
55
+ const root = doc.createElement('div');
56
+ root.appendChild(gutter(doc, 0, '1'));
57
+
58
+ remapSnippetLineNumbers(root as unknown as HTMLElement, 1);
59
+
60
+ expect(
61
+ root.querySelector('[data-line-number-content]')?.textContent,
62
+ ).toBe('1');
63
+ });
64
+ });
@@ -29,3 +29,31 @@ export function sliceSnippetWindow(
29
29
  focusLine == null ? null : Math.max(1, focusLine - sliceStart + 1),
30
30
  };
31
31
  }
32
+
33
+ /**
34
+ * Remap Pierre gutter numbers from slice-local (1-based) to absolute file lines.
35
+ * Pierre numbers whatever `contents` it receives starting at 1; sliced snippets
36
+ * need `sliceStart` applied so the gutter matches the source file.
37
+ */
38
+ export function remapSnippetLineNumbers(
39
+ fileContainer: HTMLElement,
40
+ sliceStart: number,
41
+ ): void {
42
+ const lineNumberOffset = sliceStart - 1;
43
+ if (lineNumberOffset === 0) return;
44
+ const root = fileContainer.shadowRoot ?? fileContainer;
45
+ const items = root.querySelectorAll(
46
+ '[data-column-number][data-line-index]',
47
+ );
48
+ items.forEach((el) => {
49
+ const idxStr = (el as HTMLElement).dataset.lineIndex;
50
+ if (idxStr == null) return;
51
+ const idx = Number.parseInt(idxStr, 10);
52
+ if (Number.isNaN(idx)) return;
53
+ const display = String(idx + 1 + lineNumberOffset);
54
+ const span = el.querySelector('[data-line-number-content]');
55
+ if (span && span.textContent !== display) {
56
+ span.textContent = display;
57
+ }
58
+ });
59
+ }
@@ -197,37 +197,6 @@ const cases: { label: string; component: SubsystemComponent }[] = [
197
197
  } satisfies GraphifyComponentDetail,
198
198
  },
199
199
  },
200
- {
201
- label: 'module — imports + exports',
202
- component: {
203
- id: 'mod-full',
204
- name: 'index',
205
- construct: 'module',
206
- file: 'src/index.ts',
207
- purl: 'pkg:github/principal-ai/agent-monitoring',
208
- symbol: 'index',
209
- declaration: {
210
- kind: 'module',
211
- imports: [
212
- { name: './session/SessionReader' },
213
- { name: './event-processing/EventProcessor' },
214
- ],
215
- exports: ['SessionReader', 'EventProcessor'],
216
- symbols: ['SessionReader', 'EventProcessor', 'normalizeSession'],
217
- } satisfies GraphifyComponentDetail,
218
- },
219
- },
220
- {
221
- label: 'module — no detail (fallback)',
222
- component: {
223
- id: 'mod-plain',
224
- name: 'utils',
225
- construct: 'module',
226
- file: 'src/utils.ts',
227
- purl: 'pkg:github/principal-ai/agent-monitoring',
228
- symbol: 'utils',
229
- },
230
- },
231
200
  {
232
201
  label: 'external',
233
202
  component: {
@@ -109,7 +109,7 @@ export const NamingConventions: Story = {
109
109
  };
110
110
 
111
111
  // ---------------------------------------------------------------------------
112
- // Kind variations — one column per kind (class / function / type / module /
112
+ // Kind variations — one column per kind (class / function / type / external —
113
113
  // external), rows going bare → detailed. Click a node to see its declaration
114
114
  // panel; type names inside it are clickable when they match another node here.
115
115
  // ---------------------------------------------------------------------------
@@ -269,50 +269,6 @@ const kindVariationComponents: SubsystemComponent[] = [
269
269
  } satisfies GraphifyComponentDetail,
270
270
  },
271
271
 
272
- // --- module: bare → exports only → exports + imports
273
- {
274
- id: 'mod-bare',
275
- name: 'transcript',
276
- construct: 'module',
277
- file: 'src/session/transcript.ts',
278
- purl: variationPurl,
279
- purpose: 'no drill-down — symbol-less module named from its file basename',
280
- symbol: '',
281
- layer: 4,
282
- },
283
- {
284
- id: 'mod-exports',
285
- name: 'paths',
286
- construct: 'module',
287
- file: 'src/session/paths.ts',
288
- purl: variationPurl,
289
- purpose: 'exports only — export statement + defines comment',
290
- symbol: '',
291
- layer: 4,
292
- declaration: {
293
- kind: 'module',
294
- exports: ['extractToolName', 'extractFilePath'],
295
- imports: [],
296
- symbols: ['extractToolName', 'extractFilePath'],
297
- } satisfies GraphifyComponentDetail,
298
- },
299
- {
300
- id: 'mod-full',
301
- name: 'index',
302
- construct: 'module',
303
- file: 'src/index.ts',
304
- purl: variationPurl,
305
- purpose: 'full — import statements + re-export statement + defines comment',
306
- symbol: '',
307
- layer: 4,
308
- declaration: {
309
- kind: 'module',
310
- exports: ['SessionReader', 'SessionStore'],
311
- imports: [{ nodeId: 'i1', name: 'transcript', relation: 'imports_from' }],
312
- symbols: ['SessionReader', 'SessionStore'],
313
- } satisfies GraphifyComponentDetail,
314
- },
315
-
316
272
  // --- external: bare → labeled
317
273
  {
318
274
  id: 'ext-bare',
@@ -351,7 +307,7 @@ function KindVariationsDemo() {
351
307
  onSelect={(id) => setSelected(id)}
352
308
  />
353
309
  <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
354
- columns: class · function · type · module · external — each goes bare → detailed
310
+ columns: class · function · type · external — each goes bare → detailed
355
311
  {selected ? ` · selected: ${selected}` : ''}
356
312
  </div>
357
313
  </div>
@@ -31,10 +31,10 @@ type Story = StoryObj<typeof meta>;
31
31
  // ---------------------------------------------------------------------------
32
32
  const v2ReaderComponents = components([
33
33
  ['capture', 'capture script', 'function', 'scripts/capture-session.ts', 'pkg:github/principal-ai/agent-monitoring', 'captures a real session for fixtures'],
34
- ['transcript', 'transcript', 'module', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards'],
35
- ['paths', 'paths', 'module', 'paths.ts', 'pkg:github/principal-ai/agent-monitoring', 'extracts tool names + file paths'],
34
+ ['transcript', 'transcript', 'function', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards'],
35
+ ['paths', 'paths', 'function', 'paths.ts', 'pkg:github/principal-ai/agent-monitoring', 'extracts tool names + file paths'],
36
36
  ['reader', 'SessionReader', 'class', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes a session into universal events', 'SessionReader.normalize', readerDetail],
37
- ['registry', 'supported-agents', 'module', 'supported-agents.ts', 'pkg:github/principal-ai/agent-monitoring', 'registry of supported agents (the shared seam)', 'registerAgent'],
37
+ ['registry', 'supported-agents', 'function', 'supported-agents.ts', 'pkg:github/principal-ai/agent-monitoring', 'registry of supported agents (the shared seam)', 'registerAgent'],
38
38
  ]);
39
39
 
40
40
  const v2ReaderEdges = graphSpecFromEdges([
@@ -95,11 +95,11 @@ const minimalComponents = components([
95
95
  ['reader', 'SessionReader', 'class', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'normalizes a session into universal events', 'SessionReader'],
96
96
  ['normalize', 'normalize', 'function', 'SessionReader.ts', 'pkg:github/principal-ai/agent-monitoring', 'maps a session into universal events', 'SessionReader.normalize'],
97
97
  ['record', 'SessionRecord', 'type', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'the parsed codex session record', 'SessionRecord'],
98
- ['transcript', 'transcript', 'module', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards', 'transcript'],
98
+ ['transcript', 'transcript', 'function', 'transcript.ts', 'pkg:github/principal-ai/agent-monitoring', 'parses session records + type guards', 'transcript'],
99
99
  ]);
100
100
 
101
101
  const sharedEdges = graphSpecFromEdges([
102
- ['transcript', 'record', 'defines'],
102
+ ['transcript', 'record', 'references'],
103
103
  ['reader', 'normalize', 'method'],
104
104
  ['normalize', 'record', 'references'],
105
105
  ]);
@@ -129,15 +129,6 @@ const resolvedComponents: SubsystemComponent[] = [
129
129
  implementors: ['SessionReaderLike'],
130
130
  } satisfies GraphifyComponentDetail,
131
131
  },
132
- {
133
- ...minimalComponents[3],
134
- declaration: {
135
- kind: 'module',
136
- exports: ['CodexRolloutRecord', 'CodexSessionMeta'],
137
- imports: [{ nodeId: 'i1', name: 'paths', relation: 'imports_from' }],
138
- symbols: ['CodexRolloutRecord', 'CodexSessionMeta'],
139
- } satisfies GraphifyComponentDetail,
140
- },
141
132
  ];
142
133
 
143
134
  function MinimalVsResolvedDemo({ resolved }: { resolved: boolean }) {
@@ -174,22 +165,16 @@ const investigationComponents: SubsystemComponent[] = [
174
165
  {
175
166
  id: 'adapter',
176
167
  name: 'OpenCodeAdapter',
177
- construct: 'module',
168
+ construct: 'function',
178
169
  file: 'apps/server/src/provider/Layers/OpenCodeAdapter.ts',
179
170
  purl: 'pkg:github/t3code/t3code',
180
171
  purpose: 'adapts opencode session/threads + events to the t3 runtime',
181
172
  symbol: 'makeOpenCodeAdapter',
182
- declaration: {
183
- kind: 'module',
184
- exports: ['makeOpenCodeAdapter', 'OpenCodeAdapterLiveOptions'],
185
- imports: [{ nodeId: 'i1', name: 'orchestration', relation: 'imports_from' }],
186
- symbols: ['makeOpenCodeAdapter', 'isOpenCodeNotFound', 'OpenCodeSessionContext'],
187
- } satisfies GraphifyComponentDetail,
188
173
  },
189
174
  {
190
175
  id: 'ingestion',
191
176
  name: 'ProviderRuntimeIngestion',
192
- construct: 'module',
177
+ construct: 'function',
193
178
  file: 'apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts',
194
179
  purl: 'pkg:github/t3code/t3code',
195
180
  purpose: 'ingests provider events into the runtime',
@@ -198,7 +183,7 @@ const investigationComponents: SubsystemComponent[] = [
198
183
  {
199
184
  id: 'contracts',
200
185
  name: 'orchestration',
201
- construct: 'module',
186
+ construct: 'function',
202
187
  file: 'packages/contracts/src/orchestration.ts',
203
188
  purl: 'pkg:github/t3code/t3code',
204
189
  purpose: 'contracts for orchestration/providers',
@@ -207,7 +192,7 @@ const investigationComponents: SubsystemComponent[] = [
207
192
  ];
208
193
 
209
194
  const investigationEdges = graphSpecFromEdges([
210
- ['adapter', 'contracts', 'imports_from'],
195
+ ['adapter', 'contracts', 'imports'],
211
196
  ['ingestion', 'adapter', 'uses'],
212
197
  ['ingestion', 'contracts', 'references'],
213
198
  ]);
@@ -234,7 +234,7 @@ const declarationOpenComponents: SubsystemComponent[] = [
234
234
  {
235
235
  id: 'detail',
236
236
  name: 'ComponentDeclaration',
237
- construct: 'module',
237
+ construct: 'function',
238
238
  file: 'packages/subsystems-react/src/subsystem/ComponentDeclaration.tsx',
239
239
  purl: 'pkg:github/principal-ai/principal-view-core-library',
240
240
  purpose: 'declaration panel — click file or L251 to open the drawer at the export line',
@@ -249,7 +249,7 @@ const declarationOpenComponents: SubsystemComponent[] = [
249
249
  {
250
250
  id: 'resolver',
251
251
  name: 'resolve.ts',
252
- construct: 'module',
252
+ construct: 'function',
253
253
  file: 'packages/subsystems-react/src/graphify/resolve.ts',
254
254
  purl: 'pkg:github/principal-ai/principal-view-core-library',
255
255
  purpose: 'graphify type-ref resolver (second file for tree navigation)',
@@ -159,12 +159,13 @@ const drawingWalkthroughs: SubsystemWalkthrough[] = [
159
159
 
160
160
  function FlowsDemo() {
161
161
  const renderWalkthroughViewer = useCallback(
162
- ({ walkthrough, stepIndex }: WalkthroughViewerContext) => (
162
+ ({ walkthrough, stepIndex, onOpenFile }: WalkthroughViewerContext) => (
163
163
  <PierreWalkthroughCodeView
164
164
  walkthrough={walkthrough}
165
165
  stepIndex={stepIndex}
166
166
  readFile={readStoryFile}
167
167
  contextLines={4}
168
+ onOpenFile={onOpenFile}
168
169
  />
169
170
  ),
170
171
  [],