@principal-ai/subsystems-react 0.52.0 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/subsystem/C4Graph.d.ts +8 -1
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +470 -155
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +42 -1
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +75 -20
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +13 -4
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/c4.d.ts +27 -3
- package/dist/subsystem/c4.d.ts.map +1 -1
- package/dist/subsystem/c4.js +48 -0
- package/dist/subsystem/c4.js.map +1 -1
- package/dist/subsystem/model.d.ts +35 -245
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +11 -29
- package/dist/subsystem/model.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +26 -2
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +40 -8
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +4 -3
- package/src/stories/Subsystem/C4Graph/C4CardGrow.stories.tsx +125 -0
- package/src/stories/Subsystem/C4Graph/C4Component.stories.tsx +21 -11
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +1 -9
- package/src/stories/Subsystem/C4Graph/C4Transition.stories.tsx +131 -0
- package/src/subsystem/C4Graph.tsx +578 -198
- package/src/subsystem/C4NodeCard.test.ts +6 -3
- package/src/subsystem/C4NodeCard.tsx +158 -49
- package/src/subsystem/IssueList.category.test.ts +39 -0
- package/src/subsystem/IssueList.tsx +14 -3
- package/src/subsystem/c4.ts +74 -3
- package/src/subsystem/model.ts +62 -305
- package/src/utils/elkLayout.ts +60 -10
package/src/subsystem/model.ts
CHANGED
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* subgraphs, only cross-package edges leave the box, and shared seams
|
|
9
9
|
* (registries/barrels/facades) are edge targets, never member nodes.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Document types come from `@principal-ai/subsystems-core` (the portable
|
|
12
|
+
* standard's home); react layers display concerns (edges, regions, layout)
|
|
13
|
+
* on top.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import {
|
|
@@ -22,9 +23,7 @@ import { EDGE_LABEL_SIDE_PADDING, EDGE_ARROW_INSET } from '../utils/edgeLabel';
|
|
|
22
23
|
import type { GraphifyComponentDetail } from '../graphify';
|
|
23
24
|
import type {
|
|
24
25
|
SubsystemConstructDeclaration,
|
|
25
|
-
SubsystemComponentRemoval,
|
|
26
26
|
} from '@principal-ai/subsystems-core';
|
|
27
|
-
import type { SubsystemDeclarationRef } from './declarationRef';
|
|
28
27
|
import { purlOwnerName, purlRepoKey } from './paths';
|
|
29
28
|
|
|
30
29
|
/**
|
|
@@ -55,7 +54,6 @@ export type { SubsystemConstructDeclaration } from '@principal-ai/subsystems-cor
|
|
|
55
54
|
export type AnyComponentDetail =
|
|
56
55
|
| SubsystemConstructDeclaration
|
|
57
56
|
| GraphifyComponentDetail;
|
|
58
|
-
export type SubsystemDeclarationProvenance = 'verified' | 'authored';
|
|
59
57
|
|
|
60
58
|
/** One declared parameter in an agent-extracted (augmented) signature. */
|
|
61
59
|
export interface SubsystemSignatureParameter {
|
|
@@ -78,84 +76,60 @@ export interface SubsystemSignatureClaim {
|
|
|
78
76
|
returnType?: string;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
export type SubsystemComponentConstruct =
|
|
82
|
-
| 'class'
|
|
83
|
-
| 'function'
|
|
84
|
-
| 'method'
|
|
85
|
-
| 'interface'
|
|
86
|
-
| 'type_alias'
|
|
87
|
-
| 'enum'
|
|
88
|
-
| 'store'
|
|
89
|
-
| 'external'
|
|
90
|
-
| 'custom_entity';
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Semantic role — where the node sits in the topology, orthogonal to
|
|
94
|
-
* `construct` (what it is). Roles have *inherited* anatomy: an entry renders
|
|
95
|
-
* as its real code shape (function dispatcher, type contract); a service has
|
|
96
|
-
* no source at all (`construct: 'external'` + purl identity). Contrast with
|
|
97
|
-
* `construct: 'store'`, which introduces its own anatomy (the state block) —
|
|
98
|
-
* that is why store is a construct and not a role. Role nodes must stay anchored to real code: an
|
|
99
|
-
* `entry` is a boundary element (route dispatcher, message contract) carrying
|
|
100
|
-
* the wire address as identity; a `service` is an external system the process
|
|
101
|
-
* calls out to (identity via purl, no `process` — it belongs to no region).
|
|
102
|
-
*/
|
|
103
|
-
export type SubsystemComponentRole = 'entry' | 'service';
|
|
104
|
-
|
|
105
79
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
80
|
+
* Document vocabulary — imported from `@principal-ai/subsystems-core`, the
|
|
81
|
+
* portable standard's home. This package used to define a parallel copy
|
|
82
|
+
* ("self-contained until promoted"); core owns the types now, so react
|
|
83
|
+
* re-exports them by construction instead of hand-mirroring.
|
|
109
84
|
*/
|
|
110
|
-
|
|
85
|
+
import type {
|
|
86
|
+
SubsystemModelDocument,
|
|
87
|
+
SubsystemTrail,
|
|
88
|
+
SubsystemTrailStep,
|
|
89
|
+
SubsystemComponentRole,
|
|
90
|
+
SubsystemTrailMechanism,
|
|
91
|
+
SubsystemEdgeMechanism,
|
|
92
|
+
SubsystemConstruct as SubsystemComponentConstruct,
|
|
93
|
+
SubsystemComponent as CoreSubsystemComponent,
|
|
94
|
+
} from '@principal-ai/subsystems-core';
|
|
95
|
+
import {
|
|
96
|
+
getSubsystemProcessRegions as coreGetSubsystemProcessRegions,
|
|
97
|
+
getSubsystemModuleRegions as coreGetSubsystemModuleRegions,
|
|
98
|
+
} from '@principal-ai/subsystems-core';
|
|
111
99
|
|
|
100
|
+
export type {
|
|
101
|
+
SubsystemModelDocument,
|
|
102
|
+
SubsystemTrail,
|
|
103
|
+
SubsystemTrailStep,
|
|
104
|
+
SubsystemComponentRole,
|
|
105
|
+
SubsystemFramework,
|
|
106
|
+
SubsystemStereotype,
|
|
107
|
+
SubsystemEntityKind,
|
|
108
|
+
SubsystemTrailMechanism,
|
|
109
|
+
SubsystemEdgeMechanism,
|
|
110
|
+
SubsystemDeclToken,
|
|
111
|
+
SubsystemDeclTokenKind,
|
|
112
|
+
SubsystemDeclarationProvenance,
|
|
113
|
+
} from '@principal-ai/subsystems-core';
|
|
114
|
+
export type { SubsystemComponentConstruct };
|
|
112
115
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* `framework: 'react'` + `stereotype: 'component'`.
|
|
116
|
+
* A component node — the named unit, construct-tagged; `file` is its location.
|
|
117
|
+
* Core's portable shape plus the one field a model document never carries:
|
|
118
|
+
* `signatureAugmentation` is host-enrichment (populated when serving an
|
|
119
|
+
* enriched graph), never persisted.
|
|
118
120
|
*/
|
|
119
|
-
export
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| 'member'
|
|
129
|
-
| 'type'
|
|
130
|
-
| 'punctuation'
|
|
131
|
-
| 'string'
|
|
132
|
-
| 'newline';
|
|
133
|
-
|
|
134
|
-
export interface SubsystemDeclToken {
|
|
135
|
-
text: string;
|
|
136
|
-
kind: SubsystemDeclTokenKind;
|
|
137
|
-
/** Shiki/Pierre foreground when tokenized client-side; omitted on legacy wire tokens. */
|
|
138
|
-
color?: string;
|
|
121
|
+
export interface SubsystemComponent extends CoreSubsystemComponent {
|
|
122
|
+
/**
|
|
123
|
+
* Accepted agent-extracted signature (from the graphify augmentation store)
|
|
124
|
+
* confirming this function/method when Graphify has no usable signature
|
|
125
|
+
* edges. Populated by the host when serving an enriched graph — never
|
|
126
|
+
* persisted in the model document. Rendered as the declaration when the
|
|
127
|
+
* component has no own `declaration`; no marker distinguishes the source.
|
|
128
|
+
*/
|
|
129
|
+
signatureAugmentation?: SubsystemSignatureClaim;
|
|
139
130
|
}
|
|
140
131
|
|
|
141
|
-
/**
|
|
142
|
-
* Trail step mechanism — runtime seams with a `file:line` site.
|
|
143
|
-
*/
|
|
144
|
-
export type SubsystemTrailMechanism =
|
|
145
|
-
| 'calls'
|
|
146
|
-
| 'uses'
|
|
147
|
-
| 'feeds'
|
|
148
|
-
| 'produces'
|
|
149
|
-
| 'writes'
|
|
150
|
-
| 'reads'
|
|
151
|
-
| 'watches'
|
|
152
|
-
| 'registers-into';
|
|
153
|
-
|
|
154
|
-
/** Union for derived graph-edge styling — the trail step mechanism. */
|
|
155
|
-
export type SubsystemEdgeMechanism = SubsystemTrailMechanism;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Which edge source the canvas draws. The two sources are disjoint; a view
|
|
132
|
+
/** Which edge source the canvas draws. The two sources are disjoint; a view
|
|
159
133
|
* shows only edges (and their labels) from the selected source.
|
|
160
134
|
* - `graphify`: only graphify-native static edges (`imports`, `contains`, …)
|
|
161
135
|
* - `trails`: only trail step edges (`calls`, `feeds`, …)
|
|
@@ -203,155 +177,10 @@ export interface SubsystemGraphifyRelation {
|
|
|
203
177
|
context?: string;
|
|
204
178
|
}
|
|
205
179
|
|
|
206
|
-
/** A component node — the named unit, construct-tagged; `file` is its location. */
|
|
207
|
-
export interface SubsystemComponent {
|
|
208
|
-
/**
|
|
209
|
-
* Model-local stable alias, unique per model. Referenced by trail
|
|
210
|
-
* `from` / `to`; edges point at the alias, not the location.
|
|
211
|
-
* Code identity (for composed multi-model views) lives on
|
|
212
|
-
* `purl` + `file` + `symbol`, not here.
|
|
213
|
-
*/
|
|
214
|
-
alias: string;
|
|
215
|
-
name: string;
|
|
216
|
-
/**
|
|
217
|
-
* The node's construct — what it IS as a declaration (class, function,
|
|
218
|
-
* method, interface, type alias, enum, store, external), driving node
|
|
219
|
-
* anatomy, color, badge, and the verification strategy. Every construct
|
|
220
|
-
* anchors to a definition; runtime occurrences (variables, activations,
|
|
221
|
-
* instances) are NOT constructs — they belong to a future execution-mode
|
|
222
|
-
* graph whose occurrence nodes reference these definitions. Ontology:
|
|
223
|
-
* construct = what it is, framework + stereotype = which framework pattern
|
|
224
|
-
* it plays, role = where it sits, process = where it runs. Prefer
|
|
225
|
-
* `framework` + `stereotype` over inventing framework-specific constructs
|
|
226
|
-
* (a React component is still `construct: 'function'`).
|
|
227
|
-
*/
|
|
228
|
-
construct: SubsystemComponentConstruct;
|
|
229
|
-
/** Source location the component lives in (repo-root-relative path). */
|
|
230
|
-
file: string;
|
|
231
|
-
/** PURL identifying the repo or package this component lives in (for subgraph grouping). */
|
|
232
|
-
purl: string;
|
|
233
|
-
/**
|
|
234
|
-
* App/repo logo shown on the declaration card in place of the GitHub owner
|
|
235
|
-
* avatar (any image URL or data URI). Optional; typically one image stamped
|
|
236
|
-
* across the components of a repo.
|
|
237
|
-
*/
|
|
238
|
-
logo?: string;
|
|
239
|
-
/** One-line purpose shown on the node. */
|
|
240
|
-
purpose?: string;
|
|
241
|
-
/**
|
|
242
|
-
* Design / migration placeholder — participates in edges and flows but is
|
|
243
|
-
* not a live source declaration yet. Verification skips source checks until
|
|
244
|
-
* promoted (`proposed` cleared, `file` + `symbol` filled). Orthogonal to
|
|
245
|
-
* `construct` (intended shape) and `role` (topology).
|
|
246
|
-
*/
|
|
247
|
-
proposed?: boolean;
|
|
248
|
-
/**
|
|
249
|
-
* The claim was real but its source is gone upstream — deleted or renamed.
|
|
250
|
-
* The inverse of `proposed`; verification skips source checks the same way.
|
|
251
|
-
* Renders with a slate border and a `deprecated` badge that opens the removal
|
|
252
|
-
* provenance. Transitional — a cleanup pass removes the node.
|
|
253
|
-
*/
|
|
254
|
-
deprecated?: boolean;
|
|
255
|
-
/** Which commit removed this claim's source, and why. Present when deprecated. */
|
|
256
|
-
removedIn?: SubsystemComponentRemoval;
|
|
257
|
-
/**
|
|
258
|
-
* Semantic role — where the node sits in the topology (boundary element,
|
|
259
|
-
* external system), orthogonal to `construct` (what it is). Drives the
|
|
260
|
-
* glyph and the edge-pairing rules: boundary-crossing edges must terminate
|
|
261
|
-
* at an entry or a service. Retained state is NOT a role — it is
|
|
262
|
-
* `construct: 'store'` (state-block anatomy, `writes`/`reads`/`watches`
|
|
263
|
-
* inbound, `produces` outbound).
|
|
264
|
-
*/
|
|
265
|
-
role?: SubsystemComponentRole;
|
|
266
|
-
/**
|
|
267
|
-
* Framework that owns the stereotype vocabulary (e.g. `react`, `nestjs`).
|
|
268
|
-
* Orthogonal to `construct` — leave empty for language-only units.
|
|
269
|
-
*/
|
|
270
|
-
framework?: SubsystemFramework;
|
|
271
|
-
/**
|
|
272
|
-
* Framework pattern this declaration plays (e.g. `component`, `hook`).
|
|
273
|
-
* When set, the node badge prefers this label over the construct name so
|
|
274
|
-
* a React UI unit reads as "component" rather than "function".
|
|
275
|
-
*/
|
|
276
|
-
stereotype?: SubsystemStereotype;
|
|
277
|
-
/**
|
|
278
|
-
* Open-string entity kind for `construct: 'custom_entity'` nodes — what the
|
|
279
|
-
* actor/entity is (e.g. `Person`, `agent`, `queue`). Not code; there is no
|
|
280
|
-
* graphify anchor. Contrast with `construct` (what the declaration is) and
|
|
281
|
-
* `role` (where it sits topologically). Empty/unset for code constructs.
|
|
282
|
-
*/
|
|
283
|
-
entityKind?: string;
|
|
284
|
-
/**
|
|
285
|
-
* Authored color override for the node border + badges, when the themed
|
|
286
|
-
* construct color isn't wanted. Primarily for `construct: 'custom_entity'`
|
|
287
|
-
* (an author picks a fitting hue for a Person/agent/queue) — but usable on
|
|
288
|
-
* any construct. Any `#rrggbb` string; when set it wins over the
|
|
289
|
-
* construct-derived color, mirrors in the declaration panel. Leave unset to
|
|
290
|
-
* inherit the Pierre construct palette.
|
|
291
|
-
*/
|
|
292
|
-
color?: string;
|
|
293
|
-
/**
|
|
294
|
-
* Runtime process membership — which deployment unit this node is a
|
|
295
|
-
* member of (e.g. `principal-studio/host`, `principal-studio/renderer`). Nodes
|
|
296
|
-
* sharing a `process` are drawn inside one boundary region; nodes without
|
|
297
|
-
* one sit outside every process boundary (external actors, services,
|
|
298
|
-
* libraries). Orthogonal to `module` (source-file frame).
|
|
299
|
-
*/
|
|
300
|
-
process?: string;
|
|
301
|
-
/**
|
|
302
|
-
* Source-module membership — which file/module this export belongs to
|
|
303
|
-
* (e.g. `src/session/transcript.ts`). Nodes sharing a `module` are drawn
|
|
304
|
-
* inside one boundary frame. Prefer this over inventing a module construct:
|
|
305
|
-
* each export keeps its real construct (`function` / `class` / …) and the
|
|
306
|
-
* file reads as a frame. Orthogonal to `process` (runtime deployment).
|
|
307
|
-
* When both are set, the module frame is the component's parent (finer
|
|
308
|
-
* grain); process framing still groups siblings that share a process.
|
|
309
|
-
*/
|
|
310
|
-
module?: string;
|
|
311
|
-
/** A symbol this component exposes / is (the node's identity). */
|
|
312
|
-
symbol?: string;
|
|
313
|
-
/**
|
|
314
|
-
* Semantic layer/phase for the layout (e.g. `1` = input, `2` = processing,
|
|
315
|
-
* `3` = output). When set, ELK places the component in this layer so the
|
|
316
|
-
* graph reads as a left-to-right pipeline and *conveys the idea* rather than
|
|
317
|
-
* letting ELK guess the order.
|
|
318
|
-
*/
|
|
319
|
-
layer?: number;
|
|
320
|
-
/**
|
|
321
|
-
* Structured declaration shape of the construct (params, members, type
|
|
322
|
-
* buckets, …). Single source of truth for the click panel — authored by
|
|
323
|
-
* agents/CLI or filled by verified graphify capture. Discriminated by kind.
|
|
324
|
-
*/
|
|
325
|
-
declaration?: SubsystemConstructDeclaration;
|
|
326
|
-
/**
|
|
327
|
-
* @deprecated No longer used — verification now tracks at the model level
|
|
328
|
-
* via `verifiedAtCommits`. Keeping the field temporarily for migration.
|
|
329
|
-
*/
|
|
330
|
-
declarationProvenance?: SubsystemDeclarationProvenance;
|
|
331
|
-
/**
|
|
332
|
-
* Pre-tokenized declaration for the detail panel. When present, the
|
|
333
|
-
* renderer skips client-side tokenization. Tokens are language-agnostic;
|
|
334
|
-
* a different language just needs a different tokenizer and text joiner.
|
|
335
|
-
*/
|
|
336
|
-
tokens?: SubsystemDeclToken[];
|
|
337
|
-
/**
|
|
338
|
-
* Accepted agent-extracted signature (from the graphify augmentation store)
|
|
339
|
-
* confirming this function/method when Graphify has no usable signature
|
|
340
|
-
* edges. Populated by the host when serving an enriched graph — never
|
|
341
|
-
* persisted in the model document. Rendered as the declaration when the
|
|
342
|
-
* component has no own `declaration`; no marker distinguishes the source.
|
|
343
|
-
*/
|
|
344
|
-
signatureAugmentation?: SubsystemSignatureClaim;
|
|
345
|
-
/**
|
|
346
|
-
* Anchored declaration location: graphify start line + hash of that line's
|
|
347
|
-
* content at capture time. Populated by verify when an exact anchor resolves.
|
|
348
|
-
*/
|
|
349
|
-
declarationRef?: SubsystemDeclarationRef;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
180
|
/**
|
|
353
181
|
* Derived / display graph edge used by renderers. Built from trail steps
|
|
354
182
|
* (and graphify-native relations) — not authored as its own document field.
|
|
183
|
+
* Richer than core's minimal edge: this is the canvas's drawing shape.
|
|
355
184
|
*/
|
|
356
185
|
export interface SubsystemComponentEdge {
|
|
357
186
|
id: string;
|
|
@@ -359,7 +188,7 @@ export interface SubsystemComponentEdge {
|
|
|
359
188
|
to: string; // component alias or external target label
|
|
360
189
|
/**
|
|
361
190
|
* The edge verb. For `provenance: 'subsystem'` (the default) this is a
|
|
362
|
-
* `
|
|
191
|
+
* `SubsystemTrailMechanism`; for `provenance: 'graphify'` it is the raw
|
|
363
192
|
* graphify relation. Typed as `string` because the display edge is a derived
|
|
364
193
|
* structure and graphify's verb set is open — the trail vocabulary
|
|
365
194
|
* (`SubsystemTrailMechanism`) stays closed.
|
|
@@ -381,60 +210,6 @@ export interface SubsystemComponentEdge {
|
|
|
381
210
|
context?: string;
|
|
382
211
|
}
|
|
383
212
|
|
|
384
|
-
/**
|
|
385
|
-
* A single runtime step — `from`/`to`/`mechanism` plus the exact `file:line`
|
|
386
|
-
* where that seam fires for a trail.
|
|
387
|
-
*/
|
|
388
|
-
export interface SubsystemTrailStep {
|
|
389
|
-
from: string;
|
|
390
|
-
to: string;
|
|
391
|
-
mechanism: SubsystemTrailMechanism;
|
|
392
|
-
/** Repo-root-relative path of the file where the seam fires. */
|
|
393
|
-
file: string;
|
|
394
|
-
/** 1-based line of the site within `file`. */
|
|
395
|
-
line: number;
|
|
396
|
-
/**
|
|
397
|
-
* File-anchored purl of the seam site (e.g.
|
|
398
|
-
* `pkg:github/owner/name#path/to/file.ts`), mirroring component `purl`.
|
|
399
|
-
* Required: readers resolve the checkout from this instead of guessing
|
|
400
|
-
* the repo from the step's endpoint components.
|
|
401
|
-
*/
|
|
402
|
-
purl: string;
|
|
403
|
-
/**
|
|
404
|
-
* Frame name for this step — the function/method on the stack at the site.
|
|
405
|
-
* Required; the Trails list shows this instead of a bare
|
|
406
|
-
* mechanism + filename fallback.
|
|
407
|
-
*/
|
|
408
|
-
symbol: string;
|
|
409
|
-
/**
|
|
410
|
-
* Planned seam — the step describes intended behavior through code that is
|
|
411
|
-
* not written yet. Orthogonal to (and redundant with) the endpoints' own
|
|
412
|
-
* `proposed` flags: a step is proposed when it says so OR either endpoint
|
|
413
|
-
* is proposed. Verification skips source checks either way, and viewers
|
|
414
|
-
* tint it exactly like a proposed node.
|
|
415
|
-
*/
|
|
416
|
-
proposed?: boolean;
|
|
417
|
-
/**
|
|
418
|
-
* Free-text note anchored to this step's site line. Optional — informative
|
|
419
|
-
* only, never verified against source; the Pierre trail code view
|
|
420
|
-
* surfaces it in the annotation column next to the highlighted line.
|
|
421
|
-
*/
|
|
422
|
-
annotation?: string;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/** An ordered runtime trail — one named behavior story. */
|
|
426
|
-
export interface SubsystemTrail {
|
|
427
|
-
id: string;
|
|
428
|
-
title: string;
|
|
429
|
-
steps: SubsystemTrailStep[];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export interface SubsystemModelDocument {
|
|
433
|
-
components: SubsystemComponent[];
|
|
434
|
-
/** Ordered runtime trails (one per named behavior). */
|
|
435
|
-
trails?: SubsystemTrail[];
|
|
436
|
-
}
|
|
437
|
-
|
|
438
213
|
/** Stable id for a derived graph edge from a trail step. */
|
|
439
214
|
export function derivedGraphEdgeId(
|
|
440
215
|
from: string,
|
|
@@ -819,47 +594,29 @@ function packageRegionLabel(packageKey: string): string {
|
|
|
819
594
|
|
|
820
595
|
/**
|
|
821
596
|
* Derive process boundary regions — one per distinct non-empty `process`
|
|
822
|
-
* value, in first-appearance order.
|
|
597
|
+
* value, in first-appearance order. Wraps core's rollup, adding the frame
|
|
598
|
+
* `kind` the canvas reads.
|
|
823
599
|
*/
|
|
824
600
|
export function getSubsystemRegions(
|
|
825
601
|
doc: Pick<SubsystemModelDocument, 'components'>,
|
|
826
602
|
): SubsystemProcessRegion[] {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const p = c.process?.trim();
|
|
830
|
-
if (!p) continue;
|
|
831
|
-
const list = byProcess.get(p) ?? [];
|
|
832
|
-
list.push(c.alias);
|
|
833
|
-
byProcess.set(p, list);
|
|
834
|
-
}
|
|
835
|
-
return [...byProcess.entries()].map(([key, memberAliases]) => ({
|
|
603
|
+
return coreGetSubsystemProcessRegions(doc).map((r) => ({
|
|
604
|
+
...r,
|
|
836
605
|
kind: 'process' as const,
|
|
837
|
-
key,
|
|
838
|
-
label: key,
|
|
839
|
-
memberAliases,
|
|
840
606
|
}));
|
|
841
607
|
}
|
|
842
608
|
|
|
843
609
|
/**
|
|
844
610
|
* Derive module boundary regions — one per distinct non-empty `module`
|
|
845
611
|
* value, in first-appearance order. Label is the module path (file).
|
|
612
|
+
* Wraps core's rollup, adding the frame `kind` the canvas reads.
|
|
846
613
|
*/
|
|
847
614
|
export function getSubsystemModuleRegions(
|
|
848
615
|
doc: Pick<SubsystemModelDocument, 'components'>,
|
|
849
616
|
): SubsystemProcessRegion[] {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const m = c.module?.trim();
|
|
853
|
-
if (!m) continue;
|
|
854
|
-
const list = byModule.get(m) ?? [];
|
|
855
|
-
list.push(c.alias);
|
|
856
|
-
byModule.set(m, list);
|
|
857
|
-
}
|
|
858
|
-
return [...byModule.entries()].map(([key, memberAliases]) => ({
|
|
617
|
+
return coreGetSubsystemModuleRegions(doc).map((r) => ({
|
|
618
|
+
...r,
|
|
859
619
|
kind: 'module' as const,
|
|
860
|
-
key,
|
|
861
|
-
label: key,
|
|
862
|
-
memberAliases,
|
|
863
620
|
}));
|
|
864
621
|
}
|
|
865
622
|
|
|
@@ -1933,7 +1690,7 @@ export function nodeMinWidthForBadges(component: {
|
|
|
1933
1690
|
* then refines with compound layout.
|
|
1934
1691
|
*/
|
|
1935
1692
|
export function convertSubsystemToNodes(
|
|
1936
|
-
doc: SubsystemModelDocument,
|
|
1693
|
+
doc: Pick<SubsystemModelDocument, 'components' | 'trails'>,
|
|
1937
1694
|
opts: { maxNodeWidth?: number } = {},
|
|
1938
1695
|
): SubsystemGraphNode[] {
|
|
1939
1696
|
const { maxNodeWidth } = opts;
|
|
@@ -2024,7 +1781,7 @@ export function convertSubsystemToGroups(
|
|
|
2024
1781
|
* relationship is visible without a member node.
|
|
2025
1782
|
*/
|
|
2026
1783
|
export function convertSubsystemToEdges(
|
|
2027
|
-
doc: SubsystemModelDocument,
|
|
1784
|
+
doc: Pick<SubsystemModelDocument, 'components' | 'trails'>,
|
|
2028
1785
|
graphifyRelations: readonly SubsystemGraphifyRelation[] = [],
|
|
2029
1786
|
): SubsystemGraphEdge[] {
|
|
2030
1787
|
const compAliases = new Set(doc.components.map((c) => c.alias));
|
|
@@ -2086,7 +1843,7 @@ export function subsystemGraphLayoutKey(
|
|
|
2086
1843
|
* layered with minimized crossings.
|
|
2087
1844
|
*/
|
|
2088
1845
|
export async function buildSubsystemGraph(
|
|
2089
|
-
doc: SubsystemModelDocument,
|
|
1846
|
+
doc: Pick<SubsystemModelDocument, 'components' | 'trails'>,
|
|
2090
1847
|
opts: {
|
|
2091
1848
|
maxNodeWidth?: number;
|
|
2092
1849
|
showEdgeLabels?: boolean;
|
package/src/utils/elkLayout.ts
CHANGED
|
@@ -150,9 +150,16 @@ export interface ElkLayoutOptions {
|
|
|
150
150
|
* decoration drawn around wherever the members landed.
|
|
151
151
|
*
|
|
152
152
|
* Only for groups whose members need no parent-relative positioning (C4's
|
|
153
|
-
* system frame).
|
|
153
|
+
* system frame).
|
|
154
|
+
*
|
|
155
|
+
* A set names specific groups to flatten, leaving the rest as real ELK
|
|
156
|
+
* parents — the drill-down needs this, because the opened container must be
|
|
157
|
+
* *sized by ELK to fit its nested components* while the system frame around
|
|
158
|
+
* it stays decorative. Descendants of a flattened group are flattened too.
|
|
159
|
+
*
|
|
160
|
+
* @default false
|
|
154
161
|
*/
|
|
155
|
-
flatGroups?: boolean
|
|
162
|
+
flatGroups?: boolean | ReadonlySet<string>;
|
|
156
163
|
|
|
157
164
|
/**
|
|
158
165
|
* ELK partition per node id. Nodes are banded by `partition` — ELK places all
|
|
@@ -520,6 +527,21 @@ export interface CompoundGroupDef {
|
|
|
520
527
|
id: string;
|
|
521
528
|
memberIds: string[];
|
|
522
529
|
minWidth?: number;
|
|
530
|
+
/**
|
|
531
|
+
* ELK partition for this group. Partitioning is activated on the root and
|
|
532
|
+
* inherited, so a compound parent must be told its band explicitly —
|
|
533
|
+
* otherwise it falls to partition 0 and lays out away from the siblings it is
|
|
534
|
+
* supposed to sit among (which, for a flat frame around those siblings, drags
|
|
535
|
+
* the frame across an unrelated band).
|
|
536
|
+
*/
|
|
537
|
+
partition?: number;
|
|
538
|
+
/**
|
|
539
|
+
* Space reserved above the first child, in px. Set it when the parent draws
|
|
540
|
+
* its own chrome at the top (C4's grown container draws a label slot and a
|
|
541
|
+
* technology row) so ELK still owns the fit — it pads the parent by this and
|
|
542
|
+
* the children land below the chrome rather than under it.
|
|
543
|
+
*/
|
|
544
|
+
padTop?: number;
|
|
523
545
|
}
|
|
524
546
|
|
|
525
547
|
/** Which groups ELK builds, and which it drops. */
|
|
@@ -528,7 +550,7 @@ export interface CompoundGroupPlan {
|
|
|
528
550
|
* Built groups in build order — a group's children always appear before it,
|
|
529
551
|
* so callers can map ids to shells in one forward pass.
|
|
530
552
|
*/
|
|
531
|
-
built: Array<{ id: string; childIds: string[]; minWidth?: number }>;
|
|
553
|
+
built: Array<{ id: string; childIds: string[]; minWidth?: number; padTop?: number; partition?: number }>;
|
|
532
554
|
/**
|
|
533
555
|
* Dropped groups. Their members are promoted into the nearest built
|
|
534
556
|
* ancestor, so callers must clear those members' `parentId`.
|
|
@@ -864,11 +886,27 @@ export async function computeElkLayout(
|
|
|
864
886
|
// and the frame is synthesized from the members' bounds after layout. This
|
|
865
887
|
// keeps every edge on one spacing instead of inheriting a nested frame's.
|
|
866
888
|
const flatGroupIds = new Set<string>();
|
|
867
|
-
if (flatGroups) {
|
|
889
|
+
if (flatGroups === true) {
|
|
868
890
|
for (const g of plan.built) flatGroupIds.add(g.id);
|
|
891
|
+
} else if (flatGroups) {
|
|
892
|
+
for (const g of plan.built) if (flatGroups.has(g.id)) flatGroupIds.add(g.id);
|
|
893
|
+
// A group inside a flat frame can't be an ELK parent either — its members
|
|
894
|
+
// lay out at the root alongside the frame's other members.
|
|
895
|
+
let grew = true;
|
|
896
|
+
while (grew) {
|
|
897
|
+
grew = false;
|
|
898
|
+
for (const g of plan.built) {
|
|
899
|
+
if (flatGroupIds.has(g.id)) continue;
|
|
900
|
+
const parentId = groupById.get(g.id)?.parentId;
|
|
901
|
+
if (parentId && flatGroupIds.has(parentId)) {
|
|
902
|
+
flatGroupIds.add(g.id);
|
|
903
|
+
grew = true;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
869
907
|
}
|
|
870
908
|
for (const g of plan.built) {
|
|
871
|
-
if (
|
|
909
|
+
if (flatGroupIds.has(g.id)) continue;
|
|
872
910
|
const children: ElkNode[] = [];
|
|
873
911
|
for (const cid of g.childIds) {
|
|
874
912
|
const leaf = elkById.get(cid);
|
|
@@ -878,14 +916,26 @@ export async function computeElkLayout(
|
|
|
878
916
|
if (nested) children.push(nested);
|
|
879
917
|
}
|
|
880
918
|
}
|
|
919
|
+
const sizeOptions: Record<string, string> = {};
|
|
920
|
+
if (g.minWidth != null) {
|
|
921
|
+
// MINIMUM_SIZE, not a forced size: ELK grows the parent past it when the
|
|
922
|
+
// children need more, but never shrinks it below it.
|
|
923
|
+
sizeOptions['elk.nodeSize.constraints'] = 'MINIMUM_SIZE';
|
|
924
|
+
sizeOptions['elk.nodeSize.minimum'] = `(${g.minWidth},0)`;
|
|
925
|
+
}
|
|
926
|
+
if (g.padTop != null) {
|
|
927
|
+
// Reserving the parent's own chrome. Children then start below it, and
|
|
928
|
+
// the parent's height still comes from ELK's fit.
|
|
929
|
+
sizeOptions['elk.padding'] = `[top=${g.padTop},left=12,bottom=12,right=12]`;
|
|
930
|
+
}
|
|
931
|
+
if (g.partition != null) sizeOptions['partition'] = String(g.partition);
|
|
881
932
|
builtGroups.set(g.id, {
|
|
882
933
|
id: g.id,
|
|
883
934
|
children,
|
|
884
|
-
layoutOptions:
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
},
|
|
935
|
+
layoutOptions:
|
|
936
|
+
Object.keys(sizeOptions).length > 0
|
|
937
|
+
? { ...compoundLayoutOptions, ...sizeOptions }
|
|
938
|
+
: compoundLayoutOptions,
|
|
889
939
|
});
|
|
890
940
|
}
|
|
891
941
|
|