@principal-ai/subsystems-react 0.21.1 → 0.22.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/graphify/consolidated.d.ts +5 -24
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/index.d.ts +1 -1
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
- package/dist/pierre/PierreSnippetView.js +4 -19
- package/dist/pierre/PierreSnippetView.js.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.js +20 -3
- package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -1
- package/dist/pierre/constructColors.d.ts.map +1 -1
- package/dist/pierre/constructColors.js +0 -5
- package/dist/pierre/constructColors.js.map +1 -1
- package/dist/pierre/index.d.ts +1 -1
- package/dist/pierre/index.d.ts.map +1 -1
- package/dist/pierre/index.js +1 -1
- package/dist/pierre/index.js.map +1 -1
- package/dist/pierre/sliceSnippet.d.ts +6 -0
- package/dist/pierre/sliceSnippet.d.ts.map +1 -1
- package/dist/pierre/sliceSnippet.js +25 -0
- package/dist/pierre/sliceSnippet.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +0 -3
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
- package/dist/subsystem/formatDeclaration.js +0 -15
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +61 -22
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +186 -63
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +3 -4
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +5 -6
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +6 -4
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +144 -54
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/graphify/consolidated.ts +4 -26
- package/src/graphify/index.ts +0 -2
- package/src/index.ts +0 -2
- package/src/pierre/PierreSnippetView.tsx +4 -18
- package/src/pierre/PierreWalkthroughCodeView.tsx +39 -3
- package/src/pierre/constructColors.test.ts +1 -2
- package/src/pierre/constructColors.ts +0 -5
- package/src/pierre/index.ts +1 -1
- package/src/pierre/sliceSnippet.test.ts +44 -1
- package/src/pierre/sliceSnippet.ts +28 -0
- package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +0 -31
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -46
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +9 -24
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +2 -2
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +287 -0
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +1 -1
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +0 -3
- package/src/subsystem/formatDeclaration.ts +0 -19
- package/src/subsystem/model.test.ts +190 -9
- package/src/subsystem/model.ts +219 -73
- package/src/subsystem/nodes.tsx +6 -6
- package/src/utils/elkLayout.ts +148 -58
|
@@ -17,7 +17,7 @@ import type { SubsystemDeclarationRef } from './declarationRef';
|
|
|
17
17
|
/** Structured declaration shape — same union as graphify drill-down payloads. */
|
|
18
18
|
export type SubsystemConstructDeclaration = GraphifyComponentDetail;
|
|
19
19
|
export type SubsystemDeclarationProvenance = 'verified' | 'authored';
|
|
20
|
-
export type SubsystemComponentConstruct = 'class' | 'function' | 'method' | 'interface' | 'type_alias' | 'enum' | '
|
|
20
|
+
export type SubsystemComponentConstruct = 'class' | 'function' | 'method' | 'interface' | 'type_alias' | 'enum' | 'store' | 'external' | 'custom_entity';
|
|
21
21
|
/**
|
|
22
22
|
* Semantic role — where the node sits in the topology, orthogonal to
|
|
23
23
|
* `construct` (what it is). Roles have *inherited* anatomy: an entry renders
|
|
@@ -55,7 +55,7 @@ export interface SubsystemDeclToken {
|
|
|
55
55
|
* Topology relation type — structural / module / type claims.
|
|
56
56
|
* Belongs on `relations[]`, not on walkthrough hops.
|
|
57
57
|
*/
|
|
58
|
-
export type SubsystemRelationType = 'imports' | '
|
|
58
|
+
export type SubsystemRelationType = 'imports' | 'extends' | 'inherits' | 'implements' | 'mixes_in' | 'method' | 'references' | 'contains';
|
|
59
59
|
/**
|
|
60
60
|
* Walkthrough hop mechanism — runtime seams with a `file:line` site.
|
|
61
61
|
*/
|
|
@@ -131,11 +131,21 @@ export interface SubsystemComponent {
|
|
|
131
131
|
/**
|
|
132
132
|
* Runtime process membership — which deployment unit this node is a
|
|
133
133
|
* member of (e.g. `principal-studio/host`, `principal-studio/renderer`). Nodes
|
|
134
|
-
* sharing a `process` are drawn inside one boundary region
|
|
135
|
-
*
|
|
136
|
-
*
|
|
134
|
+
* sharing a `process` are drawn inside one boundary region; nodes without
|
|
135
|
+
* one sit outside every process boundary (external actors, services,
|
|
136
|
+
* libraries). Orthogonal to `module` (source-file frame).
|
|
137
137
|
*/
|
|
138
138
|
process?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Source-module membership — which file/module this export belongs to
|
|
141
|
+
* (e.g. `src/session/transcript.ts`). Nodes sharing a `module` are drawn
|
|
142
|
+
* inside one boundary frame. Prefer this over inventing a module construct:
|
|
143
|
+
* each export keeps its real construct (`function` / `class` / …) and the
|
|
144
|
+
* file reads as a frame. Orthogonal to `process` (runtime deployment).
|
|
145
|
+
* When both are set, the module frame is the component's parent (finer
|
|
146
|
+
* grain); process framing still groups siblings that share a process.
|
|
147
|
+
*/
|
|
148
|
+
module?: string;
|
|
139
149
|
/** A symbol this component exposes / is (the node's identity). */
|
|
140
150
|
symbol?: string;
|
|
141
151
|
/**
|
|
@@ -248,13 +258,11 @@ export declare function deriveGraphEdges(doc: {
|
|
|
248
258
|
*
|
|
249
259
|
* `symbol` is the source of truth (fully-qualified code identity). The name
|
|
250
260
|
* is the symbol itself:
|
|
251
|
-
* - class/type/
|
|
252
|
-
* - method
|
|
253
|
-
* -
|
|
254
|
-
* a common-sense convention for whole-file modules, not a real TS name
|
|
255
|
-
* - otherwise no symbol → fall back to an existing name
|
|
261
|
+
* - class/type/function/... symbol → symbol (e.g. `SessionReader`)
|
|
262
|
+
* - method `Owner.method` → last segment (e.g. `SessionReader.normalize` → `normalize`)
|
|
263
|
+
* - otherwise no symbol → fall back to an existing name
|
|
256
264
|
*/
|
|
257
|
-
export declare function deriveNameFromSymbol(symbol: string | undefined, construct: SubsystemComponentConstruct, existingName?: string,
|
|
265
|
+
export declare function deriveNameFromSymbol(symbol: string | undefined, construct: SubsystemComponentConstruct, existingName?: string, _file?: string, stereotype?: string): string;
|
|
258
266
|
/**
|
|
259
267
|
* Human-readable purl identity — drops the `pkg:<type>/` scheme wrapper and
|
|
260
268
|
* trailing version, keeping the package / owner-repo identity:
|
|
@@ -270,11 +278,13 @@ export declare function deriveNameFromSymbol(symbol: string | undefined, constru
|
|
|
270
278
|
export declare function formatPurl(purl: string): string;
|
|
271
279
|
export type SubsystemGraphNodeType = 'subsystem-component' | 'subsystem-group';
|
|
272
280
|
/**
|
|
273
|
-
* One
|
|
274
|
-
* Nodes without
|
|
281
|
+
* One boundary region — either a process (runtime deployment unit) or a
|
|
282
|
+
* module (source file). Nodes without that field sit outside those frames.
|
|
275
283
|
*/
|
|
276
284
|
export interface SubsystemProcessRegion {
|
|
277
|
-
/**
|
|
285
|
+
/** Discriminator — which field produced this region. */
|
|
286
|
+
kind: 'process' | 'module';
|
|
287
|
+
/** The `process` or `module` value (e.g. `principal-studio/host`, `src/a.ts`). */
|
|
278
288
|
key: string;
|
|
279
289
|
/** Display label for the boundary frame. */
|
|
280
290
|
label: string;
|
|
@@ -283,11 +293,38 @@ export interface SubsystemProcessRegion {
|
|
|
283
293
|
}
|
|
284
294
|
/** React Flow id for a process boundary group node. */
|
|
285
295
|
export declare function processGroupNodeId(processKey: string): string;
|
|
296
|
+
/** React Flow id for a module boundary group node. */
|
|
297
|
+
export declare function moduleGroupNodeId(moduleKey: string): string;
|
|
298
|
+
/** React Flow id for a boundary group of either kind. */
|
|
299
|
+
export declare function boundaryGroupNodeId(region: Pick<SubsystemProcessRegion, 'kind' | 'key'>): string;
|
|
286
300
|
/**
|
|
287
|
-
* Derive boundary regions
|
|
288
|
-
*
|
|
301
|
+
* Derive process boundary regions — one per distinct non-empty `process`
|
|
302
|
+
* value, in first-appearance order.
|
|
289
303
|
*/
|
|
290
304
|
export declare function getSubsystemRegions(doc: Pick<SubsystemModelDocument, 'components'>): SubsystemProcessRegion[];
|
|
305
|
+
/**
|
|
306
|
+
* Derive module boundary regions — one per distinct non-empty `module`
|
|
307
|
+
* value, in first-appearance order. Label is the module path (file).
|
|
308
|
+
*/
|
|
309
|
+
export declare function getSubsystemModuleRegions(doc: Pick<SubsystemModelDocument, 'components'>): SubsystemProcessRegion[];
|
|
310
|
+
/**
|
|
311
|
+
* One compound frame for ELK / React Flow — module frames may nest under a
|
|
312
|
+
* process frame via `parentId`.
|
|
313
|
+
*/
|
|
314
|
+
export interface BoundaryLayoutGroup {
|
|
315
|
+
id: string;
|
|
316
|
+
memberIds: string[];
|
|
317
|
+
/** When set, this group is a child of another boundary group (process). */
|
|
318
|
+
parentId?: string;
|
|
319
|
+
region: SubsystemProcessRegion;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Build the process → module → leaf group tree for layout.
|
|
323
|
+
* Multi-member modules nest under a process when every member shares that
|
|
324
|
+
* process; process ELK children are nested module group ids plus any
|
|
325
|
+
* process members that are not inside a kept module frame.
|
|
326
|
+
*/
|
|
327
|
+
export declare function buildBoundaryLayoutGroups(doc: Pick<SubsystemModelDocument, 'components'>): BoundaryLayoutGroup[];
|
|
291
328
|
export interface SubsystemGraphNodeData extends Record<string, unknown> {
|
|
292
329
|
component: SubsystemComponent;
|
|
293
330
|
/** Set while a file is open in the drawer: true if this node's component
|
|
@@ -399,17 +436,19 @@ export declare function nodeMinWidthForBadges(component: {
|
|
|
399
436
|
}): number;
|
|
400
437
|
/**
|
|
401
438
|
* Convert a subsystem graph document into React Flow nodes. Components that
|
|
402
|
-
* carry a `
|
|
403
|
-
* (`
|
|
404
|
-
*
|
|
405
|
-
*
|
|
439
|
+
* carry a `module` get a `parentId` pointing at their module frame
|
|
440
|
+
* (`module:<path>`); otherwise a `process` stamps `process:<process>`. Module
|
|
441
|
+
* frames may themselves nest under a process frame (stamped on the group node
|
|
442
|
+
* in `buildSubsystemGraph`). Nodes without either stay top-level. The initial
|
|
443
|
+
* grid clusters by `module ?? process ?? purl`; ELK then refines with compound
|
|
444
|
+
* layout.
|
|
406
445
|
*/
|
|
407
446
|
export declare function convertSubsystemToNodes(doc: SubsystemModelDocument, opts?: {
|
|
408
447
|
maxNodeWidth?: number;
|
|
409
448
|
}): SubsystemGraphNode[];
|
|
410
449
|
/**
|
|
411
|
-
* Convert boundary regions into React Flow parent (group) nodes.
|
|
412
|
-
*
|
|
450
|
+
* Convert boundary regions into React Flow parent (group) nodes. Module
|
|
451
|
+
* frames that share a process nest under that process via `parentId`.
|
|
413
452
|
* Positions/sizes are placeholders — ELK compound layout overwrites them.
|
|
414
453
|
*/
|
|
415
454
|
export declare function convertSubsystemToGroups(doc: Pick<SubsystemModelDocument, 'components'>): SubsystemGraphNode[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/subsystem/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAEL,KAAK,IAAI,EACT,KAAK,IAAI,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEhE,iFAAiF;AACjF,MAAM,MAAM,6BAA6B,GAAG,uBAAuB,CAAC;AACpE,MAAM,MAAM,8BAA8B,GAAG,UAAU,GAAG,UAAU,CAAC;AAErE,MAAM,MAAM,2BAA2B,GACnC,OAAO,GACP,UAAU,GACV,QAAQ,GACR,WAAW,GACX,YAAY,GACZ,MAAM,GACN,
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/subsystem/model.ts"],"names":[],"mappings":"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"}
|
package/dist/subsystem/model.js
CHANGED
|
@@ -59,23 +59,15 @@ export function deriveGraphEdges(doc) {
|
|
|
59
59
|
*
|
|
60
60
|
* `symbol` is the source of truth (fully-qualified code identity). The name
|
|
61
61
|
* is the symbol itself:
|
|
62
|
-
* - class/type/
|
|
63
|
-
* - method
|
|
64
|
-
* -
|
|
65
|
-
* a common-sense convention for whole-file modules, not a real TS name
|
|
66
|
-
* - otherwise no symbol → fall back to an existing name
|
|
62
|
+
* - class/type/function/... symbol → symbol (e.g. `SessionReader`)
|
|
63
|
+
* - method `Owner.method` → last segment (e.g. `SessionReader.normalize` → `normalize`)
|
|
64
|
+
* - otherwise no symbol → fall back to an existing name
|
|
67
65
|
*/
|
|
68
|
-
export function deriveNameFromSymbol(symbol, construct, existingName,
|
|
66
|
+
export function deriveNameFromSymbol(symbol, construct, existingName, _file, stereotype) {
|
|
69
67
|
let name;
|
|
70
68
|
if (symbol && symbol.trim()) {
|
|
71
69
|
name = symbol;
|
|
72
70
|
}
|
|
73
|
-
else if (construct === 'module' && file) {
|
|
74
|
-
const base = file.split('/').pop() ?? '';
|
|
75
|
-
const clean = base.replace(/\.[^.]+$/, ''); // strip extension
|
|
76
|
-
if (clean)
|
|
77
|
-
name = clean;
|
|
78
|
-
}
|
|
79
71
|
if (!name)
|
|
80
72
|
name = existingName ?? 'untitled';
|
|
81
73
|
// Decorations = what the drill-down shows. Framework stereotypes can override
|
|
@@ -83,7 +75,7 @@ export function deriveNameFromSymbol(symbol, construct, existingName, file, ster
|
|
|
83
75
|
// Executable constructs wear `()`; brace-bodied constructs (interface,
|
|
84
76
|
// type_alias, enum) wear ` {}`. Classes render bare — the construct badge
|
|
85
77
|
// already says "class".
|
|
86
|
-
// Everything else (class, store,
|
|
78
|
+
// Everything else (class, store, external) renders bare.
|
|
87
79
|
if (stereotype === 'component' && !name.startsWith('<')) {
|
|
88
80
|
return `<${name}>`;
|
|
89
81
|
}
|
|
@@ -127,9 +119,19 @@ export function formatPurl(purl) {
|
|
|
127
119
|
export function processGroupNodeId(processKey) {
|
|
128
120
|
return `process:${processKey}`;
|
|
129
121
|
}
|
|
122
|
+
/** React Flow id for a module boundary group node. */
|
|
123
|
+
export function moduleGroupNodeId(moduleKey) {
|
|
124
|
+
return `module:${moduleKey}`;
|
|
125
|
+
}
|
|
126
|
+
/** React Flow id for a boundary group of either kind. */
|
|
127
|
+
export function boundaryGroupNodeId(region) {
|
|
128
|
+
return region.kind === 'module'
|
|
129
|
+
? moduleGroupNodeId(region.key)
|
|
130
|
+
: processGroupNodeId(region.key);
|
|
131
|
+
}
|
|
130
132
|
/**
|
|
131
|
-
* Derive boundary regions
|
|
132
|
-
*
|
|
133
|
+
* Derive process boundary regions — one per distinct non-empty `process`
|
|
134
|
+
* value, in first-appearance order.
|
|
133
135
|
*/
|
|
134
136
|
export function getSubsystemRegions(doc) {
|
|
135
137
|
const byProcess = new Map();
|
|
@@ -142,16 +144,84 @@ export function getSubsystemRegions(doc) {
|
|
|
142
144
|
byProcess.set(p, list);
|
|
143
145
|
}
|
|
144
146
|
return [...byProcess.entries()].map(([key, memberIds]) => ({
|
|
147
|
+
kind: 'process',
|
|
145
148
|
key,
|
|
146
149
|
label: key,
|
|
147
150
|
memberIds,
|
|
148
151
|
}));
|
|
149
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Derive module boundary regions — one per distinct non-empty `module`
|
|
155
|
+
* value, in first-appearance order. Label is the module path (file).
|
|
156
|
+
*/
|
|
157
|
+
export function getSubsystemModuleRegions(doc) {
|
|
158
|
+
const byModule = new Map();
|
|
159
|
+
for (const c of doc.components) {
|
|
160
|
+
const m = c.module?.trim();
|
|
161
|
+
if (!m)
|
|
162
|
+
continue;
|
|
163
|
+
const list = byModule.get(m) ?? [];
|
|
164
|
+
list.push(c.id);
|
|
165
|
+
byModule.set(m, list);
|
|
166
|
+
}
|
|
167
|
+
return [...byModule.entries()].map(([key, memberIds]) => ({
|
|
168
|
+
kind: 'module',
|
|
169
|
+
key,
|
|
170
|
+
label: key,
|
|
171
|
+
memberIds,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build the process → module → leaf group tree for layout.
|
|
176
|
+
* Multi-member modules nest under a process when every member shares that
|
|
177
|
+
* process; process ELK children are nested module group ids plus any
|
|
178
|
+
* process members that are not inside a kept module frame.
|
|
179
|
+
*/
|
|
180
|
+
export function buildBoundaryLayoutGroups(doc) {
|
|
181
|
+
const byId = new Map(doc.components.map((c) => [c.id, c]));
|
|
182
|
+
const modules = getSubsystemModuleRegions(doc).filter((r) => r.memberIds.length >= 2);
|
|
183
|
+
const processes = getSubsystemRegions(doc).filter((r) => r.memberIds.length >= 2);
|
|
184
|
+
const keptProcessKeys = new Set(processes.map((r) => r.key));
|
|
185
|
+
const moduleGroups = modules.map((r) => {
|
|
186
|
+
const processesOfMembers = new Set();
|
|
187
|
+
for (const id of r.memberIds) {
|
|
188
|
+
const p = byId.get(id)?.process?.trim();
|
|
189
|
+
if (p)
|
|
190
|
+
processesOfMembers.add(p);
|
|
191
|
+
}
|
|
192
|
+
let parentId;
|
|
193
|
+
if (processesOfMembers.size === 1) {
|
|
194
|
+
const p = [...processesOfMembers][0];
|
|
195
|
+
if (keptProcessKeys.has(p))
|
|
196
|
+
parentId = processGroupNodeId(p);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
id: moduleGroupNodeId(r.key),
|
|
200
|
+
memberIds: [...r.memberIds],
|
|
201
|
+
parentId,
|
|
202
|
+
region: r,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
const processGroups = processes.map((r) => {
|
|
206
|
+
const processId = processGroupNodeId(r.key);
|
|
207
|
+
const nestedModules = moduleGroups.filter((m) => m.parentId === processId);
|
|
208
|
+
const nestedModuleKeys = new Set(nestedModules.map((m) => m.region.key));
|
|
209
|
+
const directLeaves = r.memberIds.filter((id) => {
|
|
210
|
+
const mod = byId.get(id)?.module?.trim();
|
|
211
|
+
if (!mod)
|
|
212
|
+
return true;
|
|
213
|
+
return !nestedModuleKeys.has(mod);
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
id: processId,
|
|
217
|
+
memberIds: [...nestedModules.map((m) => m.id), ...directLeaves],
|
|
218
|
+
region: r,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
return [...moduleGroups, ...processGroups];
|
|
222
|
+
}
|
|
150
223
|
export const MECHANISM_COLOR = {
|
|
151
224
|
imports: '#0893d2',
|
|
152
|
-
imports_from: '#5aa9e6',
|
|
153
|
-
re_exports: '#3aa5c9',
|
|
154
|
-
defines: '#2e86ab',
|
|
155
225
|
calls: '#4ec9b0',
|
|
156
226
|
extends: '#b48ead',
|
|
157
227
|
inherits: '#9b6fd0',
|
|
@@ -170,9 +240,6 @@ export const MECHANISM_COLOR = {
|
|
|
170
240
|
};
|
|
171
241
|
export const MECHANISM_STYLE = {
|
|
172
242
|
imports: 'solid',
|
|
173
|
-
imports_from: 'solid',
|
|
174
|
-
re_exports: 'solid',
|
|
175
|
-
defines: 'solid',
|
|
176
243
|
calls: 'solid',
|
|
177
244
|
extends: 'dashed',
|
|
178
245
|
inherits: 'dashed',
|
|
@@ -193,9 +260,6 @@ export const MECHANISM_STYLE = {
|
|
|
193
260
|
* directly verifiable" styling of edge labels. */
|
|
194
261
|
export const MECHANISM_DESCRIPTIONS = [
|
|
195
262
|
['imports', 'import statement (code-level dependency)', true],
|
|
196
|
-
['imports_from', 'imported by (reverse dependency)', true],
|
|
197
|
-
['re_exports', 're-exports symbols from', true],
|
|
198
|
-
['defines', 'defines / declares symbol', true],
|
|
199
263
|
['calls', 'function/method call (call graph edge)', true],
|
|
200
264
|
['extends', 'class inheritance', true],
|
|
201
265
|
['inherits', 'class inheritance', true],
|
|
@@ -204,7 +268,7 @@ export const MECHANISM_DESCRIPTIONS = [
|
|
|
204
268
|
['uses', 'general dependency (import, call, or reference)', false],
|
|
205
269
|
['method', 'structural: has method / member', true],
|
|
206
270
|
['references', 'type / symbol reference (not a call)', true],
|
|
207
|
-
['contains', '
|
|
271
|
+
['contains', 'UI composition: shell/view owns a child surface', false],
|
|
208
272
|
['feeds', 'data flow: output feeds into input', false],
|
|
209
273
|
['produces', 'data flow: produces / outputs', false],
|
|
210
274
|
['writes', 'state access: mutates retained state', true],
|
|
@@ -349,20 +413,20 @@ export function nodeMinWidthForBadges(component) {
|
|
|
349
413
|
}
|
|
350
414
|
/**
|
|
351
415
|
* Convert a subsystem graph document into React Flow nodes. Components that
|
|
352
|
-
* carry a `
|
|
353
|
-
* (`
|
|
354
|
-
*
|
|
355
|
-
*
|
|
416
|
+
* carry a `module` get a `parentId` pointing at their module frame
|
|
417
|
+
* (`module:<path>`); otherwise a `process` stamps `process:<process>`. Module
|
|
418
|
+
* frames may themselves nest under a process frame (stamped on the group node
|
|
419
|
+
* in `buildSubsystemGraph`). Nodes without either stay top-level. The initial
|
|
420
|
+
* grid clusters by `module ?? process ?? purl`; ELK then refines with compound
|
|
421
|
+
* layout.
|
|
356
422
|
*/
|
|
357
423
|
export function convertSubsystemToNodes(doc, opts = {}) {
|
|
358
424
|
const { maxNodeWidth } = opts;
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
// purl is code identity — nodes without a process (external actors,
|
|
362
|
-
// services, libraries) fall back to purl and sit outside every boundary.
|
|
425
|
+
// Cluster for the pre-ELK grid: module (source file) → process (runtime)
|
|
426
|
+
// → purl (package identity).
|
|
363
427
|
const byPkg = new Map();
|
|
364
428
|
for (const c of doc.components) {
|
|
365
|
-
const regionKey = c.process ?? c.purl;
|
|
429
|
+
const regionKey = c.module ?? c.process ?? c.purl;
|
|
366
430
|
const list = byPkg.get(regionKey) ?? [];
|
|
367
431
|
list.push(c);
|
|
368
432
|
byPkg.set(regionKey, list);
|
|
@@ -394,11 +458,17 @@ export function convertSubsystemToNodes(doc, opts = {}) {
|
|
|
394
458
|
const cssBorder = 4; // 2px border each side
|
|
395
459
|
const rawWidth = Math.max(cssMinWidth, textWidth + cssPadding + cssBorder);
|
|
396
460
|
const nodeWidth = Math.max(cssMinWidth, Math.min(cap, rawWidth));
|
|
461
|
+
const moduleKey = c.module?.trim();
|
|
397
462
|
const processKey = c.process?.trim();
|
|
463
|
+
const parentId = moduleKey
|
|
464
|
+
? moduleGroupNodeId(moduleKey)
|
|
465
|
+
: processKey
|
|
466
|
+
? processGroupNodeId(processKey)
|
|
467
|
+
: undefined;
|
|
398
468
|
nodes.push({
|
|
399
469
|
id: c.id,
|
|
400
470
|
type: 'subsystem-component',
|
|
401
|
-
...(
|
|
471
|
+
...(parentId ? { parentId } : {}),
|
|
402
472
|
position: { x: PAD + col * COL_W, y: cursorY + row * ROW_H },
|
|
403
473
|
width: nodeWidth,
|
|
404
474
|
height: 84,
|
|
@@ -410,18 +480,19 @@ export function convertSubsystemToNodes(doc, opts = {}) {
|
|
|
410
480
|
return nodes;
|
|
411
481
|
}
|
|
412
482
|
/**
|
|
413
|
-
* Convert boundary regions into React Flow parent (group) nodes.
|
|
414
|
-
*
|
|
483
|
+
* Convert boundary regions into React Flow parent (group) nodes. Module
|
|
484
|
+
* frames that share a process nest under that process via `parentId`.
|
|
415
485
|
* Positions/sizes are placeholders — ELK compound layout overwrites them.
|
|
416
486
|
*/
|
|
417
487
|
export function convertSubsystemToGroups(doc) {
|
|
418
|
-
return
|
|
419
|
-
id:
|
|
488
|
+
return buildBoundaryLayoutGroups(doc).map((g) => ({
|
|
489
|
+
id: g.id,
|
|
420
490
|
type: 'subsystem-group',
|
|
421
491
|
position: { x: 0, y: 0 },
|
|
422
492
|
width: 400,
|
|
423
493
|
height: 300,
|
|
424
|
-
|
|
494
|
+
...(g.parentId ? { parentId: g.parentId } : {}),
|
|
495
|
+
data: { region: g.region },
|
|
425
496
|
}));
|
|
426
497
|
}
|
|
427
498
|
/**
|
|
@@ -458,7 +529,7 @@ export function convertSubsystemToEdges(doc) {
|
|
|
458
529
|
/** Stable key for layout-affecting graph fields (ignores declarationRef, etc.). */
|
|
459
530
|
export function subsystemGraphLayoutKey(doc) {
|
|
460
531
|
const components = doc.components
|
|
461
|
-
.map(({ id, purl, name, symbol, construct, file, purpose, process }) => [id, purl, name, symbol ?? '', construct, file, purpose ?? '', process ?? ''].join('\0'))
|
|
532
|
+
.map(({ id, purl, name, symbol, construct, file, purpose, process, module }) => [id, purl, name, symbol ?? '', construct, file, purpose ?? '', process ?? '', module ?? ''].join('\0'))
|
|
462
533
|
.sort()
|
|
463
534
|
.join('\n');
|
|
464
535
|
const edgeKey = deriveGraphEdges(doc)
|
|
@@ -476,16 +547,28 @@ export async function buildSubsystemGraph(doc, opts = {}) {
|
|
|
476
547
|
const { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights } = opts;
|
|
477
548
|
const nodes = convertSubsystemToNodes(doc, { maxNodeWidth });
|
|
478
549
|
const edges = convertSubsystemToEdges(doc);
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
const
|
|
550
|
+
// Nested boundary tree: process → module → leaves (when both fields set).
|
|
551
|
+
const layoutGroups = buildBoundaryLayoutGroups(doc);
|
|
552
|
+
const regions = layoutGroups.map((g) => g.region);
|
|
553
|
+
const regionIds = new Set(layoutGroups.map((g) => g.id));
|
|
554
|
+
const byId = new Map(doc.components.map((c) => [c.id, c]));
|
|
555
|
+
// Drop leaf parentIds that point at singleton / missing frames; fall back
|
|
556
|
+
// from a dropped module frame to a kept process frame when possible.
|
|
484
557
|
for (const n of nodes) {
|
|
485
558
|
if (n.type !== 'subsystem-component')
|
|
486
559
|
continue;
|
|
487
|
-
const
|
|
488
|
-
if (
|
|
560
|
+
const parentId = n.parentId;
|
|
561
|
+
if (!parentId)
|
|
562
|
+
continue;
|
|
563
|
+
if (regionIds.has(parentId))
|
|
564
|
+
continue;
|
|
565
|
+
const comp = n.data.component;
|
|
566
|
+
const processKey = comp?.process?.trim() || byId.get(n.id)?.process?.trim();
|
|
567
|
+
const processId = processKey ? processGroupNodeId(processKey) : undefined;
|
|
568
|
+
if (processId && regionIds.has(processId)) {
|
|
569
|
+
n.parentId = processId;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
489
572
|
delete n.parentId;
|
|
490
573
|
}
|
|
491
574
|
}
|
|
@@ -537,8 +620,7 @@ export async function buildSubsystemGraph(doc, opts = {}) {
|
|
|
537
620
|
n.height = mh;
|
|
538
621
|
}
|
|
539
622
|
}
|
|
540
|
-
// ELK auto-layout:
|
|
541
|
-
// process partitions as compound parents so boundaries shape the layout.
|
|
623
|
+
// ELK auto-layout: nested compound parents (process → module → leaves).
|
|
542
624
|
let placedNodes = nodes;
|
|
543
625
|
let labelPositions = new Map();
|
|
544
626
|
let elkPathStrings = new Map();
|
|
@@ -554,34 +636,75 @@ export async function buildSubsystemGraph(doc, opts = {}) {
|
|
|
554
636
|
interLayerSpacing: 120,
|
|
555
637
|
preserveNodePositions: false,
|
|
556
638
|
edgeLabels: showEdgeLabels === false ? { enabled: false } : { enabled: true, placement: 'CENTER' },
|
|
557
|
-
groups:
|
|
558
|
-
id:
|
|
559
|
-
memberIds:
|
|
639
|
+
groups: layoutGroups.map((g) => ({
|
|
640
|
+
id: g.id,
|
|
641
|
+
memberIds: g.memberIds,
|
|
642
|
+
parentId: g.parentId,
|
|
560
643
|
})),
|
|
561
644
|
});
|
|
562
|
-
const
|
|
563
|
-
|
|
645
|
+
const builtGroupIds = new Set(result.groupBounds.keys());
|
|
646
|
+
// Parents before children — process frames, then nested module frames.
|
|
647
|
+
const processGroupNodes = [];
|
|
648
|
+
const moduleGroupNodes = [];
|
|
649
|
+
for (const g of layoutGroups) {
|
|
650
|
+
const bounds = result.groupBounds.get(g.id);
|
|
564
651
|
if (!bounds)
|
|
565
|
-
|
|
652
|
+
continue;
|
|
653
|
+
const parentId = g.parentId && builtGroupIds.has(g.parentId) ? g.parentId : undefined;
|
|
566
654
|
const group = {
|
|
567
|
-
id:
|
|
655
|
+
id: g.id,
|
|
568
656
|
type: 'subsystem-group',
|
|
569
657
|
position: { x: bounds.x, y: bounds.y },
|
|
570
658
|
width: Math.max(200, bounds.width),
|
|
571
659
|
height: Math.max(160, bounds.height),
|
|
572
|
-
|
|
660
|
+
...(parentId ? { parentId } : {}),
|
|
661
|
+
data: { region: g.region },
|
|
573
662
|
};
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
663
|
+
if (g.region.kind === 'process')
|
|
664
|
+
processGroupNodes.push(group);
|
|
665
|
+
else
|
|
666
|
+
moduleGroupNodes.push(group);
|
|
667
|
+
}
|
|
668
|
+
// Clear leaf parentIds that point at groups ELK dropped.
|
|
669
|
+
for (const n of result.nodes) {
|
|
670
|
+
if (n.type !== 'subsystem-component')
|
|
671
|
+
continue;
|
|
672
|
+
const parentId = n.parentId;
|
|
673
|
+
if (parentId && !builtGroupIds.has(parentId)) {
|
|
674
|
+
delete n.parentId;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
placedNodes = [
|
|
678
|
+
...processGroupNodes,
|
|
679
|
+
...moduleGroupNodes,
|
|
680
|
+
...result.nodes,
|
|
681
|
+
];
|
|
578
682
|
labelPositions = result.edgeLabelPositions;
|
|
579
683
|
elkPathStrings = result.edgePaths;
|
|
580
684
|
elkPathPoints = result.edgePathPoints;
|
|
581
685
|
}
|
|
582
686
|
catch (err) {
|
|
583
|
-
// Fall back to the (unpositioned) grid if ELK is unavailable
|
|
687
|
+
// Fall back to the (unpositioned) grid if ELK is unavailable — still
|
|
688
|
+
// emit multi-member frames so parentId targets exist.
|
|
584
689
|
console.warn('[subsystem-graph] ELK layout failed, using manual positions:', err);
|
|
690
|
+
const processGroupNodes = [];
|
|
691
|
+
const moduleGroupNodes = [];
|
|
692
|
+
for (const g of layoutGroups) {
|
|
693
|
+
const group = {
|
|
694
|
+
id: g.id,
|
|
695
|
+
type: 'subsystem-group',
|
|
696
|
+
position: { x: 0, y: 0 },
|
|
697
|
+
width: 400,
|
|
698
|
+
height: 300,
|
|
699
|
+
...(g.parentId ? { parentId: g.parentId } : {}),
|
|
700
|
+
data: { region: g.region },
|
|
701
|
+
};
|
|
702
|
+
if (g.region.kind === 'process')
|
|
703
|
+
processGroupNodes.push(group);
|
|
704
|
+
else
|
|
705
|
+
moduleGroupNodes.push(group);
|
|
706
|
+
}
|
|
707
|
+
placedNodes = [...processGroupNodes, ...moduleGroupNodes, ...nodes];
|
|
585
708
|
}
|
|
586
709
|
}
|
|
587
710
|
// Stamp ELK-computed label midpoints onto edge data so the overlay can
|