@principal-ai/subsystems-react 0.20.5 → 0.21.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 (87) hide show
  1. package/README.md +1 -1
  2. package/dist/graphify/anchor.d.ts +17 -0
  3. package/dist/graphify/anchor.d.ts.map +1 -1
  4. package/dist/graphify/anchor.js +41 -0
  5. package/dist/graphify/anchor.js.map +1 -1
  6. package/dist/graphify/consolidated.d.ts +40 -1
  7. package/dist/graphify/consolidated.d.ts.map +1 -1
  8. package/dist/graphify/construct.d.ts +23 -0
  9. package/dist/graphify/construct.d.ts.map +1 -0
  10. package/dist/graphify/construct.js +77 -0
  11. package/dist/graphify/construct.js.map +1 -0
  12. package/dist/graphify/index.d.ts +2 -2
  13. package/dist/graphify/index.d.ts.map +1 -1
  14. package/dist/graphify/index.js +1 -1
  15. package/dist/graphify/index.js.map +1 -1
  16. package/dist/index.d.ts +7 -7
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/pierre/PierreWalkthroughCodeView.d.ts +22 -0
  21. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -0
  22. package/dist/pierre/PierreWalkthroughCodeView.js +198 -0
  23. package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -0
  24. package/dist/pierre/index.d.ts +2 -2
  25. package/dist/pierre/index.js +1 -1
  26. package/dist/pierre/pierreFileLang.d.ts +1 -1
  27. package/dist/pierre/pierreFileLang.js +1 -1
  28. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +14 -2
  29. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  30. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +86 -20
  31. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  32. package/dist/subsystem/ComponentDeclaration.js +1 -1
  33. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  34. package/dist/subsystem/FileDrawer.d.ts +2 -2
  35. package/dist/subsystem/FileDrawer.js +2 -2
  36. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -24
  37. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  38. package/dist/subsystem/SubsystemComponentGraph.js +220 -194
  39. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  40. package/dist/subsystem/formatDeclaration.d.ts +2 -1
  41. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  42. package/dist/subsystem/formatDeclaration.js +57 -33
  43. package/dist/subsystem/formatDeclaration.js.map +1 -1
  44. package/dist/subsystem/model.d.ts +109 -42
  45. package/dist/subsystem/model.d.ts.map +1 -1
  46. package/dist/subsystem/model.js +107 -5
  47. package/dist/subsystem/model.js.map +1 -1
  48. package/dist/subsystem/nodes.d.ts.map +1 -1
  49. package/dist/subsystem/nodes.js +43 -18
  50. package/dist/subsystem/nodes.js.map +1 -1
  51. package/dist/subsystem/tokenizeComponent.js +2 -2
  52. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  53. package/package.json +3 -3
  54. package/src/graphify/anchor.test.ts +31 -2
  55. package/src/graphify/anchor.ts +46 -0
  56. package/src/graphify/consolidated.ts +43 -1
  57. package/src/graphify/{kind.test.ts → construct.test.ts} +20 -20
  58. package/src/graphify/{kind.ts → construct.ts} +17 -17
  59. package/src/graphify/index.ts +5 -2
  60. package/src/index.ts +20 -9
  61. package/src/pierre/{PierreThroughlineCodeView.tsx → PierreWalkthroughCodeView.tsx} +39 -47
  62. package/src/pierre/index.ts +2 -2
  63. package/src/pierre/pierreFileLang.ts +1 -1
  64. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  65. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +14 -14
  66. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +13 -15
  67. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
  68. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +16 -20
  69. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +11 -10
  70. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +13 -14
  71. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +31 -66
  72. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +2 -2
  73. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  74. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  75. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +254 -0
  76. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +518 -33
  77. package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +13 -13
  78. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +105 -21
  79. package/src/subsystem/ComponentDeclaration.tsx +1 -1
  80. package/src/subsystem/FileDrawer.tsx +2 -2
  81. package/src/subsystem/SubsystemComponentGraph.tsx +284 -254
  82. package/src/subsystem/formatDeclaration.test.ts +76 -1
  83. package/src/subsystem/formatDeclaration.ts +57 -33
  84. package/src/subsystem/model.test.ts +49 -15
  85. package/src/subsystem/model.ts +210 -51
  86. package/src/subsystem/nodes.tsx +63 -22
  87. package/src/subsystem/tokenizeComponent.ts +2 -2
@@ -9,7 +9,7 @@ import type {
9
9
  SubsystemModelDocument,
10
10
  } from '../../../subsystem/model';
11
11
  import type { GraphifyComponentDetail } from '../../../graphify';
12
- import { components, edges } from './fixtures';
12
+ import { components, graphSpecFromEdges, relations } from './fixtures';
13
13
 
14
14
  const meta = {
15
15
  title: 'Subsystem/ComponentGraph/Scenarios',
@@ -92,7 +92,7 @@ const mermaidComponents: SubsystemComponent[] = [
92
92
  },
93
93
  ];
94
94
 
95
- const mermaidEdges = edges([
95
+ const mermaidEdges = graphSpecFromEdges([
96
96
  ['input', 'slide', 'feeds'],
97
97
  ['slide', 'chunk', 'produces'],
98
98
  ['chunk', 'lazy', 'feeds'],
@@ -106,7 +106,7 @@ function MermaidDemo() {
106
106
  <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
107
107
  <SubsystemComponentGraph
108
108
  components={mermaidComponents}
109
- edges={mermaidEdges}
109
+ relations={mermaidEdges.relations} walkthroughs={mermaidEdges.walkthroughs}
110
110
  onSelect={(id) => setSelected(id)}
111
111
  />
112
112
  <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
@@ -137,7 +137,7 @@ const multiRepoComponents = components([
137
137
  ['rewire', 'extract.py', 'module', 'graphify/extract.py', graphifyPurl, 'corpus pass folding unique-label stubs onto definitions', '_rewire_unique_stub_nodes'],
138
138
  ]);
139
139
 
140
- const multiRepoEdges = edges([
140
+ const multiRepoEdges = graphSpecFromEdges([
141
141
  ['detail', 'reftypes', 'imports'],
142
142
  ['resolver', 'reftypes', 'imports'],
143
143
  ['resolver', 'engine', 'references'],
@@ -151,7 +151,7 @@ function MultiRepoDemo() {
151
151
  title="Type-ref resolution across repos"
152
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
153
  components={multiRepoComponents}
154
- edges={multiRepoEdges}
154
+ relations={multiRepoEdges.relations} walkthroughs={multiRepoEdges.walkthroughs}
155
155
  />
156
156
  </div>
157
157
  );
@@ -260,7 +260,7 @@ const accessSurfaceComponents: SubsystemComponent[] = [
260
260
  process: 'principal-studio/host',
261
261
  purpose: 'retained state: <id>.json files + _index.json + in-memory bookkeeping.\nState-only: the access mechanism lives in the accessor nodes.',
262
262
  layer: 3,
263
- detail: {
263
+ declaration: {
264
264
  kind: 'store',
265
265
  properties: [
266
266
  { name: 'ROOT', type: 'string' },
@@ -354,7 +354,7 @@ const accessSurfaceComponents: SubsystemComponent[] = [
354
354
  },
355
355
  ];
356
356
 
357
- const accessSurfaceEdges = edges([
357
+ const accessSurfaceEdges = graphSpecFromEdges([
358
358
  ['agents', 'http-entry', 'calls'],
359
359
  ['http-entry', 'create', 'calls'],
360
360
  ['http-entry', 'update', 'calls'],
@@ -384,7 +384,7 @@ function AccessSurfacesDemo() {
384
384
  title="Access surfaces, roles, and process boundaries"
385
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
386
  components={accessSurfaceComponents}
387
- edges={accessSurfaceEdges}
387
+ relations={accessSurfaceEdges.relations} walkthroughs={accessSurfaceEdges.walkthroughs}
388
388
  onSelect={(id) => setSelected(id)}
389
389
  onEdgeSelect={(e) => setSelectedEdge(e)}
390
390
  />
@@ -453,7 +453,7 @@ const storeFlavorComponents: SubsystemComponent[] = [
453
453
  process: 'principal-studio/host',
454
454
  purpose: 'module-level state: ROOT, INDEX_PATH, listener + watch bookkeeping',
455
455
  layer: 2,
456
- detail: {
456
+ declaration: {
457
457
  kind: 'store',
458
458
  properties: [
459
459
  { name: 'ROOT', type: 'string' },
@@ -474,7 +474,7 @@ const storeFlavorComponents: SubsystemComponent[] = [
474
474
  symbol: 'SessionCache',
475
475
  purpose: 'manages access to cached sessions — the verifiable access mechanism',
476
476
  layer: 3,
477
- detail: {
477
+ declaration: {
478
478
  kind: 'class',
479
479
  methods: [
480
480
  { nodeId: 'cm1', name: 'put', parameters: [{ type: 'SessionRecord' }] },
@@ -496,7 +496,7 @@ const storeFlavorComponents: SubsystemComponent[] = [
496
496
  purl: storeFlavorPurl,
497
497
  purpose: 'retained state visualized alongside its manager class',
498
498
  layer: 4,
499
- detail: {
499
+ declaration: {
500
500
  kind: 'store',
501
501
  properties: [
502
502
  { name: 'sessions', type: 'Map<string, SessionRecord>' },
@@ -524,7 +524,7 @@ const storeFlavorComponents: SubsystemComponent[] = [
524
524
  purl: 'pkg:generic/local--Users-me-.principal-app-state.db',
525
525
  purpose: 'sqlite on disk — retained state outside any repo or process',
526
526
  layer: 6,
527
- detail: {
527
+ declaration: {
528
528
  kind: 'store',
529
529
  properties: [
530
530
  { name: 'settings', type: 'AppSettingsRow[]' },
@@ -534,7 +534,7 @@ const storeFlavorComponents: SubsystemComponent[] = [
534
534
  },
535
535
  ];
536
536
 
537
- const storeFlavorEdges = edges([
537
+ const storeFlavorEdges = graphSpecFromEdges([
538
538
  ['f1-create', 'f1-store', 'writes'],
539
539
  ['f1-get', 'f1-store', 'reads'],
540
540
  ['f2-cache', 'f2-store', 'writes'],
@@ -549,7 +549,7 @@ function StoreFlavorsDemo() {
549
549
  title="Store flavors"
550
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
551
  components={storeFlavorComponents}
552
- edges={storeFlavorEdges}
552
+ relations={storeFlavorEdges.relations} walkthroughs={storeFlavorEdges.walkthroughs}
553
553
  onSelect={(id) => setSelected(id)}
554
554
  />
555
555
  <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
@@ -627,7 +627,7 @@ const sharedStoreComponents: SubsystemComponent[] = [
627
627
  purl: 'pkg:generic/local--Users-me-.principal-subsystem-models',
628
628
  purpose: 'file-per-graph + _index.json — shared state owned by no single process',
629
629
  layer: 2,
630
- detail: {
630
+ declaration: {
631
631
  kind: 'store',
632
632
  properties: [
633
633
  { name: 'graphs', type: 'Map<graphId, StoredSubsystemModel>' },
@@ -637,7 +637,7 @@ const sharedStoreComponents: SubsystemComponent[] = [
637
637
  },
638
638
  ];
639
639
 
640
- const sharedStoreEdges = edges([
640
+ const sharedStoreEdges = graphSpecFromEdges([
641
641
  ['ss-load', 'ss-store', 'reads'],
642
642
  ['ss-save', 'ss-store', 'writes'],
643
643
  ['ss-watch', 'ss-store', 'watches'],
@@ -652,7 +652,7 @@ function SharedStoreDemo() {
652
652
  title="Shared store across processes"
653
653
  description="A store with no process sits outside every boundary; accessors from both processes reach across to it."
654
654
  components={sharedStoreComponents}
655
- edges={sharedStoreEdges}
655
+ relations={sharedStoreEdges.relations} walkthroughs={sharedStoreEdges.walkthroughs}
656
656
  onEdgeSelect={(e) => setSelectedEdge(e)}
657
657
  />
658
658
  <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
@@ -668,6 +668,483 @@ export const SharedStoreAcrossProcesses: Story = {
668
668
  render: () => <SharedStoreDemo />,
669
669
  };
670
670
 
671
+ // ---------------------------------------------------------------------------
672
+ // SCENARIO: State → Store spectrum — every holder modeled as `construct:
673
+ // 'store'` (state-block anatomy) so the differences show up ONLY in name,
674
+ // purpose, and retained `properties`. Ordered strongest → weakest store-ness:
675
+ //
676
+ // 1. app-state.db — persistent, on disk, outside any process/repo
677
+ // 2. Graph Store — module consts backed by files (survives restarts)
678
+ // 3. Session Cache State — class-managed retained data (two-node pattern)
679
+ // 4. Audit Metrics Buffer — module in-memory data collection, no interface
680
+ // 5. Studio Config — module singleton values, stable + named
681
+ // 6. Selected Graph Id — one scalar module binding
682
+ // 7. Proposals Subscribers — behavior payload (callbacks), transient
683
+ //
684
+ // REVIEW: all seven claim `store`. Which would you stop calling a store, and
685
+ // where is the cut? Does one construct flatten over the persistence / contract
686
+ // / data-vs-behavior distinctions we actually care about?
687
+ // ---------------------------------------------------------------------------
688
+ const spectrumPurl = 'pkg:github/principal-ai/principal-view-core-library';
689
+
690
+ const storeSpectrumComponents: SubsystemComponent[] = [
691
+ // --- 1. persistent store — strongest store-ness
692
+ {
693
+ id: 'db',
694
+ name: 'app-state.db',
695
+ construct: 'store',
696
+ file: '',
697
+ purl: 'pkg:generic/local-sqlite-app-state',
698
+ purpose:
699
+ 'persistent — on disk, outside any process or repo; survives restarts',
700
+ layer: 0,
701
+ declaration: {
702
+ kind: 'store',
703
+ storage: 'external',
704
+ properties: [
705
+ { name: 'settings', type: 'AppSettingsRow[]' },
706
+ { name: 'schemaVersion', type: 'number' },
707
+ ],
708
+ } satisfies GraphifyComponentDetail,
709
+ },
710
+ {
711
+ id: 'load',
712
+ name: 'loadAppState',
713
+ construct: 'function',
714
+ file: 'packages/subsystems-studio/src/bun/app-state.ts',
715
+ purl: spectrumPurl,
716
+ symbol: 'loadAppState',
717
+ purpose: 'reads retained state into the process',
718
+ layer: 1,
719
+ },
720
+ // --- 2. file-backed module store
721
+ {
722
+ id: 'graph-store',
723
+ name: 'Graph Store',
724
+ construct: 'store',
725
+ file: 'packages/subsystems-studio/src/bun/subsystem-model-store.ts',
726
+ purl: spectrumPurl,
727
+ purpose:
728
+ 'module consts backed by files — persists across restarts; bookkeeping itself is Maps/Sets',
729
+ layer: 2,
730
+ declaration: {
731
+ kind: 'store',
732
+ storage: 'disk',
733
+ properties: [
734
+ { name: 'ROOT', type: 'string' },
735
+ { name: 'INDEX_PATH', type: 'string' },
736
+ { name: 'recentSelfWrites', type: 'Map<string, number>' },
737
+ { name: 'pendingWatchIds', type: 'Set<string>' },
738
+ ],
739
+ } satisfies GraphifyComponentDetail,
740
+ },
741
+ {
742
+ id: 'create',
743
+ name: 'createSubsystemModel',
744
+ construct: 'function',
745
+ file: 'packages/subsystems-studio/src/bun/subsystem-model-store.ts',
746
+ purl: spectrumPurl,
747
+ symbol: 'createSubsystemModel',
748
+ purpose: 'writes one graph file + index entry',
749
+ layer: 1,
750
+ },
751
+ // --- 3. class-managed retained data — manager class + separate store node
752
+ {
753
+ id: 'cache',
754
+ name: 'SessionCache',
755
+ construct: 'class',
756
+ file: 'src/session/SessionCache.ts',
757
+ purl: 'pkg:github/principal-ai/agent-monitoring',
758
+ symbol: 'SessionCache',
759
+ purpose: 'manager — access mechanism; the state it owns is a SEPARATE node',
760
+ layer: 3,
761
+ declaration: {
762
+ kind: 'class',
763
+ methods: [
764
+ { nodeId: 'cm1', name: 'put', parameters: [{ type: 'SessionRecord' }] },
765
+ {
766
+ nodeId: 'cm2',
767
+ name: 'get',
768
+ parameters: [{ type: 'string' }],
769
+ returnType: 'SessionRecord | null',
770
+ },
771
+ { nodeId: 'cm3', name: 'evict', parameters: [{ type: 'string' }] },
772
+ ],
773
+ properties: [],
774
+ extends: [],
775
+ implements: [],
776
+ instantiations: [],
777
+ references: [],
778
+ } satisfies GraphifyComponentDetail,
779
+ },
780
+ {
781
+ id: 'cache-state',
782
+ name: 'Session Cache State',
783
+ construct: 'store',
784
+ file: 'src/session/SessionCache.ts',
785
+ purl: 'pkg:github/principal-ai/agent-monitoring',
786
+ purpose:
787
+ 'class-managed, in-memory retained data — has a contract, dies with the process',
788
+ layer: 4,
789
+ declaration: {
790
+ kind: 'store',
791
+ storage: 'memory',
792
+ properties: [
793
+ { name: 'sessions', type: 'Map<string, SessionRecord>' },
794
+ { name: 'ttlSeconds', type: 'number' },
795
+ ],
796
+ } satisfies GraphifyComponentDetail,
797
+ },
798
+ // --- 4. module in-memory data collection — no interface, no persistence
799
+ {
800
+ id: 'metrics',
801
+ name: 'Audit Metrics Buffer',
802
+ construct: 'store',
803
+ file: 'packages/subsystems-studio/src/bun/audit-metrics.ts',
804
+ purl: spectrumPurl,
805
+ purpose:
806
+ 'module Map — in-memory data, app-lifetime, no access interface, lost on restart',
807
+ layer: 6,
808
+ declaration: {
809
+ kind: 'store',
810
+ storage: 'memory',
811
+ properties: [
812
+ { name: 'byRepo', type: 'Map<string, number[]>' },
813
+ { name: 'lastFlushAt', type: 'number | null' },
814
+ ],
815
+ } satisfies GraphifyComponentDetail,
816
+ },
817
+ {
818
+ id: 'record',
819
+ name: 'recordAuditResult',
820
+ construct: 'function',
821
+ file: 'packages/subsystems-studio/src/bun/audit-metrics.ts',
822
+ purl: spectrumPurl,
823
+ symbol: 'recordAuditResult',
824
+ purpose: 'appends a timed audit result to the buffer',
825
+ layer: 5,
826
+ },
827
+ // --- 5. module singleton values
828
+ {
829
+ id: 'config',
830
+ name: 'Studio Config',
831
+ construct: 'store',
832
+ file: 'packages/subsystems-studio/src/bun/config.ts',
833
+ purl: spectrumPurl,
834
+ purpose: 'module singleton — named settings, stable for the app, not persisted',
835
+ layer: 8,
836
+ declaration: {
837
+ kind: 'store',
838
+ storage: 'memory',
839
+ properties: [
840
+ { name: 'openaiBaseUrl', type: 'string' },
841
+ { name: 'showTrailIdsInList', type: 'boolean' },
842
+ ],
843
+ } satisfies GraphifyComponentDetail,
844
+ },
845
+ {
846
+ id: 'conf',
847
+ name: 'readStudioConfig',
848
+ construct: 'function',
849
+ file: 'packages/subsystems-studio/src/bun/config.ts',
850
+ purl: spectrumPurl,
851
+ symbol: 'readStudioConfig',
852
+ purpose: 'reads the live singleton',
853
+ layer: 7,
854
+ },
855
+ // --- 6. lone scalar module binding
856
+ {
857
+ id: 'selected',
858
+ name: 'Selected Graph Id',
859
+ construct: 'store',
860
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
861
+ purl: spectrumPurl,
862
+ purpose: 'one scalar binding — a variable that holds state; nothing to enumerate',
863
+ layer: 10,
864
+ declaration: {
865
+ kind: 'store',
866
+ storage: 'memory',
867
+ properties: [{ name: 'selectedId', type: 'string | null' }],
868
+ } satisfies GraphifyComponentDetail,
869
+ },
870
+ {
871
+ id: 'sel',
872
+ name: 'chooseGraph',
873
+ construct: 'function',
874
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
875
+ purl: spectrumPurl,
876
+ symbol: 'chooseGraph',
877
+ purpose: 'writes the active graph id',
878
+ layer: 9,
879
+ },
880
+ // --- 7. behavior bag — least store-like (the disputed rpc.ts case)
881
+ {
882
+ id: 'subs',
883
+ name: 'Proposals Subscribers',
884
+ construct: 'store',
885
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
886
+ purl: spectrumPurl,
887
+ symbol: 'subsystemModelProposalsChangeSubscribers',
888
+ purpose:
889
+ 'module Set of callbacks — TRANSIENT behavior, emptied by unmount; its only “members” are its own add/delete',
890
+ layer: 12,
891
+ declaration: {
892
+ kind: 'store',
893
+ storage: 'memory',
894
+ properties: [
895
+ { name: 'add', type: '(fn) => void' },
896
+ { name: 'delete', type: '(fn) => void' },
897
+ ],
898
+ } satisfies GraphifyComponentDetail,
899
+ },
900
+ {
901
+ id: 'dispatch',
902
+ name: 'subsystemModelProposalsChanged',
903
+ construct: 'function',
904
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
905
+ purl: spectrumPurl,
906
+ symbol: 'subsystemModelProposalsChanged',
907
+ purpose: 'fans a host message out to every registered callback',
908
+ layer: 11,
909
+ },
910
+ {
911
+ id: 'view',
912
+ name: 'SubsystemModelsView',
913
+ construct: 'function',
914
+ file: 'packages/subsystems-studio/src/mainview/views/SubsystemModelsView.tsx',
915
+ purl: spectrumPurl,
916
+ symbol: 'SubsystemModelsView',
917
+ purpose: 'registers a callback on mount, unregisters on unmount',
918
+ layer: 13,
919
+ },
920
+ ];
921
+
922
+ const storeSpectrumEdges = graphSpecFromEdges([
923
+ ['load', 'db', 'reads'],
924
+ ['create', 'graph-store', 'writes'],
925
+ ['cache', 'cache-state', 'writes'],
926
+ ['record', 'metrics', 'writes'],
927
+ ['conf', 'config', 'reads'],
928
+ ['sel', 'selected', 'writes'],
929
+ ['dispatch', 'subs', 'feeds'],
930
+ ['subs', 'view', 'feeds'],
931
+ ]);
932
+
933
+ function StoreStateSpectrumDemo() {
934
+ const [selected, setSelected] = useState<string | null>(null);
935
+ return (
936
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
937
+ <SubsystemComponentGraph
938
+ title="State → Store spectrum, all modeled as store"
939
+ description="Every holder here claims construct: 'store' (state-block anatomy). They differ only in what they hold and how long it lives. Which ones do you call a store? Where is the cut?"
940
+ components={storeSpectrumComponents}
941
+ relations={storeSpectrumEdges.relations} walkthroughs={storeSpectrumEdges.walkthroughs}
942
+ onSelect={(id) => setSelected(id)}
943
+ />
944
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
945
+ top → bottom: persistent db · file-backed module store · class-managed cache · in-memory
946
+ data buffer · singleton config · scalar binding · transient behavior bag
947
+ {selected ? ` · selected: ${selected}` : ''}
948
+ </div>
949
+ </div>
950
+ );
951
+ }
952
+
953
+ export const StateStoreSpectrum: Story = {
954
+ render: () => <StoreStateSpectrumDemo />,
955
+ };
956
+
957
+ // ---------------------------------------------------------------------------
958
+ // SCENARIO: Type-family spectrum — what a type_alias/interface/enum can hold.
959
+ // Structured buckets where available (callable signature, union, alias, enum
960
+ // members); `rhs` is the verbatim escape hatch when the shape doesn't fit.
961
+ // ---------------------------------------------------------------------------
962
+ const typePurl = 'pkg:github/principal-ai/subsystem-modeling';
963
+
964
+ const typeSpectrumComponents: SubsystemComponent[] = [
965
+ // --- 1. generic callable — the StudioMessageSubscriber refactor case
966
+ {
967
+ id: 'subscriber',
968
+ name: 'StudioMessageSubscriber',
969
+ construct: 'type_alias',
970
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
971
+ purl: typePurl,
972
+ symbol: 'StudioMessageSubscriber',
973
+ purpose:
974
+ 'generic callback contract — `(payload) => void` keyed by a StudioMessages entry',
975
+ layer: 2,
976
+ declaration: {
977
+ kind: 'type',
978
+ generics: [{ name: 'K', constraint: 'keyof StudioMessages' }],
979
+ signature: {
980
+ parameters: [{ name: 'payload', type: 'StudioMessages[K]' }],
981
+ returnType: 'void',
982
+ },
983
+ } satisfies GraphifyComponentDetail,
984
+ },
985
+ {
986
+ id: 'listener',
987
+ name: 'registerProposalListener',
988
+ construct: 'function',
989
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
990
+ purl: typePurl,
991
+ symbol: 'registerProposalListener',
992
+ purpose: 'the consumer the alias types — adds to the fan-out bag',
993
+ layer: 1,
994
+ },
995
+ // --- 2. interface — object shape, the path that worked before
996
+ {
997
+ id: 'messages',
998
+ name: 'StudioMessages',
999
+ construct: 'interface',
1000
+ file: 'packages/subsystems-studio/src/shared/contract.ts',
1001
+ purl: typePurl,
1002
+ symbol: 'StudioMessages',
1003
+ purpose: 'the message contract — every entry is a listener target',
1004
+ layer: 3,
1005
+ declaration: {
1006
+ kind: 'type',
1007
+ properties: [
1008
+ {
1009
+ name: 'subsystemModelProposalsChanged',
1010
+ type: '{ status: "run" | "done"; proposals: unknown[] }',
1011
+ },
1012
+ { name: 'graphifyChanged', type: '{ repo: string; graph: unknown }' },
1013
+ ],
1014
+ } satisfies GraphifyComponentDetail,
1015
+ },
1016
+ // --- 3. enum — named members
1017
+ {
1018
+ id: 'kind',
1019
+ name: 'MaintainRunKind',
1020
+ construct: 'enum',
1021
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
1022
+ purl: typePurl,
1023
+ symbol: 'MaintainRunKind',
1024
+ purpose: 'enum — named states with literal values',
1025
+ layer: 5,
1026
+ declaration: {
1027
+ kind: 'type',
1028
+ enumMembers: [
1029
+ { name: 'Running', value: "'running'" },
1030
+ { name: 'Done', value: "'done'" },
1031
+ { name: 'Error', value: "'error'" },
1032
+ ],
1033
+ } satisfies GraphifyComponentDetail,
1034
+ },
1035
+ // --- 4. union — alternatives
1036
+ {
1037
+ id: 'state',
1038
+ name: 'UIState',
1039
+ construct: 'type_alias',
1040
+ file: 'packages/subsystems-studio/src/mainview/rpc.ts',
1041
+ purl: typePurl,
1042
+ symbol: 'UIState',
1043
+ purpose: 'simple union — one of these literals',
1044
+ layer: 7,
1045
+ declaration: {
1046
+ kind: 'type',
1047
+ unionOf: ["'idle'", "'busy'", "'running'", "'error'"],
1048
+ } satisfies GraphifyComponentDetail,
1049
+ },
1050
+ // --- 5. plain reference alias
1051
+ {
1052
+ id: 'rows',
1053
+ name: 'SessionRows',
1054
+ construct: 'type_alias',
1055
+ file: 'packages/subsystems-studio/src/bun/server-sessions.ts',
1056
+ purl: typePurl,
1057
+ symbol: 'SessionRows',
1058
+ purpose: 'transparent alias — RHS is just another type name',
1059
+ layer: 9,
1060
+ declaration: {
1061
+ kind: 'type',
1062
+ aliasOf: 'ServerSessionRow[]',
1063
+ } satisfies GraphifyComponentDetail,
1064
+ },
1065
+ // --- 6. does-not-fit → raw rhs escape hatch
1066
+ {
1067
+ id: 'deep',
1068
+ name: 'DeepPartial',
1069
+ construct: 'type_alias',
1070
+ file: 'packages/subsystems-studio/src/mainview/types.ts',
1071
+ purl: typePurl,
1072
+ symbol: 'DeepPartial',
1073
+ purpose:
1074
+ 'mapped + conditional + recursive — a type computation, not a shape; shown verbatim via rhs',
1075
+ layer: 6,
1076
+ declaration: {
1077
+ kind: 'type',
1078
+ generics: [{ name: 'T' }],
1079
+ rhs: '{ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }',
1080
+ } satisfies GraphifyComponentDetail,
1081
+ },
1082
+ {
1083
+ id: 'api',
1084
+ name: 'ApiRoute',
1085
+ construct: 'type_alias',
1086
+ file: 'packages/subsystems-studio/src/bun/http-server.ts',
1087
+ purl: typePurl,
1088
+ symbol: 'ApiRoute',
1089
+ purpose: 'template-literal type — no structured bucket; raw rhs shown as-is',
1090
+ layer: 11,
1091
+ declaration: {
1092
+ kind: 'type',
1093
+ rhs: "'/api/${string}'",
1094
+ } satisfies GraphifyComponentDetail,
1095
+ },
1096
+ // --- 7. the reference target the aliasOf/raw types point at
1097
+ {
1098
+ id: 'record',
1099
+ name: 'ServerSessionRow',
1100
+ construct: 'interface',
1101
+ file: 'packages/subsystems-studio/src/shared/contract.ts',
1102
+ purl: typePurl,
1103
+ symbol: 'ServerSessionRow',
1104
+ purpose: 'row shape the SessionRows alias references',
1105
+ layer: 10,
1106
+ declaration: {
1107
+ kind: 'type',
1108
+ properties: [
1109
+ { name: 'id', type: 'string' },
1110
+ { name: 'model', type: 'string' },
1111
+ { name: 'sessionId', type: 'string | null' },
1112
+ ],
1113
+ } satisfies GraphifyComponentDetail,
1114
+ },
1115
+ ];
1116
+
1117
+ const typeSpectrumEdges = graphSpecFromEdges([
1118
+ ['listener', 'subscriber', 'uses'],
1119
+ ['subscriber', 'messages', 'references'],
1120
+ ['state', 'listener', 'feeds'],
1121
+ ['deep', 'record', 'references'],
1122
+ ['rows', 'record', 'references'],
1123
+ ]);
1124
+
1125
+ function TypeFamilySpectrumDemo() {
1126
+ const [selected, setSelected] = useState<string | null>(null);
1127
+ return (
1128
+ <div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
1129
+ <SubsystemComponentGraph
1130
+ title="Type-family — what a type can hold"
1131
+ description="Structured buckets: callable signature (generics + params), interface properties, enum members, union, plain alias reference. `rhs` = verbatim escape hatch for shapes that don't fit (mapped/conditional/template-literal). Click each to see the declaration."
1132
+ components={typeSpectrumComponents}
1133
+ relations={typeSpectrumEdges.relations} walkthroughs={typeSpectrumEdges.walkthroughs}
1134
+ onSelect={(id) => setSelected(id)}
1135
+ />
1136
+ <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
1137
+ callable · interface · enum · union · reference · rhs (DeepPartial) · rhs (template-literal)
1138
+ {selected ? ` · selected: ${selected}` : ''}
1139
+ </div>
1140
+ </div>
1141
+ );
1142
+ }
1143
+
1144
+ export const TypeFamilySpectrum: Story = {
1145
+ render: () => <TypeFamilySpectrumDemo />,
1146
+ };
1147
+
671
1148
  // ---------------------------------------------------------------------------
672
1149
  // SCENARIO: Queue as store — ordered retained state. The queue is construct:
673
1150
  // 'store' (state block: pending jobs, depth); producers write, workers read.
@@ -696,7 +1173,7 @@ const queueComponents: SubsystemComponent[] = [
696
1173
  process: 'principal-studio/host',
697
1174
  purpose: 'ordered retained state — pending jobs, depth, head pointer',
698
1175
  layer: 2,
699
- detail: {
1176
+ declaration: {
700
1177
  kind: 'store',
701
1178
  properties: [
702
1179
  { name: 'pendingJobs', type: 'AnalysisJob[]' },
@@ -725,14 +1202,14 @@ const queueComponents: SubsystemComponent[] = [
725
1202
  process: 'principal-studio/host',
726
1203
  purpose: 'retained results — keyed by repo purl + sha',
727
1204
  layer: 4,
728
- detail: {
1205
+ declaration: {
729
1206
  kind: 'store',
730
1207
  properties: [{ name: 'results', type: 'Map<purl, AnalysisResult>' }],
731
1208
  } satisfies GraphifyComponentDetail,
732
1209
  },
733
1210
  ];
734
1211
 
735
- const queueEdges = edges([
1212
+ const queueEdges = graphSpecFromEdges([
736
1213
  ['q-producer', 'q-queue', 'writes'],
737
1214
  ['q-worker', 'q-queue', 'reads'],
738
1215
  ['q-worker', 'q-results', 'writes'],
@@ -746,7 +1223,7 @@ function QueueAsStoreDemo() {
746
1223
  title="Queue as store"
747
1224
  description="Ordered retained state. Stores as pipeline stages: producer writes the queue, the worker reads it and writes results."
748
1225
  components={queueComponents}
749
- edges={queueEdges}
1226
+ relations={queueEdges.relations} walkthroughs={queueEdges.walkthroughs}
750
1227
  onEdgeSelect={(e) => setSelectedEdge(e)}
751
1228
  />
752
1229
  <div style={{ marginTop: 8, fontFamily: 'monospace', fontSize: 12, color: '#aaa' }}>
@@ -812,7 +1289,7 @@ const dataVizDoc: SubsystemModelDocument = {
812
1289
  process: 'principal-studio/host',
813
1290
  purpose: 'state-block anatomy — anchored to a state location, not a declaration',
814
1291
  layer: 3,
815
- detail: {
1292
+ declaration: {
816
1293
  kind: 'store',
817
1294
  properties: [
818
1295
  { name: 'ROOT', type: 'string' },
@@ -860,19 +1337,26 @@ const dataVizDoc: SubsystemModelDocument = {
860
1337
  purl: 'pkg:github/principal-ai/agent-monitoring',
861
1338
  purpose: 'its retained state — the db/state visualization',
862
1339
  layer: 6,
863
- detail: {
1340
+ declaration: {
864
1341
  kind: 'store',
865
1342
  properties: [{ name: 'sessions', type: 'Map<string, SessionRecord>' }],
866
1343
  },
867
1344
  },
868
1345
  ],
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' },
1346
+ relations: [],
1347
+ walkthroughs: [
1348
+ {
1349
+ id: 'data-viz-hops',
1350
+ title: 'Runtime hops',
1351
+ steps: [
1352
+ { from: 'agents', to: 'http-entry', mechanism: 'calls', file: 'src/http.ts', line: 1 },
1353
+ { from: 'http-entry', to: 'create', mechanism: 'calls', file: 'src/http.ts', line: 2 },
1354
+ { from: 'create', to: 'store', mechanism: 'writes', file: 'src/create.ts', line: 1 },
1355
+ { from: 'get', to: 'store', mechanism: 'reads', file: 'src/get.ts', line: 1 },
1356
+ { from: 'store', to: 'broadcast', mechanism: 'produces', file: 'src/store.ts', line: 1 },
1357
+ { from: 'cache', to: 'cache-state', mechanism: 'writes', file: 'src/cache.ts', line: 1 },
1358
+ ],
1359
+ },
876
1360
  ],
877
1361
  };
878
1362
 
@@ -885,8 +1369,8 @@ function DataVsVisualizationDemo() {
885
1369
  setText(next);
886
1370
  try {
887
1371
  const parsed = JSON.parse(next) as SubsystemModelDocument;
888
- if (!Array.isArray(parsed.components) || !Array.isArray(parsed.edges)) {
889
- throw new Error('document needs `components` and `edges` arrays');
1372
+ if (!Array.isArray(parsed.components) || !Array.isArray(parsed.relations)) {
1373
+ throw new Error('document needs `components` and `relations` arrays');
890
1374
  }
891
1375
  setDoc(parsed);
892
1376
  setError(null);
@@ -947,7 +1431,8 @@ function DataVsVisualizationDemo() {
947
1431
  title="the visualization"
948
1432
  description="Same document, rendered: construct:→ node anatomy, role → topology glyph, process → boundary region, mechanism → edge color/style."
949
1433
  components={doc.components}
950
- edges={doc.edges}
1434
+ relations={doc.relations}
1435
+ walkthroughs={doc.walkthroughs}
951
1436
  />
952
1437
  </div>
953
1438
  </div>