@principal-ai/subsystems-react 0.42.0 → 0.43.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/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js +1 -1
- package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +6 -9
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +15 -50
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +2 -3
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +2 -3
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +5 -5
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +9 -18
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -12
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +30 -110
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +18 -51
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +2 -51
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/toC4.d.ts +4 -4
- package/dist/subsystem/toC4.d.ts.map +1 -1
- package/dist/subsystem/toC4.js +3 -7
- package/dist/subsystem/toC4.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +0 -4
- package/src/stories/Subsystem/AgentsPanel.stories.tsx +3 -5
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +1 -4
- package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -4
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +19 -19
- package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +0 -1
- package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +0 -2
- package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +2 -6
- package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +21 -21
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +0 -3
- package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +30 -32
- package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +1 -26
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +3 -25
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +7 -8
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +9 -11
- package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +3 -6
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +8 -6
- package/src/stories/Subsystem/ComponentGraph/ProposedWalkthroughs.stories.tsx +0 -1
- package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +0 -1
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +26 -28
- package/src/stories/Subsystem/ComponentGraph/WalkthroughAutoplay.stories.tsx +0 -1
- package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +2 -3
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +18 -58
- package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +0 -2
- package/src/subsystem/C4Graph.tsx +2 -3
- package/src/subsystem/IssueList.tsx +12 -22
- package/src/subsystem/SubsystemComponentGraph.tsx +38 -132
- package/src/subsystem/model.test.ts +37 -24
- package/src/subsystem/model.ts +18 -101
- package/src/subsystem/toC4.test.ts +5 -12
- package/src/subsystem/toC4.ts +5 -9
|
@@ -48,13 +48,35 @@ const comps: SubsystemComponent[] = [
|
|
|
48
48
|
{ alias: 'transcript', name: 'transcript', construct: 'function', file: 'transcript.ts', purl: 'pkg:github/principal-ai/agent-monitoring' },
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
// 'host' is NOT a component — this is the cross-package external case.
|
|
52
|
+
const walkthroughs: SubsystemWalkthrough[] = [
|
|
53
|
+
{
|
|
54
|
+
id: 'wt',
|
|
55
|
+
title: 'flow',
|
|
56
|
+
steps: [
|
|
57
|
+
{
|
|
58
|
+
from: 'transcript',
|
|
59
|
+
to: 'reader',
|
|
60
|
+
mechanism: 'calls',
|
|
61
|
+
file: 'transcript.ts',
|
|
62
|
+
line: 1,
|
|
63
|
+
purl: 'pkg:github/principal-ai/agent-monitoring#transcript.ts',
|
|
64
|
+
symbol: 'transcript',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
from: 'reader',
|
|
68
|
+
to: 'host',
|
|
69
|
+
mechanism: 'calls',
|
|
70
|
+
file: 'SessionReader.ts',
|
|
71
|
+
line: 1,
|
|
72
|
+
purl: 'pkg:github/principal-ai/agent-monitoring#SessionReader.ts',
|
|
73
|
+
symbol: 'SessionReader',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
55
77
|
];
|
|
56
78
|
|
|
57
|
-
const doc = { components: comps,
|
|
79
|
+
const doc = { components: comps, walkthroughs };
|
|
58
80
|
|
|
59
81
|
describe('subsystem graph model', () => {
|
|
60
82
|
test('converts all components to flat component nodes', () => {
|
|
@@ -85,7 +107,6 @@ describe('subsystem graph model', () => {
|
|
|
85
107
|
];
|
|
86
108
|
const { nodes } = await buildSubsystemGraph({
|
|
87
109
|
components: withExternal,
|
|
88
|
-
relations: [],
|
|
89
110
|
walkthroughs: [{
|
|
90
111
|
id: 'w1',
|
|
91
112
|
title: 'feed',
|
|
@@ -294,7 +315,6 @@ describe('subsystem graph model', () => {
|
|
|
294
315
|
},
|
|
295
316
|
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
296
317
|
],
|
|
297
|
-
relations: [],
|
|
298
318
|
});
|
|
299
319
|
expect((nodes.find((n) => n.id === 'a') as { parentId?: string }).parentId).toBe(
|
|
300
320
|
moduleGroupNodeId('src/a.ts'),
|
|
@@ -308,7 +328,6 @@ describe('subsystem graph model', () => {
|
|
|
308
328
|
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
309
329
|
{ alias: 'd', name: 'd', construct: 'function', file: 'd.ts', purl: 'pkg:github/acme/app' },
|
|
310
330
|
],
|
|
311
|
-
relations: [],
|
|
312
331
|
});
|
|
313
332
|
expect((nodes.find((n) => n.id === 'a') as { parentId?: string }).parentId).toBe(
|
|
314
333
|
processGroupNodeId('app/host'),
|
|
@@ -404,7 +423,6 @@ describe('subsystem graph model', () => {
|
|
|
404
423
|
symbol: 'SessionStore',
|
|
405
424
|
},
|
|
406
425
|
],
|
|
407
|
-
relations: [],
|
|
408
426
|
});
|
|
409
427
|
expect(regions.map((r) => r.key).sort()).toEqual(
|
|
410
428
|
['app/host', 'src/main.ts', 'src/store.ts'].sort(),
|
|
@@ -432,7 +450,6 @@ describe('subsystem graph model', () => {
|
|
|
432
450
|
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
433
451
|
{ alias: 'solo', name: 'solo', construct: 'function', file: 's.ts', purl: 'pkg:github/acme/app', process: 'app/lonely' },
|
|
434
452
|
],
|
|
435
|
-
relations: [],
|
|
436
453
|
walkthroughs: [{
|
|
437
454
|
id: 'w1',
|
|
438
455
|
title: 'call',
|
|
@@ -566,7 +583,6 @@ describe('subsystem graph model', () => {
|
|
|
566
583
|
symbol: 'lonely',
|
|
567
584
|
},
|
|
568
585
|
],
|
|
569
|
-
relations: [],
|
|
570
586
|
});
|
|
571
587
|
expect(regions.map((r) => r.key)).toEqual(['src/session/transcript.ts']);
|
|
572
588
|
expect(regions[0]!.kind).toBe('module');
|
|
@@ -701,7 +717,6 @@ describe('subsystem graph model', () => {
|
|
|
701
717
|
module: 'src/b.ts',
|
|
702
718
|
},
|
|
703
719
|
],
|
|
704
|
-
relations: [],
|
|
705
720
|
});
|
|
706
721
|
const pkgA = nodes.find((n) => n.id === packageGroupNodeId('pkg:github/acme/app'));
|
|
707
722
|
const proc = nodes.find((n) => n.id === processGroupNodeId('app/host'));
|
|
@@ -791,7 +806,7 @@ describe('subsystem graph model', () => {
|
|
|
791
806
|
}
|
|
792
807
|
: c,
|
|
793
808
|
),
|
|
794
|
-
|
|
809
|
+
walkthroughs,
|
|
795
810
|
};
|
|
796
811
|
expect(subsystemGraphLayoutKey(base)).toBe(subsystemGraphLayoutKey(withRef));
|
|
797
812
|
});
|
|
@@ -799,7 +814,7 @@ describe('subsystem graph model', () => {
|
|
|
799
814
|
|
|
800
815
|
describe('isConstructsOnlyModel', () => {
|
|
801
816
|
test('true when components exist and there are no edges', () => {
|
|
802
|
-
expect(isConstructsOnlyModel({ components: comps,
|
|
817
|
+
expect(isConstructsOnlyModel({ components: comps, walkthroughs: [] })).toBe(true);
|
|
803
818
|
expect(isConstructsOnlyModel({ components: comps })).toBe(true);
|
|
804
819
|
});
|
|
805
820
|
|
|
@@ -812,13 +827,12 @@ describe('isConstructsOnlyModel', () => {
|
|
|
812
827
|
).toBe(false);
|
|
813
828
|
});
|
|
814
829
|
|
|
815
|
-
test('false when empty, or when
|
|
816
|
-
expect(isConstructsOnlyModel({ components: []
|
|
817
|
-
expect(isConstructsOnlyModel({ components: comps,
|
|
830
|
+
test('false when empty, or when walkthrough hops exist', () => {
|
|
831
|
+
expect(isConstructsOnlyModel({ components: [] })).toBe(false);
|
|
832
|
+
expect(isConstructsOnlyModel({ components: comps, walkthroughs })).toBe(false);
|
|
818
833
|
expect(
|
|
819
834
|
isConstructsOnlyModel({
|
|
820
835
|
components: comps,
|
|
821
|
-
relations: [],
|
|
822
836
|
walkthroughs: [
|
|
823
837
|
{
|
|
824
838
|
id: 'wt',
|
|
@@ -993,7 +1007,6 @@ describe('reorderTargetIndex', () => {
|
|
|
993
1007
|
describe('graphify-native edges', () => {
|
|
994
1008
|
const doc = {
|
|
995
1009
|
components: comps,
|
|
996
|
-
relations: [],
|
|
997
1010
|
};
|
|
998
1011
|
const graphify = [
|
|
999
1012
|
{ id: 'g1', from: 'reader', to: 'dst', relation: 'imports' },
|
|
@@ -1006,12 +1019,12 @@ describe('graphify-native edges', () => {
|
|
|
1006
1019
|
expect(e.mechanism).toBe('imports');
|
|
1007
1020
|
});
|
|
1008
1021
|
|
|
1009
|
-
test('
|
|
1022
|
+
test('walkthrough edges keep no graphify provenance', () => {
|
|
1010
1023
|
const [e] = deriveGraphEdges({
|
|
1011
|
-
|
|
1024
|
+
walkthroughs: [walkthroughs[0]!],
|
|
1012
1025
|
});
|
|
1013
1026
|
expect(e.provenance).toBeUndefined();
|
|
1014
|
-
expect(e.mechanism).toBe('
|
|
1027
|
+
expect(e.mechanism).toBe('calls');
|
|
1015
1028
|
});
|
|
1016
1029
|
|
|
1017
1030
|
test('graphify edges color from the separate palette', () => {
|
|
@@ -1019,7 +1032,7 @@ describe('graphify-native edges', () => {
|
|
|
1019
1032
|
GRAPHIFY_RELATION_COLOR.imports,
|
|
1020
1033
|
);
|
|
1021
1034
|
expect(edgeColor({ mechanism: 'imports', provenance: 'graphify' })).not.toBe(
|
|
1022
|
-
MECHANISM_COLOR.
|
|
1035
|
+
MECHANISM_COLOR.calls,
|
|
1023
1036
|
);
|
|
1024
1037
|
});
|
|
1025
1038
|
|
|
@@ -1030,7 +1043,7 @@ describe('graphify-native edges', () => {
|
|
|
1030
1043
|
});
|
|
1031
1044
|
|
|
1032
1045
|
test('subsystem mechanism color is unaffected by the graphify palette', () => {
|
|
1033
|
-
expect(edgeColor({ mechanism: '
|
|
1046
|
+
expect(edgeColor({ mechanism: 'calls' })).toBe(MECHANISM_COLOR.calls);
|
|
1034
1047
|
});
|
|
1035
1048
|
|
|
1036
1049
|
test('graphify edges all share the graphify stroke style', () => {
|
package/src/subsystem/model.ts
CHANGED
|
@@ -108,17 +108,6 @@ export interface SubsystemDeclToken {
|
|
|
108
108
|
color?: string;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/**
|
|
112
|
-
* Topology relation type — structural / module / type claims.
|
|
113
|
-
* Belongs on `relations[]`, not on walkthrough hops.
|
|
114
|
-
*/
|
|
115
|
-
export type SubsystemRelationType =
|
|
116
|
-
| 'extends'
|
|
117
|
-
| 'inherits'
|
|
118
|
-
| 'implements'
|
|
119
|
-
| 'mixes_in'
|
|
120
|
-
| 'method';
|
|
121
|
-
|
|
122
111
|
/**
|
|
123
112
|
* Walkthrough hop mechanism — runtime seams with a `file:line` site.
|
|
124
113
|
*/
|
|
@@ -132,24 +121,21 @@ export type SubsystemWalkthroughMechanism =
|
|
|
132
121
|
| 'watches'
|
|
133
122
|
| 'registers-into';
|
|
134
123
|
|
|
135
|
-
/** Union for derived graph-edge styling
|
|
136
|
-
export type SubsystemEdgeMechanism =
|
|
137
|
-
| SubsystemRelationType
|
|
138
|
-
| SubsystemWalkthroughMechanism;
|
|
124
|
+
/** Union for derived graph-edge styling — the walkthrough hop mechanism. */
|
|
125
|
+
export type SubsystemEdgeMechanism = SubsystemWalkthroughMechanism;
|
|
139
126
|
|
|
140
127
|
/**
|
|
141
|
-
* Which edge
|
|
142
|
-
*
|
|
143
|
-
* - `
|
|
128
|
+
* Which edge source the canvas draws. The two sources are disjoint; a view
|
|
129
|
+
* shows only edges (and their labels) from the selected source.
|
|
130
|
+
* - `graphify`: only graphify-native static edges (`imports`, `contains`, …)
|
|
144
131
|
* - `walkthroughs`: only walkthrough hop edges (`calls`, `feeds`, …)
|
|
145
132
|
*/
|
|
146
|
-
export type SubsystemEdgeView = '
|
|
133
|
+
export type SubsystemEdgeView = 'graphify' | 'walkthroughs';
|
|
147
134
|
|
|
148
135
|
/**
|
|
149
136
|
* Where a display edge came from.
|
|
150
137
|
*
|
|
151
|
-
* - `subsystem` (default): a
|
|
152
|
-
* (`SubsystemRelationType` / `SubsystemWalkthroughMechanism`), colored from
|
|
138
|
+
* - `subsystem` (default): a walkthrough hop mechanism verb, colored from
|
|
153
139
|
* `MECHANISM_COLOR`.
|
|
154
140
|
* - `graphify`: a raw relation read off graphify's static symbol graph
|
|
155
141
|
* (`imports`, `contains`, `re_exports`, …). These are DERIVED, never authored,
|
|
@@ -162,10 +148,8 @@ export type SubsystemEdgeProvenance = 'subsystem' | 'graphify';
|
|
|
162
148
|
* A graphify-native topology edge — a raw graphify relation that has no
|
|
163
149
|
* subsystem mechanism equivalent.
|
|
164
150
|
*
|
|
165
|
-
*
|
|
166
|
-
* a
|
|
167
|
-
* derived from a graphify run and carry graphify's own (open) verb set. They are
|
|
168
|
-
* a display input only — never written back into a `SubsystemModelDocument`.
|
|
151
|
+
* Derived from a graphify run and carry graphify's own (open) verb set. They
|
|
152
|
+
* are a display input only — never written back into a `SubsystemModelDocument`.
|
|
169
153
|
*/
|
|
170
154
|
export interface SubsystemGraphifyRelation {
|
|
171
155
|
id: string;
|
|
@@ -192,8 +176,8 @@ export interface SubsystemGraphifyRelation {
|
|
|
192
176
|
/** A component node — the named unit, construct-tagged; `file` is its location. */
|
|
193
177
|
export interface SubsystemComponent {
|
|
194
178
|
/**
|
|
195
|
-
* Model-local stable alias, unique per model. Referenced by
|
|
196
|
-
*
|
|
179
|
+
* Model-local stable alias, unique per model. Referenced by walkthrough
|
|
180
|
+
* `from` / `to`; edges point at the alias, not the location.
|
|
197
181
|
* Code identity (for composed multi-model views) lives on
|
|
198
182
|
* `purl` + `file` + `symbol`, not here.
|
|
199
183
|
*/
|
|
@@ -330,19 +314,9 @@ export interface SubsystemComponent {
|
|
|
330
314
|
declarationRef?: SubsystemDeclarationRef;
|
|
331
315
|
}
|
|
332
316
|
|
|
333
|
-
/** A topology relation between components (structural / module / type). */
|
|
334
|
-
export interface SubsystemRelation {
|
|
335
|
-
id: string;
|
|
336
|
-
from: string;
|
|
337
|
-
to: string;
|
|
338
|
-
relationType: SubsystemRelationType;
|
|
339
|
-
/** Concrete file/symbol refs backing the relation. */
|
|
340
|
-
refs?: string[];
|
|
341
|
-
}
|
|
342
|
-
|
|
343
317
|
/**
|
|
344
|
-
* Derived / display graph edge used by renderers. Built from
|
|
345
|
-
* and
|
|
318
|
+
* Derived / display graph edge used by renderers. Built from walkthrough hops
|
|
319
|
+
* (and graphify-native relations) — not authored as its own document field.
|
|
346
320
|
*/
|
|
347
321
|
export interface SubsystemComponentEdge {
|
|
348
322
|
id: string;
|
|
@@ -352,8 +326,8 @@ export interface SubsystemComponentEdge {
|
|
|
352
326
|
* The edge verb. For `provenance: 'subsystem'` (the default) this is a
|
|
353
327
|
* `SubsystemEdgeMechanism`; for `provenance: 'graphify'` it is the raw
|
|
354
328
|
* graphify relation. Typed as `string` because the display edge is a derived
|
|
355
|
-
* structure and graphify's verb set is open — the
|
|
356
|
-
* (`
|
|
329
|
+
* structure and graphify's verb set is open — the walkthrough vocabulary
|
|
330
|
+
* (`SubsystemWalkthroughMechanism`) stays closed.
|
|
357
331
|
*/
|
|
358
332
|
mechanism: string;
|
|
359
333
|
/** Origin of the edge; absent means `'subsystem'`. */
|
|
@@ -414,13 +388,11 @@ export interface SubsystemWalkthrough {
|
|
|
414
388
|
|
|
415
389
|
export interface SubsystemModelDocument {
|
|
416
390
|
components: SubsystemComponent[];
|
|
417
|
-
/** Topology relations (structural / module / type). May be empty. */
|
|
418
|
-
relations: SubsystemRelation[];
|
|
419
391
|
/** Ordered runtime walkthroughs (one per named behavior). */
|
|
420
392
|
walkthroughs?: SubsystemWalkthrough[];
|
|
421
393
|
}
|
|
422
394
|
|
|
423
|
-
/** Stable id for a derived graph edge from a
|
|
395
|
+
/** Stable id for a derived graph edge from a walkthrough hop. */
|
|
424
396
|
export function derivedGraphEdgeId(
|
|
425
397
|
from: string,
|
|
426
398
|
to: string,
|
|
@@ -429,10 +401,6 @@ export function derivedGraphEdgeId(
|
|
|
429
401
|
return `${from}--${mechanism}-->${to}`;
|
|
430
402
|
}
|
|
431
403
|
|
|
432
|
-
/**
|
|
433
|
-
* Build display edges for the graph canvas from topology relations and
|
|
434
|
-
* walkthrough hops (deduped by from/to/mechanism).
|
|
435
|
-
*/
|
|
436
404
|
/** React Flow / canvas edge id for a walkthrough hop. */
|
|
437
405
|
export function walkthroughStepGraphEdgeId(
|
|
438
406
|
step: Pick<SubsystemWalkthroughStep, 'from' | 'to' | 'mechanism'>,
|
|
@@ -472,23 +440,10 @@ export function reorderTargetIndex(boundary: number, from: number): number {
|
|
|
472
440
|
}
|
|
473
441
|
|
|
474
442
|
export function deriveGraphEdges(doc: {
|
|
475
|
-
relations?: readonly SubsystemRelation[];
|
|
476
443
|
walkthroughs?: readonly SubsystemWalkthrough[];
|
|
477
444
|
graphifyRelations?: readonly SubsystemGraphifyRelation[];
|
|
478
445
|
}): SubsystemComponentEdge[] {
|
|
479
446
|
const byId = new Map<string, SubsystemComponentEdge>();
|
|
480
|
-
for (const r of doc.relations ?? []) {
|
|
481
|
-
const id = r.id || derivedGraphEdgeId(r.from, r.to, r.relationType);
|
|
482
|
-
if (!byId.has(id)) {
|
|
483
|
-
byId.set(id, {
|
|
484
|
-
id,
|
|
485
|
-
from: r.from,
|
|
486
|
-
to: r.to,
|
|
487
|
-
mechanism: r.relationType,
|
|
488
|
-
refs: r.refs,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
447
|
for (const w of doc.walkthroughs ?? []) {
|
|
493
448
|
for (const step of w.steps) {
|
|
494
449
|
const id = derivedGraphEdgeId(step.from, step.to, step.mechanism);
|
|
@@ -522,18 +477,16 @@ export function deriveGraphEdges(doc: {
|
|
|
522
477
|
}
|
|
523
478
|
|
|
524
479
|
/**
|
|
525
|
-
* True when the model has components but no
|
|
480
|
+
* True when the model has components but no walkthrough or graphify edges.
|
|
526
481
|
* Those snapshots are a catalog of declarations, not a graph.
|
|
527
482
|
*/
|
|
528
483
|
export function isConstructsOnlyModel(doc: {
|
|
529
484
|
components: readonly { alias: string }[];
|
|
530
|
-
relations?: readonly SubsystemRelation[];
|
|
531
485
|
walkthroughs?: readonly SubsystemWalkthrough[];
|
|
532
486
|
graphifyRelations?: readonly SubsystemGraphifyRelation[];
|
|
533
487
|
}): boolean {
|
|
534
488
|
if (doc.components.length === 0) return false;
|
|
535
489
|
return deriveGraphEdges({
|
|
536
|
-
relations: doc.relations,
|
|
537
490
|
walkthroughs: doc.walkthroughs,
|
|
538
491
|
graphifyRelations: doc.graphifyRelations,
|
|
539
492
|
}).length === 0;
|
|
@@ -1131,19 +1084,6 @@ export interface SubsystemGraphEdgeData extends Record<string, unknown> {
|
|
|
1131
1084
|
|
|
1132
1085
|
export type SubsystemGraphEdge = Edge<SubsystemGraphEdgeData>;
|
|
1133
1086
|
|
|
1134
|
-
/**
|
|
1135
|
-
* Runtime vocabulary of relation types — mirrors `SubsystemRelationType`.
|
|
1136
|
-
* Used to split derived display edges into their relation vs walkthrough
|
|
1137
|
-
* source (the two unions are disjoint).
|
|
1138
|
-
*/
|
|
1139
|
-
export const SUBSYSTEM_RELATION_TYPES = [
|
|
1140
|
-
'extends',
|
|
1141
|
-
'inherits',
|
|
1142
|
-
'implements',
|
|
1143
|
-
'mixes_in',
|
|
1144
|
-
'method',
|
|
1145
|
-
] as const satisfies readonly SubsystemRelationType[];
|
|
1146
|
-
|
|
1147
1087
|
/** Runtime vocabulary of walkthrough hop mechanisms — mirrors `SubsystemWalkthroughMechanism`. */
|
|
1148
1088
|
export const SUBSYSTEM_WALKTHROUGH_MECHANISMS = [
|
|
1149
1089
|
'calls',
|
|
@@ -1156,18 +1096,10 @@ export const SUBSYSTEM_WALKTHROUGH_MECHANISMS = [
|
|
|
1156
1096
|
'registers-into',
|
|
1157
1097
|
] as const satisfies readonly SubsystemWalkthroughMechanism[];
|
|
1158
1098
|
|
|
1159
|
-
const RELATION_TYPE_SET: ReadonlySet<string> = new Set(SUBSYSTEM_RELATION_TYPES);
|
|
1160
1099
|
const WALKTHROUGH_MECHANISM_SET: ReadonlySet<string> = new Set(
|
|
1161
1100
|
SUBSYSTEM_WALKTHROUGH_MECHANISMS,
|
|
1162
1101
|
);
|
|
1163
1102
|
|
|
1164
|
-
/** True when a mechanism belongs to the topology relation vocabulary. */
|
|
1165
|
-
export function isRelationMechanism(
|
|
1166
|
-
mechanism: string,
|
|
1167
|
-
): mechanism is SubsystemRelationType {
|
|
1168
|
-
return RELATION_TYPE_SET.has(mechanism);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
1103
|
/** True when a mechanism belongs to the walkthrough hop vocabulary. */
|
|
1172
1104
|
export function isWalkthroughMechanism(
|
|
1173
1105
|
mechanism: string,
|
|
@@ -1177,12 +1109,7 @@ export function isWalkthroughMechanism(
|
|
|
1177
1109
|
|
|
1178
1110
|
export const MECHANISM_COLOR: Record<SubsystemEdgeMechanism, string> = {
|
|
1179
1111
|
calls: '#22c55e', // green
|
|
1180
|
-
extends: '#b48ead', // purple
|
|
1181
|
-
inherits: '#9b6fd0', // purple
|
|
1182
|
-
implements: '#c586c0', // magenta
|
|
1183
|
-
mixes_in: '#d474a8', // pink-magenta
|
|
1184
1112
|
uses: '#e3b341', // gold
|
|
1185
|
-
method: '#c586c0', // magenta
|
|
1186
1113
|
feeds: '#4ec9b0', // teal — data-flow into a processor
|
|
1187
1114
|
produces: '#a78bfa', // violet — emits an output type
|
|
1188
1115
|
writes: '#e8853a', // orange — mutates retained state
|
|
@@ -1193,12 +1120,7 @@ export const MECHANISM_COLOR: Record<SubsystemEdgeMechanism, string> = {
|
|
|
1193
1120
|
|
|
1194
1121
|
export const MECHANISM_STYLE: Record<SubsystemEdgeMechanism, 'solid' | 'dashed' | 'dotted'> = {
|
|
1195
1122
|
calls: 'solid',
|
|
1196
|
-
extends: 'dashed',
|
|
1197
|
-
inherits: 'dashed',
|
|
1198
|
-
implements: 'dashed',
|
|
1199
|
-
mixes_in: 'dashed',
|
|
1200
1123
|
uses: 'solid',
|
|
1201
|
-
method: 'solid',
|
|
1202
1124
|
feeds: 'solid',
|
|
1203
1125
|
produces: 'solid',
|
|
1204
1126
|
writes: 'solid',
|
|
@@ -1363,12 +1285,7 @@ export function boundaryFill(color: string, alpha = '1f'): string {
|
|
|
1363
1285
|
* directly verifiable" styling of edge labels. */
|
|
1364
1286
|
export const MECHANISM_DESCRIPTIONS: [SubsystemEdgeMechanism, string, boolean][] = [
|
|
1365
1287
|
['calls', 'function/method call (call graph edge)', true],
|
|
1366
|
-
['extends', 'class inheritance', true],
|
|
1367
|
-
['inherits', 'class inheritance', true],
|
|
1368
|
-
['implements', 'implements interface / protocol', true],
|
|
1369
|
-
['mixes_in', 'applies mixin', true],
|
|
1370
1288
|
['uses', 'general dependency (import, call, or reference)', false],
|
|
1371
|
-
['method', 'structural: has method / member', true],
|
|
1372
1289
|
['feeds', 'data flow: output feeds into input', false],
|
|
1373
1290
|
['produces', 'data flow: produces / outputs', false],
|
|
1374
1291
|
['writes', 'state access: mutates retained state', true],
|
|
@@ -1733,7 +1650,7 @@ export function convertSubsystemToEdges(
|
|
|
1733
1650
|
|
|
1734
1651
|
/** Stable key for layout-affecting graph fields (ignores declarationRef, etc.). */
|
|
1735
1652
|
export function subsystemGraphLayoutKey(
|
|
1736
|
-
doc: Pick<SubsystemModelDocument, 'components' | '
|
|
1653
|
+
doc: Pick<SubsystemModelDocument, 'components' | 'walkthroughs'> & {
|
|
1737
1654
|
graphifyRelations?: readonly SubsystemGraphifyRelation[];
|
|
1738
1655
|
},
|
|
1739
1656
|
): string {
|
|
@@ -18,17 +18,12 @@ const doc: SubsystemModelDocument = {
|
|
|
18
18
|
{ alias: 'x', name: 'Gist API', construct: 'external', file: '', purl: 'external:api.github.com/gists' },
|
|
19
19
|
{ alias: 'agent', name: 'Maintenance agent', construct: 'custom_entity', file: '', purl: 'external' },
|
|
20
20
|
],
|
|
21
|
-
relations: [
|
|
22
|
-
{ id: 'r1', from: 'a', to: 'b', relationType: 'method' }, // intra p1
|
|
23
|
-
{ id: 'r2', from: 'a', to: 'c', relationType: 'method' },
|
|
24
|
-
{ id: 'r3', from: 'b', to: 'c', relationType: 'method' },
|
|
25
|
-
{ id: 'r4', from: 'a', to: 'x', relationType: 'method' },
|
|
26
|
-
],
|
|
27
21
|
walkthroughs: [
|
|
28
22
|
{
|
|
29
23
|
id: 'w1',
|
|
30
24
|
title: 'flow',
|
|
31
25
|
steps: [
|
|
26
|
+
{ from: 'a', to: 'b', mechanism: 'calls', file: 'src/a.ts', line: 1, purl: `${REPO}#src/a.ts`, symbol: 'Alpha' },
|
|
32
27
|
{ from: 'b', to: 'c', mechanism: 'calls', file: 'src/b.ts', line: 1, purl: `${REPO}#src/b.ts`, symbol: 'Beta' },
|
|
33
28
|
{ from: 'a', to: 'c', mechanism: 'calls', file: 'src/a.ts', line: 2, purl: `${REPO}#src/a.ts`, symbol: 'Alpha' },
|
|
34
29
|
{ from: 'a', to: 'agent', mechanism: 'calls', file: 'src/a.ts', line: 3, purl: `${REPO}#src/a.ts`, symbol: 'Alpha' },
|
|
@@ -77,18 +72,16 @@ describe('container view', () => {
|
|
|
77
72
|
expect(model.groups[0]!.memberIds.sort()).toEqual(['container:p1', 'container:p2']);
|
|
78
73
|
});
|
|
79
74
|
|
|
80
|
-
test('edges roll up
|
|
75
|
+
test('flow edges roll up to containers; only flow edges exist', () => {
|
|
81
76
|
const byId = Object.fromEntries(model.edges.map((e) => [e.id, e]));
|
|
82
77
|
// a->b is intra-p1 — dropped.
|
|
83
78
|
expect(model.edges.some((e) => e.source === e.target)).toBe(false);
|
|
84
|
-
const rel = byId['relationship:container:p1\u0000container:p2']!;
|
|
85
|
-
expect(rel.kind).toBe('relationship');
|
|
86
|
-
expect(rel.count).toBe(2); // r2 + r3
|
|
87
79
|
const flow = byId['flow:container:p1\u0000container:p2']!;
|
|
80
|
+
expect(flow.kind).toBe('flow');
|
|
88
81
|
expect(flow.count).toBe(2); // a->c + b->c
|
|
89
|
-
expect(byId['relationship:container:p1\u0000external:external:api.github.com/gists']).toBeDefined();
|
|
90
82
|
expect(byId['flow:container:p1\u0000actor:agent']).toBeDefined();
|
|
91
|
-
expect(model.edges).
|
|
83
|
+
expect(model.edges.every((e) => e.kind === 'flow')).toBe(true);
|
|
84
|
+
expect(model.edges).toHaveLength(2);
|
|
92
85
|
});
|
|
93
86
|
});
|
|
94
87
|
|
package/src/subsystem/toC4.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* toC4 — project a subsystem-model (or a composition of them) onto C4 levels.
|
|
3
3
|
*
|
|
4
4
|
* The stored document is flat: `components[]` carry `process` (runtime unit),
|
|
5
|
-
* `purl` (repo/package), `construct`, `role`, `module`, plus `
|
|
6
|
-
* (
|
|
5
|
+
* `purl` (repo/package), `construct`, `role`, `module`, plus `walkthroughs[]`
|
|
6
|
+
* (dynamic). C4 wants a hierarchy above the
|
|
7
7
|
* component — system → container → component — with externals and actors
|
|
8
8
|
* outside it. This module derives that hierarchy without inventing data:
|
|
9
9
|
*
|
|
@@ -59,12 +59,12 @@ export interface C4Group {
|
|
|
59
59
|
memberIds: string[];
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
/** A rolled-up collaboration
|
|
62
|
+
/** A rolled-up collaboration — a walkthrough flow between owners. */
|
|
63
63
|
export interface C4Edge {
|
|
64
64
|
id: string;
|
|
65
65
|
source: string;
|
|
66
66
|
target: string;
|
|
67
|
-
kind: '
|
|
67
|
+
kind: 'flow';
|
|
68
68
|
label: string;
|
|
69
69
|
mechanisms: string[];
|
|
70
70
|
count: number;
|
|
@@ -234,7 +234,6 @@ export function toC4(doc: SubsystemModelDocument, options: ToC4Options = {}): C4
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
// --- Edges: roll each endpoint up to its owner at this view -------------
|
|
237
|
-
const relEdges = new Map<string, C4Edge>();
|
|
238
237
|
const flowEdges = new Map<string, C4Edge>();
|
|
239
238
|
|
|
240
239
|
const addEdge = (
|
|
@@ -255,16 +254,13 @@ export function toC4(doc: SubsystemModelDocument, options: ToC4Options = {}): C4
|
|
|
255
254
|
if (!edge.mechanisms.includes(mechanism)) edge.mechanisms.push(mechanism);
|
|
256
255
|
};
|
|
257
256
|
|
|
258
|
-
for (const r of doc.relations ?? []) {
|
|
259
|
-
addEdge(relEdges, ownerOf.get(r.from), ownerOf.get(r.to), 'relationship', r.relationType);
|
|
260
|
-
}
|
|
261
257
|
for (const w of doc.walkthroughs ?? []) {
|
|
262
258
|
for (const s of w.steps ?? []) {
|
|
263
259
|
addEdge(flowEdges, ownerOf.get(s.from), ownerOf.get(s.to), 'flow', s.mechanism);
|
|
264
260
|
}
|
|
265
261
|
}
|
|
266
262
|
|
|
267
|
-
const edges = [...
|
|
263
|
+
const edges = [...flowEdges.values()];
|
|
268
264
|
for (const e of edges) e.label = [...e.mechanisms].sort().join(' + ');
|
|
269
265
|
|
|
270
266
|
return {
|