@principal-ai/subsystems-react 0.24.27 → 0.25.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.
- 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 +36 -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/ComponentGraph/ModuleBadges.stories.tsx +1 -1
- 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 +51 -51
- package/src/subsystem/model.ts +47 -41
- package/src/subsystem/nodes.tsx +6 -6
|
@@ -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' },
|
package/src/subsystem/model.ts
CHANGED
|
@@ -127,7 +127,13 @@ export type SubsystemEdgeView = 'relations' | 'walkthroughs';
|
|
|
127
127
|
|
|
128
128
|
/** A component node — the named unit, construct-tagged; `file` is its location. */
|
|
129
129
|
export interface SubsystemComponent {
|
|
130
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Model-local stable alias, unique per model. Referenced by relation /
|
|
132
|
+
* walkthrough `from` / `to`; edges point at the alias, not the location.
|
|
133
|
+
* Code identity (for composed multi-model views) lives on
|
|
134
|
+
* `purl` + `file` + `symbol`, not here.
|
|
135
|
+
*/
|
|
136
|
+
alias: string;
|
|
131
137
|
name: string;
|
|
132
138
|
/**
|
|
133
139
|
* The node's construct — what it IS as a declaration (class, function,
|
|
@@ -268,8 +274,8 @@ export interface SubsystemRelation {
|
|
|
268
274
|
*/
|
|
269
275
|
export interface SubsystemComponentEdge {
|
|
270
276
|
id: string;
|
|
271
|
-
from: string; // component
|
|
272
|
-
to: string; // component
|
|
277
|
+
from: string; // component alias
|
|
278
|
+
to: string; // component alias or external target label
|
|
273
279
|
mechanism: SubsystemEdgeMechanism;
|
|
274
280
|
/** Concrete file/symbol refs backing the edge (the seam). */
|
|
275
281
|
refs?: string[];
|
|
@@ -374,7 +380,7 @@ export function deriveGraphEdges(doc: {
|
|
|
374
380
|
* Those snapshots are a catalog of declarations, not a graph.
|
|
375
381
|
*/
|
|
376
382
|
export function isConstructsOnlyModel(doc: {
|
|
377
|
-
components: readonly {
|
|
383
|
+
components: readonly { alias: string }[];
|
|
378
384
|
relations?: readonly SubsystemRelation[];
|
|
379
385
|
walkthroughs?: readonly SubsystemWalkthrough[];
|
|
380
386
|
}): boolean {
|
|
@@ -473,7 +479,7 @@ export interface SubsystemProcessRegion {
|
|
|
473
479
|
/** Display label for the boundary frame. */
|
|
474
480
|
label: string;
|
|
475
481
|
/** Component ids that are members of this region. */
|
|
476
|
-
|
|
482
|
+
memberAliases: string[];
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
/** Options for which boundary frames are kept. */
|
|
@@ -598,14 +604,14 @@ export function getSubsystemRegions(
|
|
|
598
604
|
const p = c.process?.trim();
|
|
599
605
|
if (!p) continue;
|
|
600
606
|
const list = byProcess.get(p) ?? [];
|
|
601
|
-
list.push(c.
|
|
607
|
+
list.push(c.alias);
|
|
602
608
|
byProcess.set(p, list);
|
|
603
609
|
}
|
|
604
|
-
return [...byProcess.entries()].map(([key,
|
|
610
|
+
return [...byProcess.entries()].map(([key, memberAliases]) => ({
|
|
605
611
|
kind: 'process' as const,
|
|
606
612
|
key,
|
|
607
613
|
label: key,
|
|
608
|
-
|
|
614
|
+
memberAliases,
|
|
609
615
|
}));
|
|
610
616
|
}
|
|
611
617
|
|
|
@@ -621,14 +627,14 @@ export function getSubsystemModuleRegions(
|
|
|
621
627
|
const m = c.module?.trim();
|
|
622
628
|
if (!m) continue;
|
|
623
629
|
const list = byModule.get(m) ?? [];
|
|
624
|
-
list.push(c.
|
|
630
|
+
list.push(c.alias);
|
|
625
631
|
byModule.set(m, list);
|
|
626
632
|
}
|
|
627
|
-
return [...byModule.entries()].map(([key,
|
|
633
|
+
return [...byModule.entries()].map(([key, memberAliases]) => ({
|
|
628
634
|
kind: 'module' as const,
|
|
629
635
|
key,
|
|
630
636
|
label: key,
|
|
631
|
-
|
|
637
|
+
memberAliases,
|
|
632
638
|
}));
|
|
633
639
|
}
|
|
634
640
|
|
|
@@ -645,14 +651,14 @@ export function getSubsystemPackageRegions(
|
|
|
645
651
|
const key = componentPackageKey(c);
|
|
646
652
|
if (!key) continue;
|
|
647
653
|
const list = byPackage.get(key) ?? [];
|
|
648
|
-
list.push(c.
|
|
654
|
+
list.push(c.alias);
|
|
649
655
|
byPackage.set(key, list);
|
|
650
656
|
}
|
|
651
|
-
return [...byPackage.entries()].map(([key,
|
|
657
|
+
return [...byPackage.entries()].map(([key, memberAliases]) => ({
|
|
652
658
|
kind: 'package' as const,
|
|
653
659
|
key,
|
|
654
660
|
label: packageRegionLabel(key),
|
|
655
|
-
|
|
661
|
+
memberAliases,
|
|
656
662
|
}));
|
|
657
663
|
}
|
|
658
664
|
|
|
@@ -662,7 +668,7 @@ export function getSubsystemPackageRegions(
|
|
|
662
668
|
*/
|
|
663
669
|
export interface BoundaryLayoutGroup {
|
|
664
670
|
id: string;
|
|
665
|
-
|
|
671
|
+
memberAliases: string[];
|
|
666
672
|
/** When set, this group is a child of another boundary group. */
|
|
667
673
|
parentId?: string;
|
|
668
674
|
region: SubsystemProcessRegion;
|
|
@@ -672,7 +678,7 @@ function keepRegion(
|
|
|
672
678
|
r: SubsystemProcessRegion,
|
|
673
679
|
showSingletons: boolean,
|
|
674
680
|
): boolean {
|
|
675
|
-
return showSingletons || r.
|
|
681
|
+
return showSingletons || r.memberAliases.length >= 2;
|
|
676
682
|
}
|
|
677
683
|
|
|
678
684
|
/**
|
|
@@ -689,7 +695,7 @@ export function buildBoundaryLayoutGroups(
|
|
|
689
695
|
): BoundaryLayoutGroup[] {
|
|
690
696
|
const showSingletons = opts.showSingletonFrames === true;
|
|
691
697
|
const packageMode = opts.packageFrames ?? 'multi-repo';
|
|
692
|
-
const
|
|
698
|
+
const byAlias = new Map(doc.components.map((c) => [c.alias, c]));
|
|
693
699
|
|
|
694
700
|
const allPackages = getSubsystemPackageRegions(doc);
|
|
695
701
|
const packageEligible =
|
|
@@ -714,8 +720,8 @@ export function buildBoundaryLayoutGroups(
|
|
|
714
720
|
const moduleGroups: BoundaryLayoutGroup[] = modules.map((r) => {
|
|
715
721
|
const processesOfMembers = new Set<string>();
|
|
716
722
|
const packagesOfMembers = new Set<string>();
|
|
717
|
-
for (const
|
|
718
|
-
const c =
|
|
723
|
+
for (const alias of r.memberAliases) {
|
|
724
|
+
const c = byAlias.get(alias);
|
|
719
725
|
const p = c?.process?.trim();
|
|
720
726
|
if (p) processesOfMembers.add(p);
|
|
721
727
|
const pkg = c ? componentPackageKey(c) : undefined;
|
|
@@ -732,7 +738,7 @@ export function buildBoundaryLayoutGroups(
|
|
|
732
738
|
}
|
|
733
739
|
return {
|
|
734
740
|
id: moduleGroupNodeId(r.key),
|
|
735
|
-
|
|
741
|
+
memberAliases: [...r.memberAliases],
|
|
736
742
|
parentId,
|
|
737
743
|
region: r,
|
|
738
744
|
};
|
|
@@ -742,15 +748,15 @@ export function buildBoundaryLayoutGroups(
|
|
|
742
748
|
const processId = processGroupNodeId(r.key);
|
|
743
749
|
const nestedModules = moduleGroups.filter((m) => m.parentId === processId);
|
|
744
750
|
const nestedModuleKeys = new Set(nestedModules.map((m) => m.region.key));
|
|
745
|
-
const directLeaves = r.
|
|
746
|
-
const mod =
|
|
751
|
+
const directLeaves = r.memberAliases.filter((alias) => {
|
|
752
|
+
const mod = byAlias.get(alias)?.module?.trim();
|
|
747
753
|
if (!mod) return true;
|
|
748
754
|
return !nestedModuleKeys.has(mod);
|
|
749
755
|
});
|
|
750
756
|
|
|
751
757
|
const packagesOfMembers = new Set<string>();
|
|
752
|
-
for (const
|
|
753
|
-
const c =
|
|
758
|
+
for (const alias of r.memberAliases) {
|
|
759
|
+
const c = byAlias.get(alias);
|
|
754
760
|
const pkg = c ? componentPackageKey(c) : undefined;
|
|
755
761
|
if (pkg) packagesOfMembers.add(pkg);
|
|
756
762
|
}
|
|
@@ -762,7 +768,7 @@ export function buildBoundaryLayoutGroups(
|
|
|
762
768
|
|
|
763
769
|
return {
|
|
764
770
|
id: processId,
|
|
765
|
-
|
|
771
|
+
memberAliases: [...nestedModules.map((m) => m.id), ...directLeaves],
|
|
766
772
|
parentId,
|
|
767
773
|
region: r,
|
|
768
774
|
};
|
|
@@ -774,15 +780,15 @@ export function buildBoundaryLayoutGroups(
|
|
|
774
780
|
const nestedModules = moduleGroups.filter((m) => m.parentId === packageId);
|
|
775
781
|
const claimed = new Set<string>();
|
|
776
782
|
for (const p of nestedProcesses) {
|
|
777
|
-
for (const
|
|
783
|
+
for (const alias of p.region.memberAliases) claimed.add(alias);
|
|
778
784
|
}
|
|
779
785
|
for (const m of nestedModules) {
|
|
780
|
-
for (const
|
|
786
|
+
for (const alias of m.region.memberAliases) claimed.add(alias);
|
|
781
787
|
}
|
|
782
|
-
const directLeaves = r.
|
|
788
|
+
const directLeaves = r.memberAliases.filter((alias) => !claimed.has(alias));
|
|
783
789
|
return {
|
|
784
790
|
id: packageId,
|
|
785
|
-
|
|
791
|
+
memberAliases: [
|
|
786
792
|
...nestedProcesses.map((p) => p.id),
|
|
787
793
|
...nestedModules.map((m) => m.id),
|
|
788
794
|
...directLeaves,
|
|
@@ -1209,7 +1215,7 @@ export function convertSubsystemToNodes(
|
|
|
1209
1215
|
? processGroupNodeId(processKey)
|
|
1210
1216
|
: undefined;
|
|
1211
1217
|
nodes.push({
|
|
1212
|
-
id: c.
|
|
1218
|
+
id: c.alias,
|
|
1213
1219
|
type: 'subsystem-component',
|
|
1214
1220
|
...(parentId ? { parentId } : {}),
|
|
1215
1221
|
position: { x: PAD + col * COL_W, y: cursorY + row * ROW_H },
|
|
@@ -1245,18 +1251,18 @@ export function convertSubsystemToGroups(
|
|
|
1245
1251
|
|
|
1246
1252
|
/**
|
|
1247
1253
|
* Convert a subsystem graph document into React Flow edges. Edges whose target
|
|
1248
|
-
* is an external label (not a component
|
|
1254
|
+
* is an external label (not a component alias) point at a synthetic stub so the
|
|
1249
1255
|
* relationship is visible without a member node.
|
|
1250
1256
|
*/
|
|
1251
1257
|
export function convertSubsystemToEdges(doc: SubsystemModelDocument): SubsystemGraphEdge[] {
|
|
1252
|
-
const
|
|
1258
|
+
const compAliases = new Set(doc.components.map((c) => c.alias));
|
|
1253
1259
|
const edges: SubsystemGraphEdge[] = [];
|
|
1254
1260
|
|
|
1255
1261
|
for (const e of deriveGraphEdges(doc)) {
|
|
1256
1262
|
const color = MECHANISM_COLOR[e.mechanism];
|
|
1257
1263
|
const style = MECHANISM_STYLE[e.mechanism];
|
|
1258
1264
|
// If `to` is a real component, connect directly; otherwise point at a stub node.
|
|
1259
|
-
const isExternal = !
|
|
1265
|
+
const isExternal = !compAliases.has(e.to);
|
|
1260
1266
|
const targetId = isExternal ? `external:${e.to}` : e.to;
|
|
1261
1267
|
|
|
1262
1268
|
edges.push({
|
|
@@ -1282,8 +1288,8 @@ export function subsystemGraphLayoutKey(
|
|
|
1282
1288
|
doc: Pick<SubsystemModelDocument, 'components' | 'relations' | 'walkthroughs'>,
|
|
1283
1289
|
): string {
|
|
1284
1290
|
const components = doc.components
|
|
1285
|
-
.map(({
|
|
1286
|
-
[
|
|
1291
|
+
.map(({ alias, purl, name, symbol, construct, file, purpose, process, module }) =>
|
|
1292
|
+
[alias, purl, name, symbol ?? '', construct, file, purpose ?? '', process ?? '', module ?? ''].join('\0'))
|
|
1287
1293
|
.sort()
|
|
1288
1294
|
.join('\n');
|
|
1289
1295
|
const edgeKey = deriveGraphEdges(doc)
|
|
@@ -1326,7 +1332,7 @@ export async function buildSubsystemGraph(
|
|
|
1326
1332
|
const layoutGroups = buildBoundaryLayoutGroups(doc, frameOpts);
|
|
1327
1333
|
const regions = layoutGroups.map((g) => g.region);
|
|
1328
1334
|
const regionIds = new Set(layoutGroups.map((g) => g.id));
|
|
1329
|
-
const
|
|
1335
|
+
const byAlias = new Map(doc.components.map((c) => [c.alias, c]));
|
|
1330
1336
|
|
|
1331
1337
|
// Resolve leaf parentIds against kept frames: module → process → package.
|
|
1332
1338
|
// Stamp package parents here (not in convertSubsystemToNodes) so single-repo
|
|
@@ -1335,7 +1341,7 @@ export async function buildSubsystemGraph(
|
|
|
1335
1341
|
if (n.type !== 'subsystem-component') continue;
|
|
1336
1342
|
const parentId = (n as { parentId?: string }).parentId;
|
|
1337
1343
|
if (parentId && regionIds.has(parentId)) continue;
|
|
1338
|
-
const comp = (n.data as SubsystemGraphNodeData).component ??
|
|
1344
|
+
const comp = (n.data as SubsystemGraphNodeData).component ?? byAlias.get(n.id);
|
|
1339
1345
|
const processKey = comp?.process?.trim();
|
|
1340
1346
|
const processId = processKey ? processGroupNodeId(processKey) : undefined;
|
|
1341
1347
|
if (processId && regionIds.has(processId)) {
|
|
@@ -1353,10 +1359,10 @@ export async function buildSubsystemGraph(
|
|
|
1353
1359
|
|
|
1354
1360
|
// External edge targets that aren't real components → create stub nodes so
|
|
1355
1361
|
// cross-package edges have something to land on.
|
|
1356
|
-
const
|
|
1362
|
+
const realAliases = new Set(doc.components.map((c) => c.alias));
|
|
1357
1363
|
const externalIds: string[] = [];
|
|
1358
1364
|
for (const e of deriveGraphEdges(doc)) {
|
|
1359
|
-
if (!
|
|
1365
|
+
if (!realAliases.has(e.to)) {
|
|
1360
1366
|
const extId = `external:${e.to}`;
|
|
1361
1367
|
if (!externalIds.includes(extId)) externalIds.push(extId);
|
|
1362
1368
|
}
|
|
@@ -1372,7 +1378,7 @@ export async function buildSubsystemGraph(
|
|
|
1372
1378
|
height: 60,
|
|
1373
1379
|
data: {
|
|
1374
1380
|
component: {
|
|
1375
|
-
|
|
1381
|
+
alias: extId,
|
|
1376
1382
|
name: label,
|
|
1377
1383
|
construct: 'external',
|
|
1378
1384
|
purl: 'external',
|
|
@@ -1416,7 +1422,7 @@ export async function buildSubsystemGraph(
|
|
|
1416
1422
|
edgeLabels: showEdgeLabels === false ? { enabled: false } : { enabled: true, placement: 'CENTER' },
|
|
1417
1423
|
groups: layoutGroups.map((g) => ({
|
|
1418
1424
|
id: g.id,
|
|
1419
|
-
memberIds: g.
|
|
1425
|
+
memberIds: g.memberAliases,
|
|
1420
1426
|
parentId: g.parentId,
|
|
1421
1427
|
minWidth: g.region.kind === 'module' ? moduleMinWidthForBadge(g.region.label) : undefined,
|
|
1422
1428
|
})),
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -69,14 +69,14 @@ function breakWords(s: string): string {
|
|
|
69
69
|
|
|
70
70
|
export interface SubsystemGraphCallbacks {
|
|
71
71
|
/** Click a component — open its file/entry point. */
|
|
72
|
-
onSelect?: (
|
|
72
|
+
onSelect?: (componentAlias: string) => void;
|
|
73
73
|
/** Click the filename badge — open that component's file in the drawer
|
|
74
74
|
* (same path as clicking the file link in the declaration panel). */
|
|
75
|
-
onOpenFile?: (
|
|
75
|
+
onOpenFile?: (componentAlias: string) => void;
|
|
76
76
|
/** Click an edge (or its label) — select the relationship. */
|
|
77
77
|
onEdgeSelect?: (edgeId: string) => void;
|
|
78
78
|
/** Hover a component (null on leave) — associates it with the file tree. */
|
|
79
|
-
onHover?: (
|
|
79
|
+
onHover?: (componentAlias: string | null) => void;
|
|
80
80
|
/** Upper bound for node width; nodes grow with content up to this, then wrap. */
|
|
81
81
|
maxNodeWidth?: number;
|
|
82
82
|
}
|
|
@@ -131,7 +131,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
131
131
|
<div
|
|
132
132
|
onMouseEnter={() => {
|
|
133
133
|
setHover(true);
|
|
134
|
-
SUBSYSTEM_CALLBACKS.onHover?.(c.
|
|
134
|
+
SUBSYSTEM_CALLBACKS.onHover?.(c.alias);
|
|
135
135
|
}}
|
|
136
136
|
onMouseLeave={() => {
|
|
137
137
|
setHover(false);
|
|
@@ -139,7 +139,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
139
139
|
}}
|
|
140
140
|
onClick={(e) => {
|
|
141
141
|
e.stopPropagation();
|
|
142
|
-
SUBSYSTEM_CALLBACKS.onSelect?.(c.
|
|
142
|
+
SUBSYSTEM_CALLBACKS.onSelect?.(c.alias);
|
|
143
143
|
}}
|
|
144
144
|
style={{
|
|
145
145
|
position: 'relative',
|
|
@@ -275,7 +275,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
275
275
|
onClick={(e) => {
|
|
276
276
|
// Open the file directly; don't also toggle node selection.
|
|
277
277
|
e.stopPropagation();
|
|
278
|
-
SUBSYSTEM_CALLBACKS.onOpenFile?.(c.
|
|
278
|
+
SUBSYSTEM_CALLBACKS.onOpenFile?.(c.alias);
|
|
279
279
|
}}
|
|
280
280
|
>
|
|
281
281
|
{c.file.split('/').pop()}
|