@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,959 @@
1
+ import React, { useState } from 'react';
2
+ import '@xyflow/react/dist/style.css';
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
5
+ import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
6
+ import type {
7
+ SubsystemComponent,
8
+ SubsystemComponentEdge,
9
+ SubsystemGraphDocument,
10
+ } from '../../../subsystem/model';
11
+ import type { GraphifyComponentDetail } from '../../../graphify';
12
+ import { components, edges } from './fixtures';
13
+
14
+ const meta = {
15
+ title: 'Subsystem/ComponentGraph/Scenarios',
16
+ component: SubsystemComponentGraph,
17
+ parameters: {
18
+ layout: 'fullscreen',
19
+ },
20
+ tags: ['autodocs'],
21
+ decorators: [
22
+ (Story) => (
23
+ <ThemeProvider theme={defaultEditorTheme}>
24
+ <Story />
25
+ </ThemeProvider>
26
+ ),
27
+ ],
28
+ } satisfies Meta<typeof SubsystemComponentGraph>;
29
+
30
+ export default meta;
31
+ type Story = StoryObj<typeof meta>;
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Mermaid diagram rendering pipeline (industry-themed-markdown)
35
+ // ---------------------------------------------------------------------------
36
+ const mermaidPurl = 'pkg:github/principal-ade/industry-themed-markdown';
37
+
38
+ const mermaidComponents: SubsystemComponent[] = [
39
+ {
40
+ id: 'input',
41
+ name: 'MarkdownContent',
42
+ construct: 'type_alias',
43
+ file: 'industryMarkdown/components/IndustryMarkdownSlide.tsx',
44
+ purl: mermaidPurl,
45
+ purpose: 'raw markdown string — the slide\u2019s input',
46
+ symbol: 'MarkdownContent',
47
+ },
48
+ {
49
+ id: 'slide',
50
+ name: 'IndustryMarkdownSlide',
51
+ construct: 'class',
52
+ file: 'industryMarkdown/components/IndustryMarkdownSlide.tsx',
53
+ purl: mermaidPurl,
54
+ purpose: 'orchestrator — parses markdown into chunks, maps mermaid chunks to diagrams',
55
+ symbol: 'IndustryMarkdownSlide',
56
+ },
57
+ {
58
+ id: 'chunk',
59
+ name: 'MermaidChunk',
60
+ construct: 'type_alias',
61
+ file: 'industryMarkdown/types/customMarkdownChunks.ts',
62
+ purl: mermaidPurl,
63
+ purpose: 'parsed chunk — code string + id, the slide\u2019s output per mermaid block',
64
+ symbol: 'MermaidChunk',
65
+ },
66
+ {
67
+ id: 'lazy',
68
+ name: 'IndustryLazyMermaidDiagram',
69
+ construct: 'class',
70
+ file: 'industryMarkdown/components/IndustryLazyMermaidDiagram.tsx',
71
+ purl: mermaidPurl,
72
+ purpose: 'IntersectionObserver lazy-loading wrapper \u2014 defers render until scrolled into view',
73
+ symbol: 'IndustryLazyMermaidDiagram',
74
+ },
75
+ {
76
+ id: 'diagram',
77
+ name: 'IndustryMermaidDiagram',
78
+ construct: 'class',
79
+ file: 'industryMarkdown/components/IndustryMermaidDiagram.tsx',
80
+ purl: mermaidPurl,
81
+ purpose: 'core renderer \u2014 picks engine (beautiful-mermaid vs mermaid.js), renders themed SVG',
82
+ symbol: 'IndustryMermaidDiagram',
83
+ },
84
+ {
85
+ id: 'helpers',
86
+ name: 'beautifulMermaid',
87
+ construct: 'module',
88
+ file: 'industryMarkdown/utils/beautifulMermaid.ts',
89
+ purl: mermaidPurl,
90
+ purpose: 'engine detection, theme\u2192options mapping, SVG post-processing',
91
+ symbol: 'beautifulMermaid',
92
+ },
93
+ ];
94
+
95
+ const mermaidEdges = edges([
96
+ ['input', 'slide', 'feeds'],
97
+ ['slide', 'chunk', 'produces'],
98
+ ['chunk', 'lazy', 'feeds'],
99
+ ['lazy', 'diagram', 'wraps'],
100
+ ['diagram', 'helpers', 'uses'],
101
+ ]);
102
+
103
+ function MermaidDemo() {
104
+ const [selected, setSelected] = useState<string | null>(null);
105
+ return (
106
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
107
+ <SubsystemComponentGraph
108
+ components={mermaidComponents}
109
+ edges={mermaidEdges}
110
+ onSelect={(id) => setSelected(id)}
111
+ />
112
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
113
+ mermaid diagram rendering pipeline — lazy → render → zoom → modal
114
+ {selected ? ` · selected: ${selected}` : ''}
115
+ </div>
116
+ </div>
117
+ );
118
+ }
119
+
120
+ export const MermaidPipeline: Story = {
121
+ render: () => <MermaidDemo />,
122
+ };
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Multi-repo: two repos → two sidebar trees, one per repo, each under its own
126
+ // owner-avatar + repo-name header. Exercises buildRepoGroups grouping,
127
+ // cross-repo edges (external stub targets), and per-tree selection.
128
+ // ---------------------------------------------------------------------------
129
+ const coreLibPurl = 'pkg:github/principal-ai/principal-view-core-library';
130
+ const graphifyPurl = 'pkg:github/Graphify-Labs/graphify';
131
+
132
+ const multiRepoComponents = components([
133
+ ['resolver', 'resolve.ts', 'module', 'packages/react/src/graphify/resolve.ts', coreLibPurl, 'consumer-side resolution mirroring graphify rewire tiers', 'createGraphifyTypeResolver'],
134
+ ['reftypes', 'consolidated.ts', 'module', 'packages/react/src/graphify/consolidated.ts', coreLibPurl, 'drill-down payload types carrying ref.nodeId', 'GraphifyParamInfo'],
135
+ ['detail', 'ComponentDeclaration.tsx', 'module', 'packages/react/src/subsystem/ComponentDeclaration.tsx', coreLibPurl, 'declaration panel rendering the selected component as code, with clickable type tokens', 'ComponentDeclaration'],
136
+ ['engine', 'engine.py', 'module', 'graphify/extractors/engine.py', graphifyPurl, 'tree-sitter walk emitting references[parameter_type] edges + sourceless stubs', 'ensure_named_node'],
137
+ ['rewire', 'extract.py', 'module', 'graphify/extract.py', graphifyPurl, 'corpus pass folding unique-label stubs onto definitions', '_rewire_unique_stub_nodes'],
138
+ ]);
139
+
140
+ const multiRepoEdges = edges([
141
+ ['detail', 'reftypes', 'imports'],
142
+ ['resolver', 'reftypes', 'imports'],
143
+ ['resolver', 'engine', 'references'],
144
+ ['rewire', 'resolver', 'calls'],
145
+ ]);
146
+
147
+ function MultiRepoDemo() {
148
+ return (
149
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
150
+ <SubsystemComponentGraph
151
+ title="Type-ref resolution across repos"
152
+ description="Two repos → two file trees. Each tree is scoped to its repo's files and headed by the owner avatar + repo name. Cross-repo edges land on external stubs."
153
+ components={multiRepoComponents}
154
+ edges={multiRepoEdges}
155
+ />
156
+ </div>
157
+ );
158
+ }
159
+
160
+ export const MultiRepoTrees: Story = {
161
+ render: () => <MultiRepoDemo />,
162
+ };
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Roles + process boundaries + state-access mechanisms — the trail-viewer
166
+ // access-surface flow authored with the real fields: `construct: store` for
167
+ // the state node, `role: entry` for the two boundary elements, `process`
168
+ // regions (grouped by `process ?? purl`), and the
169
+ // writes/reads/watches edges. Agents and services carry no process, so they
170
+ // sit outside every boundary; the push leg routes THROUGH the IPC entry
171
+ // (boundary invariant: inter-process edges terminate at a boundary element).
172
+ // ---------------------------------------------------------------------------
173
+ const coreLibPurl2 = 'pkg:github/principal-ai/principal-view-core-library';
174
+
175
+ const accessSurfaceComponents: SubsystemComponent[] = [
176
+ // --- external actor: no process → outside every boundary region
177
+ {
178
+ id: 'agents',
179
+ name: 'agent clients',
180
+ construct: 'external',
181
+ file: '',
182
+ purl: 'pkg:generic/local-agent-clients',
183
+ purpose: 'agents and tooling outside the process — enter only via the HTTP bridge',
184
+ layer: 0,
185
+ },
186
+ // --- trail-viewer/host process region
187
+ {
188
+ id: 'http-entry',
189
+ name: 'HTTP bridge :3045',
190
+ construct: 'function',
191
+ file: 'packages/trail-viewer/src/bun/http-server.ts',
192
+ purl: coreLibPurl2,
193
+ symbol: 'handleSubsystemGraphRequest',
194
+ role: 'entry',
195
+ process: 'trail-viewer/host',
196
+ purpose: 'the process\u2019s HTTP surface — request leg only, no push',
197
+ layer: 1,
198
+ },
199
+ {
200
+ id: 'create',
201
+ name: 'createSubsystemGraph',
202
+ construct: 'function',
203
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
204
+ purl: coreLibPurl2,
205
+ symbol: 'createSubsystemGraph',
206
+ process: 'trail-viewer/host',
207
+ purpose: 'persists a new graph JSON into the store',
208
+ layer: 2,
209
+ },
210
+ {
211
+ id: 'update',
212
+ name: 'updateSubsystemGraph',
213
+ construct: 'function',
214
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
215
+ purl: coreLibPurl2,
216
+ symbol: 'updateSubsystemGraph',
217
+ process: 'trail-viewer/host',
218
+ purpose: 'rewrites an existing graph file on PUT',
219
+ layer: 2,
220
+ },
221
+ {
222
+ id: 'delete',
223
+ name: 'deleteSubsystemGraph',
224
+ construct: 'function',
225
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
226
+ purl: coreLibPurl2,
227
+ symbol: 'deleteSubsystemGraph',
228
+ process: 'trail-viewer/host',
229
+ purpose: 'unlinks the graph file and updates the index',
230
+ layer: 2,
231
+ },
232
+ {
233
+ id: 'get',
234
+ name: 'getSubsystemGraph',
235
+ construct: 'function',
236
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
237
+ purl: coreLibPurl2,
238
+ symbol: 'getSubsystemGraph',
239
+ process: 'trail-viewer/host',
240
+ purpose: 'reads one stored graph — serves both surfaces',
241
+ layer: 2,
242
+ },
243
+ {
244
+ id: 'watcher',
245
+ name: 'startSubsystemGraphDirWatcher',
246
+ construct: 'function',
247
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
248
+ purl: coreLibPurl2,
249
+ symbol: 'startSubsystemGraphDirWatcher',
250
+ process: 'trail-viewer/host',
251
+ purpose: 'fs.watch on the graphs dir — observes, owns nothing',
252
+ layer: 2,
253
+ },
254
+ {
255
+ id: 'store',
256
+ name: 'Graph Store',
257
+ construct: 'store',
258
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
259
+ purl: coreLibPurl2,
260
+ process: 'trail-viewer/host',
261
+ purpose: 'retained state: <id>.json files + _index.json + in-memory bookkeeping.\nState-only: the access mechanism lives in the accessor nodes.',
262
+ layer: 3,
263
+ detail: {
264
+ kind: 'store',
265
+ properties: [
266
+ { name: 'ROOT', type: 'string' },
267
+ { name: 'INDEX_PATH', type: 'string' },
268
+ { name: 'changeListener', type: 'SubsystemGraphChangeListener | null' },
269
+ { name: 'recentSelfWrites', type: 'Map<string, number>' },
270
+ { name: 'dirWatcher', type: 'FSWatcher | null' },
271
+ { name: 'pendingWatchIds', type: 'Set<string>' },
272
+ ],
273
+ } satisfies GraphifyComponentDetail,
274
+ },
275
+ {
276
+ id: 'broadcast',
277
+ name: 'broadcastSubsystemGraphChanged',
278
+ construct: 'function',
279
+ file: 'packages/trail-viewer/src/bun/index.ts',
280
+ purl: coreLibPurl2,
281
+ symbol: 'broadcastSubsystemGraphChanged',
282
+ process: 'trail-viewer/host',
283
+ purpose: 'push leg — sends subsystemGraphChanged over Electrobun RPC',
284
+ layer: 4,
285
+ },
286
+ {
287
+ id: 'ipc-entry',
288
+ name: 'TrailViewerMessages',
289
+ construct: 'interface',
290
+ file: 'packages/trail-viewer/src/shared/contract.ts',
291
+ purl: coreLibPurl2,
292
+ symbol: 'TrailViewerMessages',
293
+ role: 'entry',
294
+ process: 'trail-viewer/host',
295
+ purpose: 'the IPC surface — requests in, pushes out (both legs)',
296
+ layer: 4,
297
+ },
298
+ {
299
+ id: 'avatars',
300
+ name: 'resolveAuthorAvatars',
301
+ construct: 'function',
302
+ file: 'packages/trail-viewer/src/bun/avatars.ts',
303
+ purl: coreLibPurl2,
304
+ symbol: 'resolveAuthorAvatars',
305
+ process: 'trail-viewer/host',
306
+ purpose: 'resolves author avatars — outbound call crossing the boundary',
307
+ layer: 4,
308
+ },
309
+ // --- trail-viewer/renderer process region
310
+ {
311
+ id: 'subs',
312
+ name: 'subsystemGraphChangeSubscribers',
313
+ construct: 'function',
314
+ file: 'packages/trail-viewer/src/mainview/rpc.ts',
315
+ purl: coreLibPurl2,
316
+ symbol: 'subsystemGraphChangeSubscribers',
317
+ process: 'trail-viewer/renderer',
318
+ purpose: 'renderer fan-out Set for subsystemGraphChanged payloads',
319
+ layer: 5,
320
+ },
321
+ {
322
+ id: 'open-view',
323
+ name: 'SubsystemGraphView',
324
+ construct: 'function',
325
+ file: 'packages/trail-viewer/src/mainview/views/SubsystemGraphView.tsx',
326
+ purl: coreLibPurl2,
327
+ symbol: 'SubsystemGraphView',
328
+ process: 'trail-viewer/renderer',
329
+ purpose: 'open graph tab — requests over the IPC surface, reloads on push',
330
+ layer: 6,
331
+ },
332
+ {
333
+ id: 'list-view',
334
+ name: 'SubsystemGraphsView',
335
+ construct: 'function',
336
+ file: 'packages/trail-viewer/src/mainview/views/SubsystemGraphsView.tsx',
337
+ purl: coreLibPurl2,
338
+ symbol: 'SubsystemGraphsView',
339
+ process: 'trail-viewer/renderer',
340
+ purpose: 'list tab — requests over the IPC surface, refreshes on push',
341
+ layer: 6,
342
+ },
343
+ // --- external service: no process, identity via purl — the far side of an
344
+ // outbound crossing
345
+ {
346
+ id: 'github',
347
+ name: 'api.github.com',
348
+ construct: 'external',
349
+ file: '',
350
+ purl: 'pkg:generic/api.github.com',
351
+ role: 'service',
352
+ purpose: 'external service — outbound calls terminate here (far-side boundary element)',
353
+ layer: 6,
354
+ },
355
+ ];
356
+
357
+ const accessSurfaceEdges = edges([
358
+ ['agents', 'http-entry', 'calls'],
359
+ ['http-entry', 'create', 'calls'],
360
+ ['http-entry', 'update', 'calls'],
361
+ ['http-entry', 'delete', 'calls'],
362
+ ['http-entry', 'get', 'calls'],
363
+ ['create', 'store', 'writes'],
364
+ ['update', 'store', 'writes'],
365
+ ['delete', 'store', 'writes'],
366
+ ['get', 'store', 'reads'],
367
+ ['watcher', 'store', 'watches'],
368
+ ['store', 'broadcast', 'produces'],
369
+ ['broadcast', 'ipc-entry', 'feeds'],
370
+ ['ipc-entry', 'subs', 'feeds'],
371
+ ['subs', 'open-view', 'feeds'],
372
+ ['subs', 'list-view', 'feeds'],
373
+ ['open-view', 'ipc-entry', 'calls'],
374
+ ['list-view', 'ipc-entry', 'calls'],
375
+ ['avatars', 'github', 'calls'],
376
+ ]);
377
+
378
+ function AccessSurfacesDemo() {
379
+ const [selected, setSelected] = useState<string | null>(null);
380
+ const [selectedEdge, setSelectedEdge] = useState<SubsystemComponentEdge | null>(null);
381
+ return (
382
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
383
+ <SubsystemComponentGraph
384
+ title="Access surfaces, roles, and process boundaries"
385
+ description="Two process regions (host, renderer) + boundary entries; agents and the external service float outside every boundary. Hover for the role badge; click the store to drill into its state-only detail."
386
+ components={accessSurfaceComponents}
387
+ edges={accessSurfaceEdges}
388
+ onSelect={(id) => setSelected(id)}
389
+ onEdgeSelect={(e) => setSelectedEdge(e)}
390
+ />
391
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
392
+ regions: trail-viewer/host · trail-viewer/renderer — agents + api.github.com outside every
393
+ boundary · store edges: writes (deep green) / reads (sky) / watches (dashed gray) / produces
394
+ (terracotta)
395
+ {selectedEdge
396
+ ? ` · edge: ${selectedEdge.from} --${selectedEdge.mechanism}--> ${selectedEdge.to}`
397
+ : selected
398
+ ? ` · selected: ${selected}`
399
+ : ''}
400
+ </div>
401
+ </div>
402
+ );
403
+ }
404
+
405
+ export const AccessSurfacesAndRoles: Story = {
406
+ render: () => <AccessSurfacesDemo />,
407
+ };
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // SCENARIO: Store flavors — three ways retained state shows up, rendered with
411
+ // `construct: 'store'` (state-block anatomy) in all of them:
412
+ // 1. module-state store — state as module-level consts (trail-viewer pattern)
413
+ // 2. class-backed store — a real `class SessionCache` exists in source; the
414
+ // node still renders as a state block (fields only) per the store/accessor
415
+ // separation. OPEN QUESTION: is dropping the class stub the right call
416
+ // when the class declaration is real?
417
+ // 3. external shared store — state on disk outside any repo (sqlite). No
418
+ // process → outside every boundary. OPEN QUESTION: does the boundary
419
+ // invariant extend so crossings may terminate at an external store?
420
+ // ---------------------------------------------------------------------------
421
+ const storeFlavorPurl = 'pkg:github/principal-ai/agent-monitoring';
422
+
423
+ const storeFlavorComponents: SubsystemComponent[] = [
424
+ // --- flavor 1: module-state store
425
+ {
426
+ id: 'f1-create',
427
+ name: 'createSubsystemGraph',
428
+ construct: 'function',
429
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
430
+ purl: coreLibPurl2,
431
+ symbol: 'createSubsystemGraph',
432
+ process: 'trail-viewer/host',
433
+ purpose: 'persists a new graph JSON',
434
+ layer: 1,
435
+ },
436
+ {
437
+ id: 'f1-get',
438
+ name: 'getSubsystemGraph',
439
+ construct: 'function',
440
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
441
+ purl: coreLibPurl2,
442
+ symbol: 'getSubsystemGraph',
443
+ process: 'trail-viewer/host',
444
+ purpose: 'reads one stored graph',
445
+ layer: 1,
446
+ },
447
+ {
448
+ id: 'f1-store',
449
+ name: 'Graph Store',
450
+ construct: 'store',
451
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
452
+ purl: coreLibPurl2,
453
+ process: 'trail-viewer/host',
454
+ purpose: 'module-level state: ROOT, INDEX_PATH, listener + watch bookkeeping',
455
+ layer: 2,
456
+ detail: {
457
+ kind: 'store',
458
+ properties: [
459
+ { name: 'ROOT', type: 'string' },
460
+ { name: 'INDEX_PATH', type: 'string' },
461
+ { name: 'changeListener', type: 'Listener | null' },
462
+ ],
463
+ } satisfies GraphifyComponentDetail,
464
+ },
465
+ // --- flavor 2: class-managed store — TWO nodes, each anchored honestly:
466
+ // the class (verifiable declaration, methods = access mechanism) and
467
+ // the state (construct: 'store', the db/state visualization)
468
+ {
469
+ id: 'f2-cache',
470
+ name: 'SessionCache',
471
+ construct: 'class',
472
+ file: 'src/session/SessionCache.ts',
473
+ purl: storeFlavorPurl,
474
+ symbol: 'SessionCache',
475
+ purpose: 'manages access to cached sessions — the verifiable access mechanism',
476
+ layer: 3,
477
+ detail: {
478
+ kind: 'class',
479
+ methods: [
480
+ { nodeId: 'cm1', name: 'put', parameters: [{ type: 'SessionRecord' }] },
481
+ { nodeId: 'cm2', name: 'get', parameters: [{ type: 'string' }], returnType: 'SessionRecord | null' },
482
+ { nodeId: 'cm3', name: 'evict', parameters: [{ type: 'string' }] },
483
+ ],
484
+ properties: [],
485
+ extends: [],
486
+ implements: [],
487
+ instantiations: [],
488
+ references: [],
489
+ } satisfies GraphifyComponentDetail,
490
+ },
491
+ {
492
+ id: 'f2-store',
493
+ name: 'Session Cache State',
494
+ construct: 'store',
495
+ file: 'src/session/SessionCache.ts',
496
+ purl: storeFlavorPurl,
497
+ purpose: 'retained state visualized alongside its manager class',
498
+ layer: 4,
499
+ detail: {
500
+ kind: 'store',
501
+ properties: [
502
+ { name: 'sessions', type: 'Map<string, SessionRecord>' },
503
+ { name: 'ttlSeconds', type: 'number' },
504
+ ],
505
+ } satisfies GraphifyComponentDetail,
506
+ },
507
+ // --- flavor 3: external shared store
508
+ {
509
+ id: 'f3-accessor',
510
+ name: 'loadAppState',
511
+ construct: 'function',
512
+ file: 'packages/trail-viewer/src/bun/app-state.ts',
513
+ purl: coreLibPurl2,
514
+ symbol: 'loadAppState',
515
+ process: 'trail-viewer/host',
516
+ purpose: 'reads app state from the on-disk sqlite db',
517
+ layer: 5,
518
+ },
519
+ {
520
+ id: 'f3-store',
521
+ name: 'app-state.db',
522
+ construct: 'store',
523
+ file: '',
524
+ purl: 'pkg:generic/local--Users-me-.principal-app-state.db',
525
+ purpose: 'sqlite on disk — retained state outside any repo or process',
526
+ layer: 6,
527
+ detail: {
528
+ kind: 'store',
529
+ properties: [
530
+ { name: 'settings', type: 'AppSettingsRow[]' },
531
+ { name: 'schemaVersion', type: 'number' },
532
+ ],
533
+ } satisfies GraphifyComponentDetail,
534
+ },
535
+ ];
536
+
537
+ const storeFlavorEdges = edges([
538
+ ['f1-create', 'f1-store', 'writes'],
539
+ ['f1-get', 'f1-store', 'reads'],
540
+ ['f2-cache', 'f2-store', 'writes'],
541
+ ['f3-accessor', 'f3-store', 'reads'],
542
+ ]);
543
+
544
+ function StoreFlavorsDemo() {
545
+ const [selected, setSelected] = useState<string | null>(null);
546
+ return (
547
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
548
+ <SubsystemComponentGraph
549
+ title="Store flavors"
550
+ description="construct:= the node's verifiable anchor. Class-managed stores are TWO nodes: the manager class (declaration, methods) + the state (store block). Click any node to see its anchor-honest drill-down."
551
+ components={storeFlavorComponents}
552
+ edges={storeFlavorEdges}
553
+ onSelect={(id) => setSelected(id)}
554
+ />
555
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
556
+ flavor 1: module consts (store node only) · flavor 2: manager class + state node (two-node
557
+ pattern) · flavor 3: external db, crossing terminates at the store (invariant extension —
558
+ refine?)
559
+ {selected ? ` · selected: ${selected}` : ''}
560
+ </div>
561
+ </div>
562
+ );
563
+ }
564
+
565
+ export const StoreFlavors: Story = {
566
+ render: () => <StoreFlavorsDemo />,
567
+ };
568
+
569
+ // ---------------------------------------------------------------------------
570
+ // SCENARIO: Shared store across processes — state that belongs to neither
571
+ // process. The store node carries NO process, so it sits outside every
572
+ // boundary; both processes' accessors reach across to it. OPEN QUESTION:
573
+ // should shared state sit outside boundaries (as here), or inside an owner
574
+ // process with crossings out? And do accessor→shared-store edges satisfy the
575
+ // boundary invariant (store as boundary element)?
576
+ // ---------------------------------------------------------------------------
577
+ const sharedStoreComponents: SubsystemComponent[] = [
578
+ {
579
+ id: 'ss-load',
580
+ name: 'loadSubsystemGraph',
581
+ construct: 'function',
582
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
583
+ purl: coreLibPurl2,
584
+ symbol: 'getSubsystemGraph',
585
+ process: 'trail-viewer/host',
586
+ purpose: 'host accessor — reads one stored graph',
587
+ layer: 1,
588
+ },
589
+ {
590
+ id: 'ss-save',
591
+ name: 'saveSubsystemGraph',
592
+ construct: 'function',
593
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
594
+ purl: coreLibPurl2,
595
+ symbol: 'updateSubsystemGraph',
596
+ process: 'trail-viewer/host',
597
+ purpose: 'host accessor — rewrites a stored graph',
598
+ layer: 1,
599
+ },
600
+ {
601
+ id: 'ss-watch',
602
+ name: 'startSubsystemGraphDirWatcher',
603
+ construct: 'function',
604
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
605
+ purl: coreLibPurl2,
606
+ symbol: 'startSubsystemGraphDirWatcher',
607
+ process: 'trail-viewer/host',
608
+ purpose: 'host watches for external writes (hand edits, other processes)',
609
+ layer: 1,
610
+ },
611
+ {
612
+ id: 'ss-migrate',
613
+ name: 'migrateGraphIndex',
614
+ construct: 'function',
615
+ file: 'packages/cli/src/graph-index.ts',
616
+ purl: coreLibPurl2,
617
+ symbol: 'migrateGraphIndex',
618
+ process: 'principal-cli',
619
+ purpose: 'a second process touches the same state — schema migration',
620
+ layer: 1,
621
+ },
622
+ {
623
+ id: 'ss-store',
624
+ name: '~/.principal/subsystem-graphs',
625
+ construct: 'store',
626
+ file: '',
627
+ purl: 'pkg:generic/local--Users-me-.principal-subsystem-graphs',
628
+ purpose: 'file-per-graph + _index.json — shared state owned by no single process',
629
+ layer: 2,
630
+ detail: {
631
+ kind: 'store',
632
+ properties: [
633
+ { name: 'graphs', type: 'Map<graphId, StoredSubsystemGraph>' },
634
+ { name: 'index', type: '_index.json cache' },
635
+ ],
636
+ } satisfies GraphifyComponentDetail,
637
+ },
638
+ ];
639
+
640
+ const sharedStoreEdges = edges([
641
+ ['ss-load', 'ss-store', 'reads'],
642
+ ['ss-save', 'ss-store', 'writes'],
643
+ ['ss-watch', 'ss-store', 'watches'],
644
+ ['ss-migrate', 'ss-store', 'writes'],
645
+ ]);
646
+
647
+ function SharedStoreDemo() {
648
+ const [selectedEdge, setSelectedEdge] = useState<SubsystemComponentEdge | null>(null);
649
+ return (
650
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
651
+ <SubsystemComponentGraph
652
+ title="Shared store across processes"
653
+ description="A store with no process sits outside every boundary; accessors from both processes reach across to it."
654
+ components={sharedStoreComponents}
655
+ edges={sharedStoreEdges}
656
+ onEdgeSelect={(e) => setSelectedEdge(e)}
657
+ />
658
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
659
+ shared state between trail-viewer/host and principal-cli — refine: outside boundaries (as
660
+ here) vs owned by one process? do these crossings satisfy the invariant?
661
+ {selectedEdge ? ` · edge: ${selectedEdge.from} --${selectedEdge.mechanism}--> ${selectedEdge.to}` : ''}
662
+ </div>
663
+ </div>
664
+ );
665
+ }
666
+
667
+ export const SharedStoreAcrossProcesses: Story = {
668
+ render: () => <SharedStoreDemo />,
669
+ };
670
+
671
+ // ---------------------------------------------------------------------------
672
+ // SCENARIO: Queue as store — ordered retained state. The queue is construct:
673
+ // 'store' (state block: pending jobs, depth); producers write, workers read.
674
+ // OPEN QUESTION: dequeue REMOVES state — is it `reads` (worker pulls) or
675
+ // `writes` (mutates the queue)? Does the verb set need a
676
+ // mutation-vs-observation distinction, or is dequeue just both?
677
+ // ---------------------------------------------------------------------------
678
+ const queueComponents: SubsystemComponent[] = [
679
+ {
680
+ id: 'q-producer',
681
+ name: 'enqueueAnalysisJob',
682
+ construct: 'function',
683
+ file: 'packages/trail-viewer/src/bun/analysis-queue.ts',
684
+ purl: coreLibPurl2,
685
+ symbol: 'enqueueAnalysisJob',
686
+ process: 'trail-viewer/host',
687
+ purpose: 'pushes a repo-analysis job onto the queue',
688
+ layer: 1,
689
+ },
690
+ {
691
+ id: 'q-queue',
692
+ name: 'Analysis Queue',
693
+ construct: 'store',
694
+ file: 'packages/trail-viewer/src/bun/analysis-queue.ts',
695
+ purl: coreLibPurl2,
696
+ process: 'trail-viewer/host',
697
+ purpose: 'ordered retained state — pending jobs, depth, head pointer',
698
+ layer: 2,
699
+ detail: {
700
+ kind: 'store',
701
+ properties: [
702
+ { name: 'pendingJobs', type: 'AnalysisJob[]' },
703
+ { name: 'depth', type: 'number' },
704
+ { name: 'head', type: 'string | null' },
705
+ ],
706
+ } satisfies GraphifyComponentDetail,
707
+ },
708
+ {
709
+ id: 'q-worker',
710
+ name: 'Bun.Worker',
711
+ construct: 'function',
712
+ file: 'packages/trail-viewer/src/bun/analysis-worker.ts',
713
+ purl: coreLibPurl2,
714
+ symbol: 'spawnAnalysisWorker',
715
+ process: 'trail-viewer/host',
716
+ purpose: 'claims the next job and runs the analysis',
717
+ layer: 3,
718
+ },
719
+ {
720
+ id: 'q-results',
721
+ name: 'Analysis Results',
722
+ construct: 'store',
723
+ file: 'packages/trail-viewer/src/bun/analysis-store.ts',
724
+ purl: coreLibPurl2,
725
+ process: 'trail-viewer/host',
726
+ purpose: 'retained results — keyed by repo purl + sha',
727
+ layer: 4,
728
+ detail: {
729
+ kind: 'store',
730
+ properties: [{ name: 'results', type: 'Map<purl, AnalysisResult>' }],
731
+ } satisfies GraphifyComponentDetail,
732
+ },
733
+ ];
734
+
735
+ const queueEdges = edges([
736
+ ['q-producer', 'q-queue', 'writes'],
737
+ ['q-worker', 'q-queue', 'reads'],
738
+ ['q-worker', 'q-results', 'writes'],
739
+ ]);
740
+
741
+ function QueueAsStoreDemo() {
742
+ const [selectedEdge, setSelectedEdge] = useState<SubsystemComponentEdge | null>(null);
743
+ return (
744
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
745
+ <SubsystemComponentGraph
746
+ title="Queue as store"
747
+ description="Ordered retained state. Stores as pipeline stages: producer writes the queue, the worker reads it and writes results."
748
+ components={queueComponents}
749
+ edges={queueEdges}
750
+ onEdgeSelect={(e) => setSelectedEdge(e)}
751
+ />
752
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
753
+ refine: dequeue removes state — reads (pull) or writes (mutate)? verb set needs a
754
+ mutation-vs-observation distinction?
755
+ {selectedEdge ? ` · edge: ${selectedEdge.from} --${selectedEdge.mechanism}--> ${selectedEdge.to}` : ''}
756
+ </div>
757
+ </div>
758
+ );
759
+ }
760
+
761
+ export const QueueAsStore: Story = {
762
+ render: () => <QueueAsStoreDemo />,
763
+ };
764
+
765
+ // ---------------------------------------------------------------------------
766
+ // SCENARIO: Data ↔ Visualization — the raw SubsystemGraphDocument on the left,
767
+ // the rendered graph on the right. The JSON is EDITABLE: change a `construct`, add
768
+ // a `role`, flip `writes` to `reads`, toggle a `process` — and the graph
769
+ // re-renders on the next valid parse. Invalid JSON keeps the last good graph
770
+ // and shows the parse error.
771
+ // ---------------------------------------------------------------------------
772
+ const dataVizDoc: SubsystemGraphDocument = {
773
+ components: [
774
+ {
775
+ id: 'agents',
776
+ name: 'agent clients',
777
+ construct: 'external',
778
+ file: '',
779
+ purl: 'pkg:generic/local-agent-clients',
780
+ purpose: 'external actor — no process, outside every boundary',
781
+ layer: 0,
782
+ },
783
+ {
784
+ id: 'http-entry',
785
+ name: 'HTTP bridge :3045',
786
+ construct: 'function',
787
+ file: 'packages/trail-viewer/src/bun/http-server.ts',
788
+ purl: 'pkg:github/principal-ai/principal-view-core-library',
789
+ symbol: 'handleSubsystemGraphRequest',
790
+ role: 'entry',
791
+ process: 'trail-viewer/host',
792
+ purpose: 'boundary element — anchored to a real declaration',
793
+ layer: 1,
794
+ },
795
+ {
796
+ id: 'create',
797
+ name: 'createSubsystemGraph',
798
+ construct: 'function',
799
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
800
+ purl: 'pkg:github/principal-ai/principal-view-core-library',
801
+ symbol: 'createSubsystemGraph',
802
+ process: 'trail-viewer/host',
803
+ purpose: 'accessor — plain declaration node',
804
+ layer: 2,
805
+ },
806
+ {
807
+ id: 'store',
808
+ name: 'Graph Store',
809
+ construct: 'store',
810
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
811
+ purl: 'pkg:github/principal-ai/principal-view-core-library',
812
+ process: 'trail-viewer/host',
813
+ purpose: 'state-block anatomy — anchored to a state location, not a declaration',
814
+ layer: 3,
815
+ detail: {
816
+ kind: 'store',
817
+ properties: [
818
+ { name: 'ROOT', type: 'string' },
819
+ { name: 'INDEX_PATH', type: 'string' },
820
+ ],
821
+ },
822
+ },
823
+ {
824
+ id: 'get',
825
+ name: 'getSubsystemGraph',
826
+ construct: 'function',
827
+ file: 'packages/trail-viewer/src/bun/subsystem-graph-store.ts',
828
+ purl: 'pkg:github/principal-ai/principal-view-core-library',
829
+ symbol: 'getSubsystemGraph',
830
+ process: 'trail-viewer/host',
831
+ purpose: 'accessor — plain declaration node',
832
+ layer: 2,
833
+ },
834
+ {
835
+ id: 'broadcast',
836
+ name: 'broadcastSubsystemGraphChanged',
837
+ construct: 'function',
838
+ file: 'packages/trail-viewer/src/bun/index.ts',
839
+ purl: 'pkg:github/principal-ai/principal-view-core-library',
840
+ symbol: 'broadcastSubsystemGraphChanged',
841
+ process: 'trail-viewer/host',
842
+ purpose: 'the store\u2019s change stream leaves as produces',
843
+ layer: 4,
844
+ },
845
+ {
846
+ id: 'cache',
847
+ name: 'SessionCache',
848
+ construct: 'class',
849
+ file: 'src/session/SessionCache.ts',
850
+ purl: 'pkg:github/principal-ai/agent-monitoring',
851
+ symbol: 'SessionCache',
852
+ purpose: 'manager class — the verifiable access mechanism (two-node pattern)',
853
+ layer: 5,
854
+ },
855
+ {
856
+ id: 'cache-state',
857
+ name: 'Session Cache State',
858
+ construct: 'store',
859
+ file: 'src/session/SessionCache.ts',
860
+ purl: 'pkg:github/principal-ai/agent-monitoring',
861
+ purpose: 'its retained state — the db/state visualization',
862
+ layer: 6,
863
+ detail: {
864
+ kind: 'store',
865
+ properties: [{ name: 'sessions', type: 'Map<string, SessionRecord>' }],
866
+ },
867
+ },
868
+ ],
869
+ edges: [
870
+ { id: 'e0', from: 'agents', to: 'http-entry', mechanism: 'calls' },
871
+ { id: 'e1', from: 'http-entry', to: 'create', mechanism: 'calls' },
872
+ { id: 'e2', from: 'create', to: 'store', mechanism: 'writes' },
873
+ { id: 'e3', from: 'get', to: 'store', mechanism: 'reads' },
874
+ { id: 'e4', from: 'store', to: 'broadcast', mechanism: 'produces' },
875
+ { id: 'e5', from: 'cache', to: 'cache-state', mechanism: 'writes' },
876
+ ],
877
+ };
878
+
879
+ function DataVsVisualizationDemo() {
880
+ const [text, setText] = useState(() => JSON.stringify(dataVizDoc, null, 2));
881
+ const [doc, setDoc] = useState<SubsystemGraphDocument>(dataVizDoc);
882
+ const [error, setError] = useState<string | null>(null);
883
+
884
+ const onChange = (next: string) => {
885
+ setText(next);
886
+ try {
887
+ const parsed = JSON.parse(next) as SubsystemGraphDocument;
888
+ if (!Array.isArray(parsed.components) || !Array.isArray(parsed.edges)) {
889
+ throw new Error('document needs `components` and `edges` arrays');
890
+ }
891
+ setDoc(parsed);
892
+ setError(null);
893
+ } catch (e) {
894
+ setError((e as Error).message);
895
+ }
896
+ };
897
+
898
+ return (
899
+ <div style={{ width: '100%', height: '100vh', display: 'flex', background: '#0f1216' }}>
900
+ <div
901
+ style={{
902
+ width: '38%',
903
+ minWidth: 380,
904
+ display: 'flex',
905
+ flexDirection: 'column',
906
+ borderRight: '1px solid #333',
907
+ background: '#14171c',
908
+ }}
909
+ >
910
+ <div
911
+ style={{
912
+ padding: '6px 10px',
913
+ fontFamily: 'monospace',
914
+ fontSize: 11,
915
+ color: '#9ca3af',
916
+ borderBottom: '1px solid #333',
917
+ }}
918
+ >
919
+ the data — SubsystemGraphDocument (edit → re-renders; invalid JSON keeps last good graph)
920
+ </div>
921
+ <textarea
922
+ value={text}
923
+ onChange={(e) => onChange(e.target.value)}
924
+ spellCheck={false}
925
+ style={{
926
+ flex: 1,
927
+ resize: 'none',
928
+ border: 'none',
929
+ outline: 'none',
930
+ padding: 10,
931
+ fontFamily: 'monospace',
932
+ fontSize: 11,
933
+ lineHeight: 1.5,
934
+ color: '#d8dee9',
935
+ background: 'transparent',
936
+ whiteSpace: 'pre',
937
+ }}
938
+ />
939
+ {error && (
940
+ <div style={{ padding: '4px 10px', fontFamily: 'monospace', fontSize: 11, color: '#e5534b' }}>
941
+ {error}
942
+ </div>
943
+ )}
944
+ </div>
945
+ <div style={{ flex: 1, minWidth: 0 }}>
946
+ <SubsystemComponentGraph
947
+ title="the visualization"
948
+ description="Same document, rendered: construct:→ node anatomy, role → topology glyph, process → boundary region, mechanism → edge color/style."
949
+ components={doc.components}
950
+ edges={doc.edges}
951
+ />
952
+ </div>
953
+ </div>
954
+ );
955
+ }
956
+
957
+ export const DataVsVisualization: Story = {
958
+ render: () => <DataVsVisualizationDemo />,
959
+ };