@principal-ai/subsystems-react 0.53.6 → 0.54.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 +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreTrailCodeView.js +7 -5
- package/dist/pierre/PierreTrailCodeView.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +1 -2
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +144 -9
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +924 -148
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +22 -1
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +10 -2
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +5 -4
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +12 -4
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +10 -7
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/GraphSidebar.d.ts +71 -0
- package/dist/subsystem/GraphSidebar.d.ts.map +1 -0
- package/dist/subsystem/GraphSidebar.js +145 -0
- package/dist/subsystem/GraphSidebar.js.map +1 -0
- package/dist/subsystem/GraphWithSidebar.d.ts +24 -0
- package/dist/subsystem/GraphWithSidebar.d.ts.map +1 -0
- package/dist/subsystem/GraphWithSidebar.js +25 -0
- package/dist/subsystem/GraphWithSidebar.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +33 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +364 -411
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/TrailStepsNav.d.ts +48 -0
- package/dist/subsystem/TrailStepsNav.d.ts.map +1 -0
- package/dist/subsystem/TrailStepsNav.js +189 -0
- package/dist/subsystem/TrailStepsNav.js.map +1 -0
- package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
- package/dist/subsystem/TrailsPanel.js +3 -4
- package/dist/subsystem/TrailsPanel.js.map +1 -1
- package/dist/subsystem/__fixtures__/c4Drilldown.d.ts +151 -0
- package/dist/subsystem/__fixtures__/c4Drilldown.d.ts.map +1 -0
- package/dist/subsystem/__fixtures__/c4Drilldown.js +783 -0
- package/dist/subsystem/__fixtures__/c4Drilldown.js.map +1 -0
- package/dist/subsystem/c4.d.ts +39 -8
- package/dist/subsystem/c4.d.ts.map +1 -1
- package/dist/subsystem/c4.js.map +1 -1
- package/dist/subsystem/declarationRef.d.ts +26 -18
- package/dist/subsystem/declarationRef.d.ts.map +1 -1
- package/dist/subsystem/declarationRef.js +9 -4
- package/dist/subsystem/declarationRef.js.map +1 -1
- package/dist/subsystem/model.d.ts +19 -1
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +12 -2
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +24 -4
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/trailBrief.d.ts.map +1 -1
- package/dist/subsystem/trailBrief.js +6 -1
- package/dist/subsystem/trailBrief.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +9 -2
- package/src/pierre/PierreTrailCodeView.tsx +9 -13
- package/src/stories/Subsystem/C4Graph/C4ToComponentDrill.stories.tsx +496 -0
- package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +7 -7
- package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +19 -25
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +8 -10
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +19 -19
- package/src/stories/Subsystem/ComponentGraph/ProposedTrails.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/TrailAutoplay.stories.tsx +7 -7
- package/src/stories/Subsystem/ComponentGraph/Trails.stories.tsx +27 -30
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +1 -2
- package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +10 -13
- package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +3 -4
- package/src/stories/Subsystem/Sidebar/GraphSidebar.stories.tsx +206 -0
- package/src/subsystem/C4Graph.groups.test.ts +96 -2
- package/src/subsystem/C4Graph.tsx +1377 -165
- package/src/subsystem/C4NodeCard.test.ts +1 -2
- package/src/subsystem/C4NodeCard.tsx +24 -2
- package/src/subsystem/ComponentDeclaration.tsx +18 -8
- package/src/subsystem/ConstructsCatalog.tsx +23 -8
- package/src/subsystem/GraphSidebar.test.tsx +152 -0
- package/src/subsystem/GraphSidebar.tsx +300 -0
- package/src/subsystem/GraphWithSidebar.test.tsx +78 -0
- package/src/subsystem/GraphWithSidebar.tsx +60 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +284 -370
- package/src/subsystem/TrailStepsNav.test.tsx +179 -0
- package/src/subsystem/TrailStepsNav.tsx +372 -0
- package/src/subsystem/TrailsPanel.tsx +3 -4
- package/src/subsystem/__fixtures__/c4Drilldown.test.ts +338 -0
- package/src/subsystem/__fixtures__/c4Drilldown.ts +847 -0
- package/src/subsystem/c4.ts +41 -8
- package/src/subsystem/declarationRef.ts +28 -19
- package/src/subsystem/model.test.ts +32 -12
- package/src/subsystem/model.ts +30 -1
- package/src/subsystem/nodes.group.test.tsx +82 -0
- package/src/subsystem/nodes.tsx +30 -2
- package/src/subsystem/trailBrief.test.ts +2 -2
- package/src/subsystem/trailBrief.ts +6 -1
package/src/subsystem/c4.ts
CHANGED
|
@@ -67,16 +67,22 @@ interface C4ElementBase {
|
|
|
67
67
|
state: C4ElementState;
|
|
68
68
|
/** Why this element is proposed as it is — shown in the confirm UI. */
|
|
69
69
|
rationale?: string;
|
|
70
|
-
/** The component aliases this element groups. */
|
|
71
|
-
members?: string[];
|
|
72
|
-
/** Distinct constructs among the members, when resolved from a document. */
|
|
73
|
-
constructs?: string[];
|
|
74
|
-
/** Source model ids that contributed members, when attribution is supplied. */
|
|
75
|
-
models?: string[];
|
|
76
70
|
/** When the state last changed (ISO). */
|
|
77
71
|
decidedAt?: string;
|
|
78
72
|
}
|
|
79
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The component aliases an element groups — the explicit member list.
|
|
76
|
+
*
|
|
77
|
+
* Only the kinds that have no `process` claim to resolve members from carry
|
|
78
|
+
* one: an external system (its few aliases) and an authored C4 component (the
|
|
79
|
+
* aliases it groups). A container's members are *derived* from its `process`
|
|
80
|
+
* claim against the model document, never stored here.
|
|
81
|
+
*/
|
|
82
|
+
interface C4MemberList {
|
|
83
|
+
members?: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
/**
|
|
81
87
|
* What kind of thing a container is.
|
|
82
88
|
*
|
|
@@ -112,7 +118,7 @@ export interface C4Container extends C4ElementBase {
|
|
|
112
118
|
* split across containers (an RPC bridge with a renderer half and a host half),
|
|
113
119
|
* which is precisely the case worth being able to state.
|
|
114
120
|
*/
|
|
115
|
-
export interface C4Component extends C4ElementBase {
|
|
121
|
+
export interface C4Component extends C4ElementBase, C4MemberList {
|
|
116
122
|
kind: 'component';
|
|
117
123
|
/** The id of the container this component sits inside. */
|
|
118
124
|
container: string;
|
|
@@ -120,7 +126,7 @@ export interface C4Component extends C4ElementBase {
|
|
|
120
126
|
technology: string;
|
|
121
127
|
}
|
|
122
128
|
/** A software system outside the boundary we are drawing. */
|
|
123
|
-
export interface C4ExternalSystem extends C4ElementBase {
|
|
129
|
+
export interface C4ExternalSystem extends C4ElementBase, C4MemberList {
|
|
124
130
|
kind: 'external-system';
|
|
125
131
|
/** Often unknown for a third-party system, so optional here. */
|
|
126
132
|
technology?: string;
|
|
@@ -223,6 +229,21 @@ export function deriveC4Groups(
|
|
|
223
229
|
return groups;
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
/**
|
|
233
|
+
* One trail contributing steps to a rolled-up C4 edge — the drill-down
|
|
234
|
+
* provenance that lets a line on the C4 diagram answer "which trail steps
|
|
235
|
+
* implement this?".
|
|
236
|
+
*
|
|
237
|
+
* Deliberately ids only: the host owns the trail document and can resolve
|
|
238
|
+
* titles, so this stays a pointer instead of duplicating content.
|
|
239
|
+
*/
|
|
240
|
+
export interface C4TrailRef {
|
|
241
|
+
/** Trail id in the subsystem model document, e.g. `tl-audit`. */
|
|
242
|
+
trailId: string;
|
|
243
|
+
/** Indices into `trail.steps` of the steps that cross this edge's endpoints. */
|
|
244
|
+
stepIndices: number[];
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
/**
|
|
227
248
|
* A line between two elements. Authored, like the boxes.
|
|
228
249
|
*
|
|
@@ -230,6 +251,12 @@ export function deriveC4Groups(
|
|
|
230
251
|
* interaction (`protocol`), and the label is that plus the trail verbs where
|
|
231
252
|
* known. `label` is what the chip shows; `protocol` and `mechanisms` are kept
|
|
232
253
|
* separate so a caller can render either.
|
|
254
|
+
*
|
|
255
|
+
* `count` / `mechanisms` / `trailRefs` are the rolled-up view of the same line:
|
|
256
|
+
* how many component-level steps produced it (union of the referenced steps),
|
|
257
|
+
* which verbs they used, and — when a builder supplied provenance — exactly
|
|
258
|
+
* which trails/steps. All three are derived data riding an authored line, so
|
|
259
|
+
* they are optional: an authored edge may carry none until a rollup has run.
|
|
233
260
|
*/
|
|
234
261
|
export interface C4Edge {
|
|
235
262
|
id: string;
|
|
@@ -243,6 +270,12 @@ export interface C4Edge {
|
|
|
243
270
|
mechanisms: string[];
|
|
244
271
|
/** How many underlying steps this line rolled up. */
|
|
245
272
|
count: number;
|
|
273
|
+
/**
|
|
274
|
+
* The trails whose steps cross this edge's endpoints. The drill-down bridge:
|
|
275
|
+
* a reader clicks the line, gets these refs, and jumps into the component
|
|
276
|
+
* graph's trail focus. Absent on an authored line no rollup has run over.
|
|
277
|
+
*/
|
|
278
|
+
trailRefs?: C4TrailRef[];
|
|
246
279
|
}
|
|
247
280
|
|
|
248
281
|
/**
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Source-span anchoring — a content-addressed range of source.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* that
|
|
6
|
-
*
|
|
4
|
+
* A span is `startLine` + `endLine` + a hash of the normalized content across
|
|
5
|
+
* that range. The type is owned by `@principal-ai/subsystems-core` (the
|
|
6
|
+
* portable standard); this module re-exports it and provides the line helpers
|
|
7
|
+
* shared by the renderers and host-side hashing.
|
|
8
|
+
*
|
|
9
|
+
* Graphify emits `source_location: "L42"` (a start line only); the end line is
|
|
10
|
+
* derived by verification, and `hashContentRange` (host-side) produces the
|
|
11
|
+
* `contentHash`.
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
export interface SubsystemDeclarationRef {
|
|
11
|
-
/** Repo-root-relative path (matches `component.file`). */
|
|
12
|
-
file: string;
|
|
13
|
-
/** 1-based declaration start line (from graphify `source_location`). */
|
|
14
|
-
startLine: number;
|
|
15
|
-
/** SHA-256 of {@link normalizeDeclarationLine} output, truncated to 32 hex chars. */
|
|
16
|
-
lineHash: string;
|
|
17
|
-
/** Graphify node id when captured from an exact anchor. */
|
|
18
|
-
graphifyNodeId?: string;
|
|
19
|
-
/** ISO timestamp when this ref was recorded. */
|
|
20
|
-
capturedAt: string;
|
|
21
|
-
}
|
|
14
|
+
import type { SubsystemSourceSpan } from '@principal-ai/subsystems-core';
|
|
22
15
|
|
|
23
|
-
export type
|
|
24
|
-
|
|
16
|
+
export type { SubsystemSourceSpan };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How fresh an anchored span is, as computed by verification.
|
|
20
|
+
*
|
|
21
|
+
* - `full` — the span (start + end + hash) is present and live content matches.
|
|
22
|
+
* - `stale` — present, but the content or position drifted.
|
|
23
|
+
* - `missing` — present, but the range no longer resolves.
|
|
24
|
+
* - `unanchored` — no span captured yet.
|
|
25
|
+
* - `unchecked` — source unreadable / repo unresolved.
|
|
26
|
+
*/
|
|
27
|
+
export type SpanFreshness =
|
|
28
|
+
| 'full'
|
|
25
29
|
| 'stale'
|
|
26
30
|
| 'missing'
|
|
27
31
|
| 'unanchored'
|
|
@@ -55,6 +59,11 @@ export function extractDeclarationLine(content: string, lineNumber: number): str
|
|
|
55
59
|
export interface SubsystemOpenFileOptions {
|
|
56
60
|
/** Scroll/highlight this 1-based line (declaration start or trail site). */
|
|
57
61
|
startLine?: number;
|
|
62
|
+
/**
|
|
63
|
+
* End of the anchored span, when the owner carries one. Lets the snippet
|
|
64
|
+
* window show the construct's full extent instead of a single line.
|
|
65
|
+
*/
|
|
66
|
+
endLine?: number;
|
|
58
67
|
/**
|
|
59
68
|
* When true with `startLine`, show the whole file scrolled to that line
|
|
60
69
|
* instead of a focused snippet window.
|
|
@@ -61,7 +61,6 @@ const trails: SubsystemTrail[] = [
|
|
|
61
61
|
to: 'reader',
|
|
62
62
|
mechanism: 'calls',
|
|
63
63
|
file: 'transcript.ts',
|
|
64
|
-
line: 1,
|
|
65
64
|
purl: 'pkg:github/principal-ai/agent-monitoring#transcript.ts',
|
|
66
65
|
symbol: 'transcript',
|
|
67
66
|
},
|
|
@@ -70,7 +69,6 @@ const trails: SubsystemTrail[] = [
|
|
|
70
69
|
to: 'host',
|
|
71
70
|
mechanism: 'calls',
|
|
72
71
|
file: 'SessionReader.ts',
|
|
73
|
-
line: 1,
|
|
74
72
|
purl: 'pkg:github/principal-ai/agent-monitoring#SessionReader.ts',
|
|
75
73
|
symbol: 'SessionReader',
|
|
76
74
|
},
|
|
@@ -112,7 +110,7 @@ describe('subsystem graph model', () => {
|
|
|
112
110
|
trails: [{
|
|
113
111
|
id: 'w1',
|
|
114
112
|
title: 'feed',
|
|
115
|
-
steps: [{ from: 'reader', to: 'proposed-watcher', mechanism: 'feeds', file: 'x.ts'
|
|
113
|
+
steps: [{ from: 'reader', to: 'proposed-watcher', mechanism: 'feeds', file: 'x.ts' }],
|
|
116
114
|
}],
|
|
117
115
|
});
|
|
118
116
|
expect(nodes.find((n) => n.id === 'proposed-watcher')).toBeDefined();
|
|
@@ -455,7 +453,7 @@ describe('subsystem graph model', () => {
|
|
|
455
453
|
trails: [{
|
|
456
454
|
id: 'w1',
|
|
457
455
|
title: 'call',
|
|
458
|
-
steps: [{ from: 'a', to: 'b', mechanism: 'calls', file: 'a.ts'
|
|
456
|
+
steps: [{ from: 'a', to: 'b', mechanism: 'calls', file: 'a.ts' }],
|
|
459
457
|
}],
|
|
460
458
|
});
|
|
461
459
|
expect(regions.map((r) => r.key)).toEqual(['app/host']);
|
|
@@ -463,6 +461,30 @@ describe('subsystem graph model', () => {
|
|
|
463
461
|
expect(nodes.find((n) => n.id === processGroupNodeId('app/lonely'))).toBeUndefined();
|
|
464
462
|
});
|
|
465
463
|
|
|
464
|
+
test('codeProcessKeys marks a process region for package chrome without changing its identity', async () => {
|
|
465
|
+
const { regions, nodes } = await buildSubsystemGraph(
|
|
466
|
+
{
|
|
467
|
+
components: [
|
|
468
|
+
{ alias: 'a', name: 'a', construct: 'function', file: 'a.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
469
|
+
{ alias: 'b', name: 'b', construct: 'function', file: 'b.ts', purl: 'pkg:github/acme/app', process: 'app/host' },
|
|
470
|
+
// Singleton, kept — the component graph passes showSingletonFrames
|
|
471
|
+
// true, and the fixture's real singleton (`subsystems-react/ui`)
|
|
472
|
+
// is exactly the demotion case.
|
|
473
|
+
{ alias: 'solo', name: 'solo', construct: 'function', file: 's.ts', purl: 'pkg:github/acme/app', process: 'lib/ui' },
|
|
474
|
+
],
|
|
475
|
+
trails: [],
|
|
476
|
+
},
|
|
477
|
+
{ showSingletonFrames: true, codeProcessKeys: new Set(['lib/ui']) },
|
|
478
|
+
);
|
|
479
|
+
const lib = regions.find((r) => r.key === 'lib/ui');
|
|
480
|
+
// Presentation only: kind stays 'process', so the group id and issue
|
|
481
|
+
// targeting still read the claim.
|
|
482
|
+
expect(lib?.kind).toBe('process');
|
|
483
|
+
expect(lib?.chrome).toBe('package');
|
|
484
|
+
expect(regions.find((r) => r.key === 'app/host')?.chrome).toBeUndefined();
|
|
485
|
+
expect(nodes.some((n) => n.id === processGroupNodeId('lib/ui'))).toBe(true);
|
|
486
|
+
});
|
|
487
|
+
|
|
466
488
|
// NOTE: buildSubsystemGraph cannot assert ELK-built frames here — elkjs
|
|
467
489
|
// constructs a Worker, which bun's test env lacks, so every call falls back
|
|
468
490
|
// to manual positions. Frame existence is covered by planCompoundGroups
|
|
@@ -792,25 +814,24 @@ describe('subsystem graph model', () => {
|
|
|
792
814
|
}
|
|
793
815
|
});
|
|
794
816
|
|
|
795
|
-
test('subsystemGraphLayoutKey ignores
|
|
817
|
+
test('subsystemGraphLayoutKey ignores declarationSpan-only changes', () => {
|
|
796
818
|
const base = doc;
|
|
797
|
-
const
|
|
819
|
+
const withSpan = {
|
|
798
820
|
components: comps.map((c, i) =>
|
|
799
821
|
i === 0
|
|
800
822
|
? {
|
|
801
823
|
...c,
|
|
802
|
-
|
|
803
|
-
file: c.file,
|
|
824
|
+
declarationSpan: {
|
|
804
825
|
startLine: 42,
|
|
805
|
-
|
|
806
|
-
|
|
826
|
+
endLine: 48,
|
|
827
|
+
contentHash: 'abc',
|
|
807
828
|
},
|
|
808
829
|
}
|
|
809
830
|
: c,
|
|
810
831
|
),
|
|
811
832
|
trails,
|
|
812
833
|
};
|
|
813
|
-
expect(subsystemGraphLayoutKey(base)).toBe(subsystemGraphLayoutKey(
|
|
834
|
+
expect(subsystemGraphLayoutKey(base)).toBe(subsystemGraphLayoutKey(withSpan));
|
|
814
835
|
});
|
|
815
836
|
});
|
|
816
837
|
|
|
@@ -845,7 +866,6 @@ describe('isConstructsOnlyModel', () => {
|
|
|
845
866
|
to: 'reader',
|
|
846
867
|
mechanism: 'calls',
|
|
847
868
|
file: 'transcript.ts',
|
|
848
|
-
line: 1,
|
|
849
869
|
},
|
|
850
870
|
],
|
|
851
871
|
},
|
package/src/subsystem/model.ts
CHANGED
|
@@ -441,6 +441,15 @@ export interface SubsystemProcessRegion {
|
|
|
441
441
|
label: string;
|
|
442
442
|
/** Component ids that are members of this region. */
|
|
443
443
|
memberAliases: string[];
|
|
444
|
+
/**
|
|
445
|
+
* Presentation override: `'package'` draws the frame with package chrome —
|
|
446
|
+
* rounded, dashed, transparent — instead of the solid runtime chrome
|
|
447
|
+
* process frames carry. For a process region the host judged code
|
|
448
|
+
* organisation (a library, an area, a proposal) rather than an accepted
|
|
449
|
+
* deployment unit. Presentation only: `kind`, node id, issue targeting and
|
|
450
|
+
* membership still read the claim as a process region.
|
|
451
|
+
*/
|
|
452
|
+
chrome?: 'package';
|
|
444
453
|
}
|
|
445
454
|
|
|
446
455
|
/** Options for which boundary frames are kept. */
|
|
@@ -466,6 +475,15 @@ export interface BoundaryFrameOptions {
|
|
|
466
475
|
* and module frames.
|
|
467
476
|
*/
|
|
468
477
|
moduleNesting?: 'exact' | 'path';
|
|
478
|
+
/**
|
|
479
|
+
* Process keys whose frames draw package chrome (rounded, dashed) instead
|
|
480
|
+
* of runtime chrome (square, solid) — the host's judgment that these claims
|
|
481
|
+
* group code organisation (a library, an area, a proposal), not an accepted
|
|
482
|
+
* deployment unit. Typically derived from the accepted C4 element set: a
|
|
483
|
+
* process no container or external backs is code, not a box. Presentation
|
|
484
|
+
* only — region identity and issue targeting are unchanged.
|
|
485
|
+
*/
|
|
486
|
+
codeProcessKeys?: ReadonlySet<string>;
|
|
469
487
|
}
|
|
470
488
|
|
|
471
489
|
/** React Flow id for a process boundary group node. */
|
|
@@ -1830,7 +1848,7 @@ export function convertSubsystemToEdges(
|
|
|
1830
1848
|
return edges;
|
|
1831
1849
|
}
|
|
1832
1850
|
|
|
1833
|
-
/** Stable key for layout-affecting graph fields (ignores
|
|
1851
|
+
/** Stable key for layout-affecting graph fields (ignores declarationSpan, etc.). */
|
|
1834
1852
|
export function subsystemGraphLayoutKey(
|
|
1835
1853
|
doc: Pick<SubsystemModelDocument, 'components' | 'trails'> & {
|
|
1836
1854
|
graphifyRelations?: readonly SubsystemGraphifyRelation[];
|
|
@@ -1894,6 +1912,7 @@ export async function buildSubsystemGraph(
|
|
|
1894
1912
|
moduleNesting,
|
|
1895
1913
|
graphifyRelations,
|
|
1896
1914
|
orderByLine = false,
|
|
1915
|
+
codeProcessKeys,
|
|
1897
1916
|
} = opts;
|
|
1898
1917
|
const frameOpts: BoundaryFrameOptions = {
|
|
1899
1918
|
showSingletonFrames,
|
|
@@ -1904,6 +1923,16 @@ export async function buildSubsystemGraph(
|
|
|
1904
1923
|
const edges = convertSubsystemToEdges(doc, graphifyRelations);
|
|
1905
1924
|
// Nested boundary tree: package → process → module → leaves.
|
|
1906
1925
|
const layoutGroups = buildBoundaryLayoutGroups(doc, frameOpts);
|
|
1926
|
+
// Demote host-judged code regions after grouping (both nesting variants
|
|
1927
|
+
// return through this call), keeping `kind` intact so ids and issue
|
|
1928
|
+
// targeting still read the process claim.
|
|
1929
|
+
if (codeProcessKeys?.size) {
|
|
1930
|
+
for (const g of layoutGroups) {
|
|
1931
|
+
if (g.region.kind === 'process' && codeProcessKeys.has(g.region.key)) {
|
|
1932
|
+
g.region = { ...g.region, chrome: 'package' };
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1907
1936
|
const regions = layoutGroups.map((g) => g.region);
|
|
1908
1937
|
const regionIds = new Set(layoutGroups.map((g) => g.id));
|
|
1909
1938
|
const byAlias = new Map(doc.components.map((c) => [c.alias, c]));
|
|
@@ -63,6 +63,88 @@ describe('SubsystemGroupNode frame color', () => {
|
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
describe('SubsystemGroupNode chrome', () => {
|
|
67
|
+
// Runtime chrome asserts "deployment unit": square, solid, tinted. A
|
|
68
|
+
// process region the host marked `chrome: 'package'` groups code
|
|
69
|
+
// organisation instead — a library, an area, a proposal — and draws as a
|
|
70
|
+
// package boundary: rounded, dashed, transparent.
|
|
71
|
+
test('a plain process region wears runtime chrome', () => {
|
|
72
|
+
const { container } = renderGroup({ region });
|
|
73
|
+
const frame = container.querySelector('div') as HTMLElement;
|
|
74
|
+
expect(frame.style.border).toContain('solid');
|
|
75
|
+
expect(frame.style.borderRadius).toBe('0px');
|
|
76
|
+
expect(frame.style.background).not.toBe('transparent');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("chrome: 'package' draws the frame as a code boundary", () => {
|
|
80
|
+
const { container } = renderGroup({
|
|
81
|
+
region: { ...region, chrome: 'package' },
|
|
82
|
+
});
|
|
83
|
+
const frame = container.querySelector('div') as HTMLElement;
|
|
84
|
+
expect(frame.style.border).toContain('dashed');
|
|
85
|
+
expect(frame.style.borderRadius).toBe('12px');
|
|
86
|
+
expect(frame.style.background).toBe('transparent');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('chrome does not change the region identity the frame renders', () => {
|
|
90
|
+
const { container } = renderGroup({
|
|
91
|
+
region: { ...region, chrome: 'package' },
|
|
92
|
+
});
|
|
93
|
+
// The badge still reads the label — kind is chrome's business alone.
|
|
94
|
+
expect(container.textContent).toContain('booking-web/server');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('SubsystemGroupNode package glyph', () => {
|
|
99
|
+
// A frame that renders as a package boundary wears the Package glyph: a
|
|
100
|
+
// real package (purl repo identity) or a process claim the host demoted to
|
|
101
|
+
// package chrome. Module/directory/plain-process frames stay text-only —
|
|
102
|
+
// the glyph is what tells the shared dashed chrome "code organisation, not
|
|
103
|
+
// a deployment unit".
|
|
104
|
+
test('a package-kind region wears the glyph', () => {
|
|
105
|
+
const { container } = renderGroup({
|
|
106
|
+
region: {
|
|
107
|
+
kind: 'package',
|
|
108
|
+
key: 'pkg:github/acme/app',
|
|
109
|
+
label: 'acme/app',
|
|
110
|
+
memberAliases: ['a', 'b'],
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
expect(container.querySelector('.lucide-package')).not.toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a process region demoted to package chrome wears the glyph", () => {
|
|
117
|
+
const { container } = renderGroup({
|
|
118
|
+
region: { ...region, chrome: 'package' },
|
|
119
|
+
});
|
|
120
|
+
expect(container.querySelector('.lucide-package')).not.toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('plain process, module, and directory regions carry no glyph', () => {
|
|
124
|
+
expect(renderGroup({ region }).container.querySelector('.lucide-package')).toBeNull();
|
|
125
|
+
expect(
|
|
126
|
+
renderGroup({
|
|
127
|
+
region: {
|
|
128
|
+
kind: 'module',
|
|
129
|
+
key: 'src/bun/index.ts',
|
|
130
|
+
label: 'src/bun/index.ts',
|
|
131
|
+
memberAliases: ['a', 'b'],
|
|
132
|
+
},
|
|
133
|
+
}).container.querySelector('.lucide-package'),
|
|
134
|
+
).toBeNull();
|
|
135
|
+
expect(
|
|
136
|
+
renderGroup({
|
|
137
|
+
region: {
|
|
138
|
+
kind: 'directory',
|
|
139
|
+
key: 'src/bun',
|
|
140
|
+
label: 'src/bun',
|
|
141
|
+
memberAliases: ['a', 'b'],
|
|
142
|
+
},
|
|
143
|
+
}).container.querySelector('.lucide-package'),
|
|
144
|
+
).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
66
148
|
describe('SubsystemGroupNode boundary badge', () => {
|
|
67
149
|
// A boundary finding is a property of the region's shape, so it badges the
|
|
68
150
|
// frame with its own icon rather than borrowing the verification-rung chips
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
import { componentColor } from '../pierre/constructColors';
|
|
47
47
|
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
48
48
|
import { ISSUE_KIND_ICON, ISSUE_RUNG_ICON } from './IssueList';
|
|
49
|
-
import { Search } from 'lucide-react';
|
|
49
|
+
import { Package, Search } from 'lucide-react';
|
|
50
50
|
import type { LucideIcon } from 'lucide-react';
|
|
51
51
|
|
|
52
52
|
export const CONSTRUCT_LABEL: Record<string, string> = {
|
|
@@ -592,7 +592,18 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
592
592
|
// Directory frames (path-derived nesting) render like modules — dashed,
|
|
593
593
|
// collapsible path badge.
|
|
594
594
|
const isModule = region?.kind === 'module' || region?.kind === 'directory';
|
|
595
|
-
|
|
595
|
+
// Runtime chrome asserts "deployment unit" — a process region the host
|
|
596
|
+
// marked `chrome: 'package'` groups code organisation instead (a library,
|
|
597
|
+
// an area, a proposal) and forgoes it, drawing like a package frame.
|
|
598
|
+
// Presentation only: `kind` still says process, so ids and issue targets
|
|
599
|
+
// key off the claim unchanged.
|
|
600
|
+
const isProcess = region?.kind === 'process' && region?.chrome !== 'package';
|
|
601
|
+
// A frame renders as a package boundary two ways: it IS a package (repo
|
|
602
|
+
// identity from `purl`) or the host demoted a process claim to package
|
|
603
|
+
// chrome (a library, an area, a proposal). Both wear the Package glyph, so
|
|
604
|
+
// "code organisation, not a deployment unit" reads off the canvas instead
|
|
605
|
+
// of hiding in the shared rounded/dashed chrome.
|
|
606
|
+
const isPackageFrame = region?.kind === 'package' || region?.chrome === 'package';
|
|
596
607
|
const expanded = isModule && expandedLabel === label;
|
|
597
608
|
const availableBadgeWidth = Math.max(0, (width ?? 400) - MODULE_BADGE_INSET * 2 - 4);
|
|
598
609
|
const collapsedLabel = isModule ? moduleBadgeLabel(label, availableBadgeWidth) : label;
|
|
@@ -707,8 +718,25 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
707
718
|
maxWidth: expanded ? undefined : availableBadgeWidth,
|
|
708
719
|
}
|
|
709
720
|
: undefined),
|
|
721
|
+
...(isPackageFrame
|
|
722
|
+
? {
|
|
723
|
+
// Icon + key on one row: the Package glyph leads the label
|
|
724
|
+
// so package chrome reads as code organisation at a glance.
|
|
725
|
+
display: 'inline-flex',
|
|
726
|
+
alignItems: 'center',
|
|
727
|
+
gap: 5,
|
|
728
|
+
}
|
|
729
|
+
: undefined),
|
|
710
730
|
}}
|
|
711
731
|
>
|
|
732
|
+
{isPackageFrame && (
|
|
733
|
+
<Package
|
|
734
|
+
size={13}
|
|
735
|
+
strokeWidth={2}
|
|
736
|
+
aria-hidden="true"
|
|
737
|
+
style={{ flexShrink: 0 }}
|
|
738
|
+
/>
|
|
739
|
+
)}
|
|
712
740
|
{canToggle ? (
|
|
713
741
|
<span style={{ whiteSpace: 'nowrap' }}>
|
|
714
742
|
{expanded ? label : hover ? hoveredLabel : collapsedLabel}
|
|
@@ -11,7 +11,7 @@ const trail: SubsystemTrail = {
|
|
|
11
11
|
to: 'api',
|
|
12
12
|
mechanism: 'calls',
|
|
13
13
|
file: 'src/ui/login.tsx',
|
|
14
|
-
|
|
14
|
+
span: { startLine: 42, endLine: 42, contentHash: 'h' },
|
|
15
15
|
purl: 'pkg:github/acme/app',
|
|
16
16
|
symbol: 'Login.submit',
|
|
17
17
|
},
|
|
@@ -20,7 +20,7 @@ const trail: SubsystemTrail = {
|
|
|
20
20
|
to: 'store',
|
|
21
21
|
mechanism: 'writes',
|
|
22
22
|
file: 'src/api/session.ts',
|
|
23
|
-
|
|
23
|
+
span: { startLine: 7, endLine: 7, contentHash: 'h' },
|
|
24
24
|
purl: 'pkg:github/acme/app',
|
|
25
25
|
symbol: 'Session.create',
|
|
26
26
|
annotation: 'session row is written here',
|
|
@@ -31,7 +31,12 @@ export function buildStepBrief(
|
|
|
31
31
|
lines.push(`- to: \`${step.to}\``);
|
|
32
32
|
lines.push(`- mechanism: \`${step.mechanism}\``);
|
|
33
33
|
lines.push(`- symbol: \`${step.symbol}\``);
|
|
34
|
-
|
|
34
|
+
const site = step.span
|
|
35
|
+
? step.span.startLine === step.span.endLine
|
|
36
|
+
? `${step.file}:${step.span.startLine}`
|
|
37
|
+
: `${step.file}:${step.span.startLine}-${step.span.endLine}`
|
|
38
|
+
: step.file;
|
|
39
|
+
lines.push(`- site: \`${site}\``);
|
|
35
40
|
lines.push(`- purl: \`${step.purl}\``);
|
|
36
41
|
if (step.annotation != null && step.annotation.length > 0) {
|
|
37
42
|
lines.push(`- note: ${step.annotation}`);
|