@principal-ai/subsystems-react 0.24.27 → 0.25.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.
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +7 -7
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +1 -1
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +2 -2
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +62 -62
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +2 -2
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +27 -27
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +11 -5
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +65 -36
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +3 -3
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +3 -3
- package/dist/subsystem/nodes.js.map +1 -1
- package/package.json +1 -1
- package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +13 -13
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +17 -17
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +2 -2
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +8 -8
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +11 -11
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +10 -10
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +7 -7
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +17 -17
- package/src/stories/Subsystem/ComponentGraph/NodeAnatomy.stories.tsx +16 -16
- package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +12 -12
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +1 -1
- package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +10 -10
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +69 -69
- package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +27 -27
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +8 -8
- package/src/subsystem/ComponentDeclaration.tsx +2 -2
- package/src/subsystem/ConstructsCatalog.tsx +71 -75
- package/src/subsystem/SubsystemComponentGraph.tsx +28 -28
- package/src/subsystem/model.test.ts +97 -51
- package/src/subsystem/model.ts +77 -41
- package/src/subsystem/nodes.tsx +6 -6
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
applyNodeChanges,
|
|
36
36
|
} from '@xyflow/react';
|
|
37
37
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
38
|
-
import {
|
|
38
|
+
import { FileText, Pause, Play, X } from 'lucide-react';
|
|
39
39
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
40
40
|
import {
|
|
41
41
|
buildSubsystemGraph,
|
|
@@ -101,7 +101,7 @@ export interface SubsystemComponentGraphProps {
|
|
|
101
101
|
* (unselected ones dimmed); everything else is hidden.
|
|
102
102
|
*/
|
|
103
103
|
walkthroughs?: SubsystemWalkthrough[];
|
|
104
|
-
onSelect?: (
|
|
104
|
+
onSelect?: (componentAlias: string) => void;
|
|
105
105
|
/** Called when an edge is clicked (relationship / mechanism + refs seam). */
|
|
106
106
|
onEdgeSelect?: (edge: SubsystemComponentEdge) => void;
|
|
107
107
|
/** Upper bound for node width in px; nodes grow with content up to this,
|
|
@@ -217,7 +217,7 @@ export interface SubsystemComponentGraphProps {
|
|
|
217
217
|
*/
|
|
218
218
|
onFileSelect?: (file: string) => void;
|
|
219
219
|
/** Verify the selected component against graphify (declaration panel). */
|
|
220
|
-
onVerifyComponent?: (
|
|
220
|
+
onVerifyComponent?: (componentAlias: string) => void;
|
|
221
221
|
/** Live verification status for the selected component. */
|
|
222
222
|
componentVerification?: ComponentVerificationState | null;
|
|
223
223
|
}
|
|
@@ -335,7 +335,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
335
335
|
// Pending deferred fit after opening the drawer; cancelled on re-entry / unmount.
|
|
336
336
|
const pendingFocusFitRef = useRef<number | null>(null);
|
|
337
337
|
// Component the pointer is over (null on leave) → transient tree highlight.
|
|
338
|
-
const [
|
|
338
|
+
const [hoveredComponentAlias, setHoveredComponentId] = useState<string | null>(null);
|
|
339
339
|
// Walkthrough focus — selected flow (or step) is full strength; other
|
|
340
340
|
// opened-flow members stay visible but dimmed; everything else is hidden.
|
|
341
341
|
const [focusedWalkthroughId, setFocusedWalkthroughId] = useState<string | null>(null);
|
|
@@ -496,7 +496,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
496
496
|
// writes back declarationRef).
|
|
497
497
|
useEffect(() => {
|
|
498
498
|
if (!selected) return;
|
|
499
|
-
const fresh = components.find((c) => c.
|
|
499
|
+
const fresh = components.find((c) => c.alias === selected.alias);
|
|
500
500
|
if (fresh && fresh !== selected) setSelected(fresh);
|
|
501
501
|
}, [components, selected]);
|
|
502
502
|
|
|
@@ -646,12 +646,12 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
646
646
|
);
|
|
647
647
|
|
|
648
648
|
useEffect(() => {
|
|
649
|
-
SUBSYSTEM_CALLBACKS.onSelect = (
|
|
650
|
-
const comp = components.find((c) => c.
|
|
649
|
+
SUBSYSTEM_CALLBACKS.onSelect = (alias: string) => {
|
|
650
|
+
const comp = components.find((c) => c.alias === alias);
|
|
651
651
|
if (comp) {
|
|
652
652
|
// Clicking the already-selected node unselects it (toggle off).
|
|
653
653
|
// Selection is independent of the file drawer — nodes never open it.
|
|
654
|
-
if (selectedRef.current?.
|
|
654
|
+
if (selectedRef.current?.alias === comp.alias) {
|
|
655
655
|
setSelected(null);
|
|
656
656
|
setSelectedEdgeId(null);
|
|
657
657
|
return;
|
|
@@ -660,7 +660,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
660
660
|
setSelectedEdgeId(null);
|
|
661
661
|
setFocusedWalkthroughId(null);
|
|
662
662
|
setFocusedStepIndex(null);
|
|
663
|
-
onSelect?.(
|
|
663
|
+
onSelect?.(alias);
|
|
664
664
|
}
|
|
665
665
|
};
|
|
666
666
|
SUBSYSTEM_CALLBACKS.onEdgeSelect = selectEdge;
|
|
@@ -800,15 +800,15 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
800
800
|
// Boundary frames follow their members: hidden when no member is
|
|
801
801
|
// visible, dimmed when members are dimmed. Never selectable.
|
|
802
802
|
if (n.type === 'subsystem-group') {
|
|
803
|
-
const
|
|
803
|
+
const memberAliases = ((n.data as { region?: { memberAliases?: string[] } } | undefined)?.region?.memberAliases) ?? [];
|
|
804
804
|
const vis = flowElementVisibility({
|
|
805
|
-
inOpened:
|
|
806
|
-
inSelected:
|
|
805
|
+
inOpened: memberAliases.some((alias) => openedNodeIds?.has(alias) === true),
|
|
806
|
+
inSelected: memberAliases.some((alias) => brightNodeIds?.has(alias) === true),
|
|
807
807
|
anyOpened: openedNodeIds != null,
|
|
808
808
|
anySelected: brightNodeIds != null,
|
|
809
809
|
});
|
|
810
810
|
const dimmed = previewNodeIds
|
|
811
|
-
? vis.hidden || !
|
|
811
|
+
? vis.hidden || !memberAliases.some((alias) => previewNodeIds.has(alias))
|
|
812
812
|
: vis.dimmed;
|
|
813
813
|
const hidden = vis.hidden;
|
|
814
814
|
return {
|
|
@@ -823,7 +823,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
823
823
|
}
|
|
824
824
|
const comp = (n.data as { component?: SubsystemComponent } | undefined)?.component;
|
|
825
825
|
const fileMatch = fileMatchForNode(comp?.file, openFile, focusNodeIds?.has(n.id) === true);
|
|
826
|
-
const isSelected = selected?.
|
|
826
|
+
const isSelected = selected?.alias !== undefined && comp?.alias === selected.alias;
|
|
827
827
|
const vis = flowElementVisibility({
|
|
828
828
|
inOpened: openedNodeIds?.has(n.id) === true,
|
|
829
829
|
inSelected: brightNodeIds?.has(n.id) === true,
|
|
@@ -944,12 +944,12 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
944
944
|
if (node.type === 'subsystem-component' && comp) {
|
|
945
945
|
// Clicking the already-selected node unselects it (toggle off).
|
|
946
946
|
// Selection is independent of the file drawer — nodes never open it.
|
|
947
|
-
if (selected?.
|
|
947
|
+
if (selected?.alias === comp.alias) {
|
|
948
948
|
setSelected(null);
|
|
949
949
|
return;
|
|
950
950
|
}
|
|
951
951
|
setSelected(comp);
|
|
952
|
-
if (comp.
|
|
952
|
+
if (comp.alias) onSelect?.(comp.alias);
|
|
953
953
|
}
|
|
954
954
|
},
|
|
955
955
|
[onSelect, selected],
|
|
@@ -1384,8 +1384,8 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1384
1384
|
// Filename-badge clicks on nodes open the drawer through the same path as
|
|
1385
1385
|
// the declaration panel's file link (toggle + tree sync, no start line).
|
|
1386
1386
|
useEffect(() => {
|
|
1387
|
-
SUBSYSTEM_CALLBACKS.onOpenFile = (
|
|
1388
|
-
const comp = components.find((c) => c.
|
|
1387
|
+
SUBSYSTEM_CALLBACKS.onOpenFile = (componentAlias: string) => {
|
|
1388
|
+
const comp = components.find((c) => c.alias === componentAlias);
|
|
1389
1389
|
if (comp?.file) onOpenDeclarationFile(comp.file);
|
|
1390
1390
|
};
|
|
1391
1391
|
return () => {
|
|
@@ -1394,23 +1394,23 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1394
1394
|
}, [components, onOpenDeclarationFile]);
|
|
1395
1395
|
|
|
1396
1396
|
// Detail-panel links: related-name clicks select the matching component —
|
|
1397
|
-
// resolved by
|
|
1397
|
+
// resolved by alias, name, or symbol (call labels may carry a trailing `()`).
|
|
1398
1398
|
const resolveRelatedComponent = useCallback(
|
|
1399
1399
|
(ref: string) => {
|
|
1400
1400
|
const clean = ref.replace(/\(\)$/, '');
|
|
1401
1401
|
const comp = components.find(
|
|
1402
1402
|
(c) =>
|
|
1403
|
-
c.
|
|
1403
|
+
c.alias === clean ||
|
|
1404
1404
|
c.name === clean ||
|
|
1405
1405
|
c.symbol === clean ||
|
|
1406
1406
|
c.symbol?.replace(/\(\)$/, '') === clean,
|
|
1407
1407
|
);
|
|
1408
|
-
if (!comp || comp.
|
|
1408
|
+
if (!comp || comp.alias === selectedRef.current?.alias) return;
|
|
1409
1409
|
setSelected(comp);
|
|
1410
1410
|
setSelectedEdgeId(null);
|
|
1411
1411
|
setFocusedWalkthroughId(null);
|
|
1412
1412
|
setFocusedStepIndex(null);
|
|
1413
|
-
onSelect?.(comp.
|
|
1413
|
+
onSelect?.(comp.alias);
|
|
1414
1414
|
},
|
|
1415
1415
|
[components, onSelect],
|
|
1416
1416
|
);
|
|
@@ -1450,9 +1450,9 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1450
1450
|
|
|
1451
1451
|
// The hovered node's file (null when not hovering / file-less component).
|
|
1452
1452
|
const hoveredFile = useMemo(() => {
|
|
1453
|
-
if (!
|
|
1454
|
-
return components.find((c) => c.
|
|
1455
|
-
}, [components,
|
|
1453
|
+
if (!hoveredComponentAlias) return null;
|
|
1454
|
+
return components.find((c) => c.alias === hoveredComponentAlias)?.file ?? null;
|
|
1455
|
+
}, [components, hoveredComponentAlias]);
|
|
1456
1456
|
|
|
1457
1457
|
// Drawer content renderer: prefer the path-keyed viewer; fall back to the
|
|
1458
1458
|
// legacy component-keyed one via a file → first-component lookup.
|
|
@@ -1569,13 +1569,13 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1569
1569
|
padding: 0,
|
|
1570
1570
|
border: 'none',
|
|
1571
1571
|
borderRadius: 4,
|
|
1572
|
-
background: descToggleHover ? theme.colors.border : 'transparent',
|
|
1573
|
-
color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
|
|
1572
|
+
background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
|
|
1573
|
+
color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
|
|
1574
1574
|
cursor: 'pointer',
|
|
1575
1575
|
transition: 'background 120ms ease, color 120ms ease',
|
|
1576
1576
|
}}
|
|
1577
1577
|
>
|
|
1578
|
-
|
|
1578
|
+
<FileText size={14} />
|
|
1579
1579
|
</button>
|
|
1580
1580
|
)}
|
|
1581
1581
|
</div>
|
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
import type { SubsystemComponent, SubsystemComponentEdge } from './model';
|
|
33
33
|
|
|
34
34
|
const comps: SubsystemComponent[] = [
|
|
35
|
-
{
|
|
36
|
-
{
|
|
35
|
+
{ alias: 'reader', name: 'SessionReader', construct: 'class', file: 'SessionReader.ts', purl: 'pkg:github/principal-ai/agent-monitoring' },
|
|
36
|
+
{ alias: 'transcript', name: 'transcript', construct: 'function', file: 'transcript.ts', purl: 'pkg:github/principal-ai/agent-monitoring' },
|
|
37
37
|
];
|
|
38
38
|
|
|
39
39
|
const relations = [
|
|
@@ -63,7 +63,7 @@ describe('subsystem graph model', () => {
|
|
|
63
63
|
const withExternal: SubsystemComponent[] = [
|
|
64
64
|
...comps,
|
|
65
65
|
{
|
|
66
|
-
|
|
66
|
+
alias: "proposed-watcher",
|
|
67
67
|
name: "watchDir",
|
|
68
68
|
construct: "external",
|
|
69
69
|
// Intentionally omit file/purl — agents often leave these off for externals.
|
|
@@ -240,24 +240,24 @@ describe('subsystem graph model', () => {
|
|
|
240
240
|
test('getSubsystemRegions groups by process, skipping process-less nodes', () => {
|
|
241
241
|
const regions = getSubsystemRegions({
|
|
242
242
|
components: [
|
|
243
|
-
{
|
|
244
|
-
{
|
|
245
|
-
{
|
|
246
|
-
{
|
|
243
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
244
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
245
|
+
{ alias: 'c', name: 'c', construct: 'function', file: 'c.ts', purl: 'pkg:github/acme/app', process: 'app/renderer' },
|
|
246
|
+
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
247
247
|
],
|
|
248
248
|
});
|
|
249
249
|
expect(regions.map((r) => r.key)).toEqual(['app/host', 'app/renderer']);
|
|
250
250
|
expect(regions.every((r) => r.kind === 'process')).toBe(true);
|
|
251
|
-
expect(regions[0]!.
|
|
251
|
+
expect(regions[0]!.memberAliases).toEqual(['a', 'b']);
|
|
252
252
|
});
|
|
253
253
|
|
|
254
254
|
test('getSubsystemModuleRegions groups by module, skipping module-less nodes', () => {
|
|
255
255
|
const regions = getSubsystemModuleRegions({
|
|
256
256
|
components: [
|
|
257
|
-
{
|
|
258
|
-
{
|
|
259
|
-
{
|
|
260
|
-
{
|
|
257
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'transcript.ts', purl: 'pkg:github/acme/app', module: 'src/session/transcript.ts' },
|
|
258
|
+
{ alias: 'b', name: 'b', construct: 'type_alias', file: 'transcript.ts', purl: 'pkg:github/acme/app', module: 'src/session/transcript.ts' },
|
|
259
|
+
{ alias: 'c', name: 'c', construct: 'function', file: 'paths.ts', purl: 'pkg:github/acme/app', module: 'src/session/paths.ts' },
|
|
260
|
+
{ alias: 'd', name: 'd', construct: 'function', file: 'other.ts', purl: 'pkg:github/acme/app' },
|
|
261
261
|
],
|
|
262
262
|
});
|
|
263
263
|
expect(regions.map((r) => r.key)).toEqual([
|
|
@@ -265,14 +265,14 @@ describe('subsystem graph model', () => {
|
|
|
265
265
|
'src/session/paths.ts',
|
|
266
266
|
]);
|
|
267
267
|
expect(regions.every((r) => r.kind === 'module')).toBe(true);
|
|
268
|
-
expect(regions[0]!.
|
|
268
|
+
expect(regions[0]!.memberAliases).toEqual(['a', 'b']);
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
test('convertSubsystemToNodes prefers module parentId over process', () => {
|
|
272
272
|
const nodes = convertSubsystemToNodes({
|
|
273
273
|
components: [
|
|
274
274
|
{
|
|
275
|
-
|
|
275
|
+
alias: 'a',
|
|
276
276
|
name: 'a',
|
|
277
277
|
construct: 'function',
|
|
278
278
|
file: 'a.ts',
|
|
@@ -280,7 +280,7 @@ describe('subsystem graph model', () => {
|
|
|
280
280
|
process: 'app/host',
|
|
281
281
|
module: 'src/a.ts',
|
|
282
282
|
},
|
|
283
|
-
{
|
|
283
|
+
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
284
284
|
],
|
|
285
285
|
relations: [],
|
|
286
286
|
});
|
|
@@ -293,8 +293,8 @@ describe('subsystem graph model', () => {
|
|
|
293
293
|
test('convertSubsystemToNodes stamps parentId for process members only', () => {
|
|
294
294
|
const nodes = convertSubsystemToNodes({
|
|
295
295
|
components: [
|
|
296
|
-
{
|
|
297
|
-
{
|
|
296
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
297
|
+
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
298
298
|
],
|
|
299
299
|
relations: [],
|
|
300
300
|
});
|
|
@@ -307,8 +307,8 @@ describe('subsystem graph model', () => {
|
|
|
307
307
|
test('convertSubsystemToGroups emits one parent per process', () => {
|
|
308
308
|
const groups = convertSubsystemToGroups({
|
|
309
309
|
components: [
|
|
310
|
-
{
|
|
311
|
-
{
|
|
310
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
311
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
312
312
|
],
|
|
313
313
|
});
|
|
314
314
|
expect(groups).toHaveLength(1);
|
|
@@ -320,7 +320,7 @@ describe('subsystem graph model', () => {
|
|
|
320
320
|
const groups = convertSubsystemToGroups({
|
|
321
321
|
components: [
|
|
322
322
|
{
|
|
323
|
-
|
|
323
|
+
alias: 'a',
|
|
324
324
|
name: 'a',
|
|
325
325
|
construct: 'function',
|
|
326
326
|
file: 'a.ts',
|
|
@@ -329,7 +329,7 @@ describe('subsystem graph model', () => {
|
|
|
329
329
|
module: 'src/a.ts',
|
|
330
330
|
},
|
|
331
331
|
{
|
|
332
|
-
|
|
332
|
+
alias: 'b',
|
|
333
333
|
name: 'b',
|
|
334
334
|
construct: 'function',
|
|
335
335
|
file: 'a.ts',
|
|
@@ -352,7 +352,7 @@ describe('subsystem graph model', () => {
|
|
|
352
352
|
const { nodes, regions } = await buildSubsystemGraph({
|
|
353
353
|
components: [
|
|
354
354
|
{
|
|
355
|
-
|
|
355
|
+
alias: 'boot',
|
|
356
356
|
name: 'boot',
|
|
357
357
|
construct: 'function',
|
|
358
358
|
file: 'main.ts',
|
|
@@ -362,7 +362,7 @@ describe('subsystem graph model', () => {
|
|
|
362
362
|
symbol: 'boot',
|
|
363
363
|
},
|
|
364
364
|
{
|
|
365
|
-
|
|
365
|
+
alias: 'create',
|
|
366
366
|
name: 'createHost',
|
|
367
367
|
construct: 'function',
|
|
368
368
|
file: 'main.ts',
|
|
@@ -372,7 +372,7 @@ describe('subsystem graph model', () => {
|
|
|
372
372
|
symbol: 'createHost',
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
|
-
|
|
375
|
+
alias: 'write',
|
|
376
376
|
name: 'writeSession',
|
|
377
377
|
construct: 'function',
|
|
378
378
|
file: 'store.ts',
|
|
@@ -382,7 +382,7 @@ describe('subsystem graph model', () => {
|
|
|
382
382
|
symbol: 'writeSession',
|
|
383
383
|
},
|
|
384
384
|
{
|
|
385
|
-
|
|
385
|
+
alias: 'store',
|
|
386
386
|
name: 'SessionStore',
|
|
387
387
|
construct: 'store',
|
|
388
388
|
file: 'store.ts',
|
|
@@ -416,9 +416,9 @@ describe('subsystem graph model', () => {
|
|
|
416
416
|
test('buildSubsystemGraph drops singleton process frames (no parentId, no group)', async () => {
|
|
417
417
|
const { nodes, regions } = await buildSubsystemGraph({
|
|
418
418
|
components: [
|
|
419
|
-
{
|
|
420
|
-
{
|
|
421
|
-
{
|
|
419
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
420
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
421
|
+
{ alias: 'solo', name: 'solo', construct: 'function', file: 's.ts', purl: 'pkg:github/acme/app', process: 'app/lonely' },
|
|
422
422
|
],
|
|
423
423
|
relations: [],
|
|
424
424
|
walkthroughs: [{
|
|
@@ -436,7 +436,7 @@ describe('subsystem graph model', () => {
|
|
|
436
436
|
const { nodes, regions } = await buildSubsystemGraph({
|
|
437
437
|
components: [
|
|
438
438
|
{
|
|
439
|
-
|
|
439
|
+
alias: 'rec',
|
|
440
440
|
name: 'CodexRolloutRecord',
|
|
441
441
|
construct: 'type_alias',
|
|
442
442
|
file: 'transcript.ts',
|
|
@@ -445,7 +445,7 @@ describe('subsystem graph model', () => {
|
|
|
445
445
|
symbol: 'CodexRolloutRecord',
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
|
-
|
|
448
|
+
alias: 'parse',
|
|
449
449
|
name: 'parseTranscript',
|
|
450
450
|
construct: 'function',
|
|
451
451
|
file: 'transcript.ts',
|
|
@@ -454,7 +454,7 @@ describe('subsystem graph model', () => {
|
|
|
454
454
|
symbol: 'parseTranscript',
|
|
455
455
|
},
|
|
456
456
|
{
|
|
457
|
-
|
|
457
|
+
alias: 'solo',
|
|
458
458
|
name: 'lonely',
|
|
459
459
|
construct: 'function',
|
|
460
460
|
file: 'solo.ts',
|
|
@@ -477,10 +477,10 @@ describe('subsystem graph model', () => {
|
|
|
477
477
|
test('getSubsystemPackageRegions groups by purl repo key, skipping externals', () => {
|
|
478
478
|
const regions = getSubsystemPackageRegions({
|
|
479
479
|
components: [
|
|
480
|
-
{
|
|
481
|
-
{
|
|
482
|
-
{
|
|
483
|
-
{
|
|
480
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app#a.ts' },
|
|
481
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app' },
|
|
482
|
+
{ alias: 'c', name: 'c', construct: 'function', file: 'c.ts', purl: 'pkg:github/other/lib' },
|
|
483
|
+
{ alias: 'ext', name: 'Stripe', construct: 'external', file: '', purl: 'pkg:npm/stripe' },
|
|
484
484
|
],
|
|
485
485
|
});
|
|
486
486
|
expect(regions.map((r) => r.key).sort()).toEqual([
|
|
@@ -494,8 +494,8 @@ describe('subsystem graph model', () => {
|
|
|
494
494
|
test('buildBoundaryLayoutGroups skips package frames for single-repo graphs', () => {
|
|
495
495
|
const groups = buildBoundaryLayoutGroups({
|
|
496
496
|
components: [
|
|
497
|
-
{
|
|
498
|
-
{
|
|
497
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app' },
|
|
498
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app' },
|
|
499
499
|
],
|
|
500
500
|
});
|
|
501
501
|
expect(groups.filter((g) => g.region.kind === 'package')).toHaveLength(0);
|
|
@@ -504,10 +504,10 @@ describe('subsystem graph model', () => {
|
|
|
504
504
|
test('buildBoundaryLayoutGroups frames packages when multi-repo', () => {
|
|
505
505
|
const groups = buildBoundaryLayoutGroups({
|
|
506
506
|
components: [
|
|
507
|
-
{
|
|
508
|
-
{
|
|
509
|
-
{
|
|
510
|
-
{
|
|
507
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app' },
|
|
508
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app' },
|
|
509
|
+
{ alias: 'c', name: 'c', construct: 'function', file: 'c.ts', purl: 'pkg:github/other/lib' },
|
|
510
|
+
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/other/lib' },
|
|
511
511
|
],
|
|
512
512
|
});
|
|
513
513
|
const pkgs = groups.filter((g) => g.region.kind === 'package');
|
|
@@ -521,7 +521,7 @@ describe('subsystem graph model', () => {
|
|
|
521
521
|
const groups = buildBoundaryLayoutGroups({
|
|
522
522
|
components: [
|
|
523
523
|
{
|
|
524
|
-
|
|
524
|
+
alias: 'a',
|
|
525
525
|
name: 'a',
|
|
526
526
|
construct: 'function',
|
|
527
527
|
file: 'a.ts',
|
|
@@ -529,7 +529,7 @@ describe('subsystem graph model', () => {
|
|
|
529
529
|
process: 'app/host',
|
|
530
530
|
},
|
|
531
531
|
{
|
|
532
|
-
|
|
532
|
+
alias: 'b',
|
|
533
533
|
name: 'b',
|
|
534
534
|
construct: 'function',
|
|
535
535
|
file: 'b.ts',
|
|
@@ -537,7 +537,7 @@ describe('subsystem graph model', () => {
|
|
|
537
537
|
process: 'app/host',
|
|
538
538
|
},
|
|
539
539
|
{
|
|
540
|
-
|
|
540
|
+
alias: 'c',
|
|
541
541
|
name: 'c',
|
|
542
542
|
construct: 'function',
|
|
543
543
|
file: 'c.ts',
|
|
@@ -545,7 +545,7 @@ describe('subsystem graph model', () => {
|
|
|
545
545
|
process: 'lib/worker',
|
|
546
546
|
},
|
|
547
547
|
{
|
|
548
|
-
|
|
548
|
+
alias: 'd',
|
|
549
549
|
name: 'd',
|
|
550
550
|
construct: 'function',
|
|
551
551
|
file: 'd.ts',
|
|
@@ -562,7 +562,7 @@ describe('subsystem graph model', () => {
|
|
|
562
562
|
const { nodes } = await buildSubsystemGraph({
|
|
563
563
|
components: [
|
|
564
564
|
{
|
|
565
|
-
|
|
565
|
+
alias: 'a1',
|
|
566
566
|
name: 'a1',
|
|
567
567
|
construct: 'function',
|
|
568
568
|
file: 'a.ts',
|
|
@@ -571,7 +571,7 @@ describe('subsystem graph model', () => {
|
|
|
571
571
|
module: 'src/a.ts',
|
|
572
572
|
},
|
|
573
573
|
{
|
|
574
|
-
|
|
574
|
+
alias: 'a2',
|
|
575
575
|
name: 'a2',
|
|
576
576
|
construct: 'function',
|
|
577
577
|
file: 'a.ts',
|
|
@@ -580,7 +580,7 @@ describe('subsystem graph model', () => {
|
|
|
580
580
|
module: 'src/a.ts',
|
|
581
581
|
},
|
|
582
582
|
{
|
|
583
|
-
|
|
583
|
+
alias: 'b1',
|
|
584
584
|
name: 'b1',
|
|
585
585
|
construct: 'function',
|
|
586
586
|
file: 'b.ts',
|
|
@@ -589,7 +589,7 @@ describe('subsystem graph model', () => {
|
|
|
589
589
|
module: 'src/b.ts',
|
|
590
590
|
},
|
|
591
591
|
{
|
|
592
|
-
|
|
592
|
+
alias: 'b2',
|
|
593
593
|
name: 'b2',
|
|
594
594
|
construct: 'function',
|
|
595
595
|
file: 'b.ts',
|
|
@@ -617,8 +617,8 @@ describe('subsystem graph model', () => {
|
|
|
617
617
|
const groups = buildBoundaryLayoutGroups(
|
|
618
618
|
{
|
|
619
619
|
components: [
|
|
620
|
-
{
|
|
621
|
-
{
|
|
620
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app' },
|
|
621
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app' },
|
|
622
622
|
],
|
|
623
623
|
},
|
|
624
624
|
{ packageFrames: 'always' },
|
|
@@ -626,6 +626,52 @@ describe('subsystem graph model', () => {
|
|
|
626
626
|
expect(groups.filter((g) => g.region.kind === 'package')).toHaveLength(1);
|
|
627
627
|
});
|
|
628
628
|
|
|
629
|
+
test('mixed-process module members live only in the module frame', () => {
|
|
630
|
+
// Same file framed under two processes (composed-model vocabulary drift):
|
|
631
|
+
// the module cannot nest, so its members must not also sit directly in
|
|
632
|
+
// the process frames — ELK throws on double-parented leaves.
|
|
633
|
+
const groups = buildBoundaryLayoutGroups({
|
|
634
|
+
components: [
|
|
635
|
+
{ alias: 'a1', name: 'a1', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', module: 'src/a.ts', process: 'app/host' },
|
|
636
|
+
{ alias: 'a2', name: 'a2', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', module: 'src/a.ts', process: 'app/renderer' },
|
|
637
|
+
{ alias: 'b1', name: 'b1', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
638
|
+
],
|
|
639
|
+
});
|
|
640
|
+
const leafHits = new Map<string, string[]>();
|
|
641
|
+
for (const g of groups) {
|
|
642
|
+
for (const id of g.memberAliases) {
|
|
643
|
+
// Nested group ids legitimately appear in parents; only leaves count.
|
|
644
|
+
if (groups.some((h) => h.id === id)) continue;
|
|
645
|
+
leafHits.set(id, [...(leafHits.get(id) ?? []), g.id]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
for (const [leaf, owners] of leafHits) {
|
|
649
|
+
expect(`${leaf} in ${owners.length} groups: ${owners.join(',')}`).toBe(`${leaf} in 1 groups: ${owners[0]}`);
|
|
650
|
+
}
|
|
651
|
+
const mod = groups.find((g) => g.region.kind === 'module');
|
|
652
|
+
expect(mod?.memberAliases).toEqual(['a1', 'a2']);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('module-less leaf in a mixed-package process lives only in the process frame', () => {
|
|
656
|
+
const groups = buildBoundaryLayoutGroups({
|
|
657
|
+
components: [
|
|
658
|
+
{ alias: 'x', name: 'x', construct: 'function', file: 'x.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
659
|
+
{ alias: 'y', name: 'y', construct: 'function', file: 'y.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
660
|
+
{ alias: 'z', name: 'z', construct: 'function', file: 'z.ts', purl: 'pkg:github/other/lib', process: 'app/host' },
|
|
661
|
+
],
|
|
662
|
+
});
|
|
663
|
+
const leafHits = new Map<string, string[]>();
|
|
664
|
+
for (const g of groups) {
|
|
665
|
+
for (const id of g.memberAliases) {
|
|
666
|
+
if (groups.some((h) => h.id === id)) continue;
|
|
667
|
+
leafHits.set(id, [...(leafHits.get(id) ?? []), g.id]);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
for (const [leaf, owners] of leafHits) {
|
|
671
|
+
expect(owners).toHaveLength(1);
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
|
|
629
675
|
test('subsystemGraphLayoutKey ignores declarationRef-only changes', () => {
|
|
630
676
|
const base = doc;
|
|
631
677
|
const withRef = {
|