@principal-ai/subsystems-react 0.42.0 → 0.43.1

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 (81) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/stories/Subsystem/C4Graph/c4Fixture.js +1 -1
  6. package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
  7. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +6 -9
  8. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  9. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +15 -50
  10. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  11. package/dist/subsystem/C4Graph.d.ts +2 -3
  12. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  13. package/dist/subsystem/C4Graph.js +2 -3
  14. package/dist/subsystem/C4Graph.js.map +1 -1
  15. package/dist/subsystem/IssueList.d.ts +5 -5
  16. package/dist/subsystem/IssueList.d.ts.map +1 -1
  17. package/dist/subsystem/IssueList.js +9 -18
  18. package/dist/subsystem/IssueList.js.map +1 -1
  19. package/dist/subsystem/SubsystemComponentGraph.d.ts +16 -12
  20. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  21. package/dist/subsystem/SubsystemComponentGraph.js +99 -113
  22. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  23. package/dist/subsystem/SubsystemFileTree.d.ts +5 -5
  24. package/dist/subsystem/SubsystemFileTree.d.ts.map +1 -1
  25. package/dist/subsystem/SubsystemFileTree.js +13 -49
  26. package/dist/subsystem/SubsystemFileTree.js.map +1 -1
  27. package/dist/subsystem/model.d.ts +67 -53
  28. package/dist/subsystem/model.d.ts.map +1 -1
  29. package/dist/subsystem/model.js +305 -55
  30. package/dist/subsystem/model.js.map +1 -1
  31. package/dist/subsystem/nodes.d.ts.map +1 -1
  32. package/dist/subsystem/nodes.js +11 -4
  33. package/dist/subsystem/nodes.js.map +1 -1
  34. package/dist/subsystem/toC4.d.ts +4 -4
  35. package/dist/subsystem/toC4.d.ts.map +1 -1
  36. package/dist/subsystem/toC4.js +3 -7
  37. package/dist/subsystem/toC4.js.map +1 -1
  38. package/dist/utils/elkLayout.d.ts.map +1 -1
  39. package/dist/utils/elkLayout.js +7 -0
  40. package/dist/utils/elkLayout.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/index.ts +0 -4
  43. package/src/stories/Subsystem/AgentsPanel.stories.tsx +3 -5
  44. package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +1 -4
  45. package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
  46. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -4
  47. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
  48. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +19 -19
  49. package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +0 -1
  50. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
  51. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +0 -2
  52. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +2 -6
  53. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +21 -21
  54. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +0 -3
  55. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +30 -32
  56. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  57. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +1 -26
  58. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +3 -25
  59. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +7 -8
  60. package/src/stories/Subsystem/ComponentGraph/ModulePathNesting.stories.tsx +116 -0
  61. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +9 -11
  62. package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +3 -6
  63. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  64. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +8 -6
  65. package/src/stories/Subsystem/ComponentGraph/ProposedWalkthroughs.stories.tsx +0 -1
  66. package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +0 -1
  67. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +26 -28
  68. package/src/stories/Subsystem/ComponentGraph/WalkthroughAutoplay.stories.tsx +0 -1
  69. package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +2 -3
  70. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +18 -58
  71. package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +0 -2
  72. package/src/subsystem/C4Graph.tsx +2 -3
  73. package/src/subsystem/IssueList.tsx +12 -22
  74. package/src/subsystem/SubsystemComponentGraph.tsx +117 -135
  75. package/src/subsystem/SubsystemFileTree.tsx +17 -50
  76. package/src/subsystem/model.test.ts +155 -24
  77. package/src/subsystem/model.ts +352 -108
  78. package/src/subsystem/nodes.tsx +13 -3
  79. package/src/subsystem/toC4.test.ts +5 -12
  80. package/src/subsystem/toC4.ts +5 -9
  81. package/src/utils/elkLayout.ts +10 -0
@@ -22,7 +22,9 @@ import {
22
22
  boundaryMinWidthForBadge,
23
23
  MODULE_BADGE_INSET,
24
24
  packageGroupNodeId,
25
+ directoryGroupNodeId,
25
26
  buildBoundaryLayoutGroups,
27
+ buildBoundaryLayoutGroupsByPath,
26
28
  buildSubsystemGraph,
27
29
  componentPackageKey,
28
30
  deriveNameFromSymbol,
@@ -48,13 +50,35 @@ const comps: SubsystemComponent[] = [
48
50
  { alias: 'transcript', name: 'transcript', construct: 'function', file: 'transcript.ts', purl: 'pkg:github/principal-ai/agent-monitoring' },
49
51
  ];
50
52
 
51
- const relations = [
52
- { id: 'e1', from: 'transcript', to: 'reader', relationType: 'method' as const },
53
- // 'host' is NOT a component — this is the cross-package external case.
54
- { id: 'e2', from: 'reader', to: 'host', relationType: 'method' as const, refs: ['bun/index.ts'] },
53
+ // 'host' is NOT a component — this is the cross-package external case.
54
+ const walkthroughs: SubsystemWalkthrough[] = [
55
+ {
56
+ id: 'wt',
57
+ title: 'flow',
58
+ steps: [
59
+ {
60
+ from: 'transcript',
61
+ to: 'reader',
62
+ mechanism: 'calls',
63
+ file: 'transcript.ts',
64
+ line: 1,
65
+ purl: 'pkg:github/principal-ai/agent-monitoring#transcript.ts',
66
+ symbol: 'transcript',
67
+ },
68
+ {
69
+ from: 'reader',
70
+ to: 'host',
71
+ mechanism: 'calls',
72
+ file: 'SessionReader.ts',
73
+ line: 1,
74
+ purl: 'pkg:github/principal-ai/agent-monitoring#SessionReader.ts',
75
+ symbol: 'SessionReader',
76
+ },
77
+ ],
78
+ },
55
79
  ];
56
80
 
57
- const doc = { components: comps, relations };
81
+ const doc = { components: comps, walkthroughs };
58
82
 
59
83
  describe('subsystem graph model', () => {
60
84
  test('converts all components to flat component nodes', () => {
@@ -85,7 +109,6 @@ describe('subsystem graph model', () => {
85
109
  ];
86
110
  const { nodes } = await buildSubsystemGraph({
87
111
  components: withExternal,
88
- relations: [],
89
112
  walkthroughs: [{
90
113
  id: 'w1',
91
114
  title: 'feed',
@@ -294,7 +317,6 @@ describe('subsystem graph model', () => {
294
317
  },
295
318
  { alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
296
319
  ],
297
- relations: [],
298
320
  });
299
321
  expect((nodes.find((n) => n.id === 'a') as { parentId?: string }).parentId).toBe(
300
322
  moduleGroupNodeId('src/a.ts'),
@@ -308,7 +330,6 @@ describe('subsystem graph model', () => {
308
330
  { alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
309
331
  { alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
310
332
  ],
311
- relations: [],
312
333
  });
313
334
  expect((nodes.find((n) => n.id === 'a') as { parentId?: string }).parentId).toBe(
314
335
  processGroupNodeId('app/host'),
@@ -404,7 +425,6 @@ describe('subsystem graph model', () => {
404
425
  symbol: 'SessionStore',
405
426
  },
406
427
  ],
407
- relations: [],
408
428
  });
409
429
  expect(regions.map((r) => r.key).sort()).toEqual(
410
430
  ['app/host', 'src/main.ts', 'src/store.ts'].sort(),
@@ -432,7 +452,6 @@ describe('subsystem graph model', () => {
432
452
  { alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
433
453
  { alias: 'solo', name: 'solo', construct: 'function', file: 's.ts', purl: 'pkg:github/acme/app', process: 'app/lonely' },
434
454
  ],
435
- relations: [],
436
455
  walkthroughs: [{
437
456
  id: 'w1',
438
457
  title: 'call',
@@ -566,7 +585,6 @@ describe('subsystem graph model', () => {
566
585
  symbol: 'lonely',
567
586
  },
568
587
  ],
569
- relations: [],
570
588
  });
571
589
  expect(regions.map((r) => r.key)).toEqual(['src/session/transcript.ts']);
572
590
  expect(regions[0]!.kind).toBe('module');
@@ -701,7 +719,6 @@ describe('subsystem graph model', () => {
701
719
  module: 'src/b.ts',
702
720
  },
703
721
  ],
704
- relations: [],
705
722
  });
706
723
  const pkgA = nodes.find((n) => n.id === packageGroupNodeId('pkg:github/acme/app'));
707
724
  const proc = nodes.find((n) => n.id === processGroupNodeId('app/host'));
@@ -791,7 +808,7 @@ describe('subsystem graph model', () => {
791
808
  }
792
809
  : c,
793
810
  ),
794
- relations,
811
+ walkthroughs,
795
812
  };
796
813
  expect(subsystemGraphLayoutKey(base)).toBe(subsystemGraphLayoutKey(withRef));
797
814
  });
@@ -799,7 +816,7 @@ describe('subsystem graph model', () => {
799
816
 
800
817
  describe('isConstructsOnlyModel', () => {
801
818
  test('true when components exist and there are no edges', () => {
802
- expect(isConstructsOnlyModel({ components: comps, relations: [], walkthroughs: [] })).toBe(true);
819
+ expect(isConstructsOnlyModel({ components: comps, walkthroughs: [] })).toBe(true);
803
820
  expect(isConstructsOnlyModel({ components: comps })).toBe(true);
804
821
  });
805
822
 
@@ -812,13 +829,12 @@ describe('isConstructsOnlyModel', () => {
812
829
  ).toBe(false);
813
830
  });
814
831
 
815
- test('false when empty, or when relations or walkthrough hops exist', () => {
816
- expect(isConstructsOnlyModel({ components: [], relations: [] })).toBe(false);
817
- expect(isConstructsOnlyModel({ components: comps, relations })).toBe(false);
832
+ test('false when empty, or when walkthrough hops exist', () => {
833
+ expect(isConstructsOnlyModel({ components: [] })).toBe(false);
834
+ expect(isConstructsOnlyModel({ components: comps, walkthroughs })).toBe(false);
818
835
  expect(
819
836
  isConstructsOnlyModel({
820
837
  components: comps,
821
- relations: [],
822
838
  walkthroughs: [
823
839
  {
824
840
  id: 'wt',
@@ -837,6 +853,122 @@ describe('isConstructsOnlyModel', () => {
837
853
  }),
838
854
  ).toBe(false);
839
855
  });
856
+
857
+ test('false when module/process containment frames exist, even with no edges', () => {
858
+ expect(
859
+ isConstructsOnlyModel({
860
+ components: [
861
+ { alias: 'a', module: 'src/a.ts' },
862
+ { alias: 'b', module: 'src/b.ts' },
863
+ ],
864
+ }),
865
+ ).toBe(false);
866
+ expect(
867
+ isConstructsOnlyModel({
868
+ components: [{ alias: 'a', process: 'app/server' }],
869
+ }),
870
+ ).toBe(false);
871
+ // Blank/whitespace membership is not a frame.
872
+ expect(
873
+ isConstructsOnlyModel({ components: [{ alias: 'a', module: ' ' }] }),
874
+ ).toBe(true);
875
+ });
876
+ });
877
+
878
+ describe('buildBoundaryLayoutGroupsByPath', () => {
879
+ const pathComps: SubsystemComponent[] = [
880
+ { alias: 'record', name: 'record', construct: 'type_alias', file: 'src/session/transcript.ts', module: 'src/session/transcript.ts', purl: 'pkg:github/p/app' },
881
+ { alias: 'parse', name: 'parse', construct: 'function', file: 'src/session/transcript.ts', module: 'src/session/transcript.ts', purl: 'pkg:github/p/app' },
882
+ { alias: 'tool-name', name: 'toolName', construct: 'function', file: 'src/session/paths.ts', module: 'src/session/paths.ts', purl: 'pkg:github/p/app' },
883
+ { alias: 'boot', name: 'boot', construct: 'function', file: 'src/host/main.ts', module: 'src/host/main.ts', purl: 'pkg:github/p/app' },
884
+ { alias: 'store', name: 'store', construct: 'store', file: 'src/host/store.ts', module: 'src/host/store.ts', purl: 'pkg:github/p/app' },
885
+ ];
886
+
887
+ test('derives directory frames and nests modules under them', () => {
888
+ const groups = buildBoundaryLayoutGroupsByPath(
889
+ { components: pathComps },
890
+ { showSingletonFrames: true },
891
+ );
892
+ const byId = new Map(groups.map((g) => [g.id, g]));
893
+
894
+ expect(byId.has(directoryGroupNodeId('src'))).toBe(true);
895
+ expect(byId.has(directoryGroupNodeId('src/session'))).toBe(true);
896
+ expect(byId.has(directoryGroupNodeId('src/host'))).toBe(true);
897
+
898
+ // directory → directory nesting
899
+ expect(byId.get(directoryGroupNodeId('src/session'))?.parentId).toBe(
900
+ directoryGroupNodeId('src'),
901
+ );
902
+ // module → directory nesting
903
+ expect(byId.get(moduleGroupNodeId('src/session/transcript.ts'))?.parentId).toBe(
904
+ directoryGroupNodeId('src/session'),
905
+ );
906
+ // top directory has no frame above it
907
+ expect(byId.get(directoryGroupNodeId('src'))?.parentId).toBeUndefined();
908
+
909
+ // Labels are relative to the parent frame, not the full path.
910
+ expect(byId.get(directoryGroupNodeId('src'))?.region.label).toBe('src');
911
+ expect(byId.get(directoryGroupNodeId('src/session'))?.region.label).toBe('session');
912
+ expect(byId.get(moduleGroupNodeId('src/session/transcript.ts'))?.region.label).toBe(
913
+ 'transcript.ts',
914
+ );
915
+ });
916
+
917
+ test('each leaf is claimed by exactly one group (no compound duplicates)', () => {
918
+ const groups = buildBoundaryLayoutGroupsByPath(
919
+ { components: pathComps },
920
+ { showSingletonFrames: true },
921
+ );
922
+ const groupIds = new Set(groups.map((g) => g.id));
923
+ const leafCount = new Map<string, number>();
924
+ for (const g of groups) {
925
+ for (const m of g.memberAliases) {
926
+ // group ids are allowed to repeat (as child references); leaves must not
927
+ if (groupIds.has(m)) continue;
928
+ leafCount.set(m, (leafCount.get(m) ?? 0) + 1);
929
+ }
930
+ }
931
+ for (const [alias, n] of leafCount) {
932
+ expect(n, `${alias} claimed ${n} times`).toBe(1);
933
+ }
934
+ });
935
+
936
+ test('compacts single-child directory chains (app → app/book)', () => {
937
+ const comps: SubsystemComponent[] = [
938
+ { alias: 'page', name: 'page', construct: 'function', file: 'app/book/page.tsx', module: 'app/book/page.tsx', purl: 'pkg:github/p/app' },
939
+ { alias: 'actions', name: 'actions', construct: 'function', file: 'app/book/actions.ts', module: 'app/book/actions.ts', purl: 'pkg:github/p/app' },
940
+ { alias: 'lib-a', name: 'a', construct: 'function', file: 'lib/a.ts', module: 'lib/a.ts', purl: 'pkg:github/p/app' },
941
+ { alias: 'lib-b', name: 'b', construct: 'function', file: 'lib/b.ts', module: 'lib/b.ts', purl: 'pkg:github/p/app' },
942
+ ];
943
+ const groups = buildBoundaryLayoutGroupsByPath(
944
+ { components: comps },
945
+ { showSingletonFrames: true },
946
+ );
947
+ const ids = new Set(groups.map((g) => g.id));
948
+ // `app` has a single child dir and no direct files → merged into `app/book`.
949
+ expect(ids.has(directoryGroupNodeId('app'))).toBe(false);
950
+ expect(ids.has(directoryGroupNodeId('app/book'))).toBe(true);
951
+ const appBook = groups.find((g) => g.id === directoryGroupNodeId('app/book'))!;
952
+ expect(appBook.parentId).toBeUndefined();
953
+ expect(appBook.region.label).toBe('app/book');
954
+ expect(ids.has(directoryGroupNodeId('lib'))).toBe(true);
955
+ });
956
+
957
+ test('exact mode adds no directory frames', () => {
958
+ const groups = buildBoundaryLayoutGroups(
959
+ { components: pathComps },
960
+ { showSingletonFrames: true, moduleNesting: 'exact' },
961
+ );
962
+ expect(groups.some((g) => g.region.kind === 'directory')).toBe(false);
963
+ });
964
+
965
+ test('path mode is selected by the moduleNesting option', () => {
966
+ const groups = buildBoundaryLayoutGroups(
967
+ { components: pathComps },
968
+ { showSingletonFrames: true, moduleNesting: 'path' },
969
+ );
970
+ expect(groups.some((g) => g.region.kind === 'directory')).toBe(true);
971
+ });
840
972
  });
841
973
 
842
974
  describe('describeConstructBreakdown', () => {
@@ -993,7 +1125,6 @@ describe('reorderTargetIndex', () => {
993
1125
  describe('graphify-native edges', () => {
994
1126
  const doc = {
995
1127
  components: comps,
996
- relations: [],
997
1128
  };
998
1129
  const graphify = [
999
1130
  { id: 'g1', from: 'reader', to: 'dst', relation: 'imports' },
@@ -1006,12 +1137,12 @@ describe('graphify-native edges', () => {
1006
1137
  expect(e.mechanism).toBe('imports');
1007
1138
  });
1008
1139
 
1009
- test('subsystem relations keep no graphify provenance', () => {
1140
+ test('walkthrough edges keep no graphify provenance', () => {
1010
1141
  const [e] = deriveGraphEdges({
1011
- relations: [{ id: 'r', from: 'reader', to: 'dst', relationType: 'method' }],
1142
+ walkthroughs: [walkthroughs[0]!],
1012
1143
  });
1013
1144
  expect(e.provenance).toBeUndefined();
1014
- expect(e.mechanism).toBe('method');
1145
+ expect(e.mechanism).toBe('calls');
1015
1146
  });
1016
1147
 
1017
1148
  test('graphify edges color from the separate palette', () => {
@@ -1019,7 +1150,7 @@ describe('graphify-native edges', () => {
1019
1150
  GRAPHIFY_RELATION_COLOR.imports,
1020
1151
  );
1021
1152
  expect(edgeColor({ mechanism: 'imports', provenance: 'graphify' })).not.toBe(
1022
- MECHANISM_COLOR.method,
1153
+ MECHANISM_COLOR.calls,
1023
1154
  );
1024
1155
  });
1025
1156
 
@@ -1030,7 +1161,7 @@ describe('graphify-native edges', () => {
1030
1161
  });
1031
1162
 
1032
1163
  test('subsystem mechanism color is unaffected by the graphify palette', () => {
1033
- expect(edgeColor({ mechanism: 'method' })).toBe(MECHANISM_COLOR.method);
1164
+ expect(edgeColor({ mechanism: 'calls' })).toBe(MECHANISM_COLOR.calls);
1034
1165
  });
1035
1166
 
1036
1167
  test('graphify edges all share the graphify stroke style', () => {