@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
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Prototype test case: feed it a `C4Model` from `toC4()` and it draws the
|
|
5
5
|
* system as a compound frame, its containers (or components) as boxes, and
|
|
6
|
-
* externals/actors outside the system.
|
|
7
|
-
* edges
|
|
8
|
-
* the source components it rolled up.
|
|
6
|
+
* externals/actors outside the system. `walkthrough` hops render as flow
|
|
7
|
+
* edges. Click a box to list the source components it rolled up.
|
|
9
8
|
*
|
|
10
9
|
* Same interaction language as the other graphs: React Flow + ELK + the shared
|
|
11
10
|
* `GRAPH_NAV_PROPS` chrome. Deliberately small — no measurement passes, no
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SubsystemIssueList — the diagnostics sidebar list.
|
|
3
3
|
*
|
|
4
4
|
* A catalogue of verification issues (audit findings), grouped by *target* so
|
|
5
|
-
* every broken component /
|
|
5
|
+
* every broken component / module / flow clusters together. Each
|
|
6
6
|
* card is severity-colored, names its kind, states the message, and offers the
|
|
7
7
|
* deterministic fix inline when one exists.
|
|
8
8
|
*
|
|
@@ -38,7 +38,6 @@ export type SubsystemIssueSeverity = 'error' | 'info';
|
|
|
38
38
|
|
|
39
39
|
export type SubsystemIssueTargetKind =
|
|
40
40
|
| 'component'
|
|
41
|
-
| 'relation'
|
|
42
41
|
| 'module'
|
|
43
42
|
| 'walkthrough'
|
|
44
43
|
| 'step'
|
|
@@ -48,8 +47,8 @@ export type SubsystemIssueTargetKind =
|
|
|
48
47
|
/**
|
|
49
48
|
* The four verification layers, in order. Mirrors the docs' progression:
|
|
50
49
|
* constructs → static topology → dynamic topology → walkthrough.
|
|
51
|
-
* Static topology =
|
|
52
|
-
*
|
|
50
|
+
* Static topology = package/module (containment); dynamic topology = process
|
|
51
|
+
* (runtime).
|
|
53
52
|
*/
|
|
54
53
|
export type SubsystemIssueCategory =
|
|
55
54
|
| 'repo'
|
|
@@ -90,7 +89,7 @@ export const SUBSYSTEM_ISSUE_CATEGORY_LABEL: Record<
|
|
|
90
89
|
walkthrough: 'Walkthrough',
|
|
91
90
|
};
|
|
92
91
|
|
|
93
|
-
/** Default `kind` → layer.
|
|
92
|
+
/** Default `kind` → layer. Module containment is static; process nest is dynamic. */
|
|
94
93
|
const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
|
|
95
94
|
missing_file: 'construct',
|
|
96
95
|
symbol_ambiguous: 'construct',
|
|
@@ -102,10 +101,7 @@ const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
|
|
|
102
101
|
signature_unconfirmed: 'construct',
|
|
103
102
|
repo_unresolved: 'repo',
|
|
104
103
|
graphify_unavailable: 'repo',
|
|
105
|
-
|
|
106
|
-
topology_import_unconfirmed: 'static-topology',
|
|
107
|
-
topology_relation_unconfirmed: 'static-topology',
|
|
108
|
-
boundary_module_file_mismatch: 'dynamic-topology',
|
|
104
|
+
boundary_module_file_mismatch: 'static-topology',
|
|
109
105
|
boundary_process_nest_disagree: 'dynamic-topology',
|
|
110
106
|
walkthrough: 'walkthrough',
|
|
111
107
|
};
|
|
@@ -189,7 +185,7 @@ export interface SubsystemIssueTarget {
|
|
|
189
185
|
id?: string;
|
|
190
186
|
/** Display label (component name, `from → to`, module path, flow title). */
|
|
191
187
|
label: string;
|
|
192
|
-
/** Optional sub-label (
|
|
188
|
+
/** Optional sub-label (step number, …). */
|
|
193
189
|
detail?: string;
|
|
194
190
|
/**
|
|
195
191
|
* For a `step` target: the walkthrough id, plus a 0-based index into that
|
|
@@ -231,8 +227,8 @@ export function humanizeIssueKind(kind: string): string {
|
|
|
231
227
|
* Curated labels. Construct-layer findings stay fully qualified ("Construct
|
|
232
228
|
* declaration file missing", "Construct type mismatch") rather than relying on
|
|
233
229
|
* the category header — this UI is new and the extra words read clearer.
|
|
234
|
-
*
|
|
235
|
-
*
|
|
230
|
+
* Boundary findings drop the `boundary_` prefix since the layer already
|
|
231
|
+
* scopes them. Explicit `issue.kindLabel` wins.
|
|
236
232
|
*/
|
|
237
233
|
const KIND_LABEL: Record<string, string> = {
|
|
238
234
|
missing_file: 'Construct declaration file missing',
|
|
@@ -245,9 +241,6 @@ const KIND_LABEL: Record<string, string> = {
|
|
|
245
241
|
signature_unconfirmed: 'Construct signature unconfirmed',
|
|
246
242
|
repo_unresolved: 'No repository clone',
|
|
247
243
|
graphify_unavailable: 'Graphify cache unavailable',
|
|
248
|
-
topology_broken_endpoint: 'Construct relationship broken',
|
|
249
|
-
topology_import_unconfirmed: 'Import unconfirmed',
|
|
250
|
-
topology_relation_unconfirmed: 'Relationship unconfirmed',
|
|
251
244
|
boundary_module_file_mismatch: 'Declaration file outside module',
|
|
252
245
|
boundary_process_nest_disagree: 'Module spans multiple process contexts',
|
|
253
246
|
walkthrough: 'Step issue',
|
|
@@ -261,8 +254,8 @@ export function issueKindLabel(issue: SubsystemIssue): string {
|
|
|
261
254
|
/**
|
|
262
255
|
* Display order within the list, by verification pipeline — not severity.
|
|
263
256
|
* Mirrors the per-component check order (file → symbol → declaration → type →
|
|
264
|
-
* signature), then
|
|
265
|
-
* remaining layers. Within a stage, "unconfirmed" precedes "mismatch": you
|
|
257
|
+
* signature), then the boundary layers (module membership → process nest) and
|
|
258
|
+
* the remaining layers. Within a stage, "unconfirmed" precedes "mismatch": you
|
|
266
259
|
* reach a verdict before you can find it wrong.
|
|
267
260
|
*/
|
|
268
261
|
const KIND_ORDER: Record<string, number> = {
|
|
@@ -278,12 +271,9 @@ const KIND_ORDER: Record<string, number> = {
|
|
|
278
271
|
construct_mismatch: 15,
|
|
279
272
|
signature_unconfirmed: 16,
|
|
280
273
|
signature_mismatch: 17,
|
|
281
|
-
// Static topology —
|
|
282
|
-
topology_broken_endpoint: 20,
|
|
283
|
-
topology_relation_unconfirmed: 21,
|
|
284
|
-
topology_import_unconfirmed: 22,
|
|
274
|
+
// Static topology — package/module membership
|
|
285
275
|
boundary_module_file_mismatch: 23,
|
|
286
|
-
// Runtime topology
|
|
276
|
+
// Runtime topology — process containment
|
|
287
277
|
boundary_process_nest_disagree: 30,
|
|
288
278
|
// Walkthrough
|
|
289
279
|
walkthrough: 40,
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
deriveGraphEdges,
|
|
40
40
|
isConstructsOnlyModel,
|
|
41
41
|
moduleGroupNodeId,
|
|
42
|
-
isRelationMechanism,
|
|
43
42
|
isWalkthroughMechanism,
|
|
44
43
|
edgeColor,
|
|
45
44
|
MECHANISM_DESCRIPTIONS,
|
|
@@ -52,7 +51,6 @@ import {
|
|
|
52
51
|
type SubsystemGraphifyRelation,
|
|
53
52
|
type SubsystemNodeIssue,
|
|
54
53
|
type SubsystemRegionIssue,
|
|
55
|
-
type SubsystemRelation,
|
|
56
54
|
type SubsystemWalkthrough,
|
|
57
55
|
} from './model';
|
|
58
56
|
import { ConstructsCatalog } from './ConstructsCatalog';
|
|
@@ -182,8 +180,6 @@ function writeViewState(
|
|
|
182
180
|
|
|
183
181
|
export interface SubsystemComponentGraphProps {
|
|
184
182
|
components: SubsystemComponent[];
|
|
185
|
-
/** Topology relations (structural / module / type). Display edges are derived with walkthrough hops. */
|
|
186
|
-
relations: SubsystemRelation[];
|
|
187
183
|
/**
|
|
188
184
|
* Ordered runtime walkthroughs — one walkthrough per flow. When present the
|
|
189
185
|
* sidebar's bottom half offers a Files/Walkthroughs toggle:
|
|
@@ -198,7 +194,7 @@ export interface SubsystemComponentGraphProps {
|
|
|
198
194
|
* graphify-native relations (raw static-graph edges: `imports`, `contains`,
|
|
199
195
|
* `re_exports`, …). Display-only, drawn with the separate
|
|
200
196
|
* `GRAPHIFY_RELATION_COLOR` palette so they read as derived facts, distinct
|
|
201
|
-
* from authored subsystem mechanisms. Visible in the `
|
|
197
|
+
* from authored subsystem mechanisms. Visible in the `graphify` edge view.
|
|
202
198
|
*/
|
|
203
199
|
graphifyRelations?: readonly SubsystemGraphifyRelation[];
|
|
204
200
|
/**
|
|
@@ -239,16 +235,15 @@ export interface SubsystemComponentGraphProps {
|
|
|
239
235
|
*/
|
|
240
236
|
showSingletonFrames?: boolean;
|
|
241
237
|
/**
|
|
242
|
-
* Which edge
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* - `
|
|
238
|
+
* Which edge source the canvas draws. The graphify and walkthrough sources
|
|
239
|
+
* are disjoint, so a graph carrying both shows one or the other — never
|
|
240
|
+
* both. Edges outside the view are hidden (labels go too).
|
|
241
|
+
* - `graphify`: graphify-native static edges (`imports`, `contains`, …)
|
|
246
242
|
* - `walkthroughs`: walkthrough hop edges (`calls`, `feeds`, …), including
|
|
247
243
|
* step numbers when a flow is focused/hovered
|
|
248
244
|
* Leave unset to let the sidebar's Files / Walkthroughs tab drive it: Files
|
|
249
|
-
* draws
|
|
250
|
-
* tabs, defaults to `walkthroughs
|
|
251
|
-
* no relations), otherwise `relations`.
|
|
245
|
+
* draws graphify edges, Walkthroughs draws runtime hops. Without visible
|
|
246
|
+
* tabs, defaults to `walkthroughs`.
|
|
252
247
|
*/
|
|
253
248
|
edgeView?: SubsystemEdgeView;
|
|
254
249
|
/** Subsystem title displayed in the sidebar. */
|
|
@@ -348,7 +343,7 @@ export interface SubsystemComponentGraphProps {
|
|
|
348
343
|
/**
|
|
349
344
|
* Click an issue. The graph first focuses the target on the canvas itself —
|
|
350
345
|
* a component target is selected and framed (any edge / walkthrough focus
|
|
351
|
-
* that would hide it is cleared),
|
|
346
|
+
* that would hide it is cleared), and a
|
|
352
347
|
* module target frames that boundary frame — then this fires so the host can
|
|
353
348
|
* open its own detail. Collapsing the card again reverses that (deselect +
|
|
354
349
|
* zoom out). Other target kinds (flow / repo) have no node to frame and just
|
|
@@ -522,7 +517,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
|
|
|
522
517
|
measured: { w: number; h: number } | null;
|
|
523
518
|
}
|
|
524
519
|
|
|
525
|
-
function Inner({ components,
|
|
520
|
+
function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, walkthroughFocusDurationMs = 300, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }: InnerProps) {
|
|
526
521
|
const { theme } = useTheme();
|
|
527
522
|
const { fitView, fitBounds } = useReactFlow();
|
|
528
523
|
const viewport = useViewport();
|
|
@@ -530,8 +525,8 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
530
525
|
// graph is its own tab/mount, so `persistKey` is stable for a mount.
|
|
531
526
|
const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
|
|
532
527
|
const graphEdges = useMemo(
|
|
533
|
-
() => deriveGraphEdges({
|
|
534
|
-
[
|
|
528
|
+
() => deriveGraphEdges({ walkthroughs }),
|
|
529
|
+
[walkthroughs],
|
|
535
530
|
);
|
|
536
531
|
const [built, setBuilt] = useState<{
|
|
537
532
|
nodes: Node[];
|
|
@@ -586,27 +581,20 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
586
581
|
const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>(
|
|
587
582
|
() => persisted.diagnosticsTab ?? 'issues',
|
|
588
583
|
);
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
// visible; it reverts when the focus is undone (see `unfocusIssueTarget`) or
|
|
592
|
-
// when the diagnostics list closes. Null = use the host-pinned / tab view.
|
|
593
|
-
const [edgeViewOverride, setEdgeViewOverride] = useState<SubsystemEdgeView | null>(
|
|
594
|
-
null,
|
|
595
|
-
);
|
|
596
|
-
// One edge vocabulary at a time. When the caller doesn't pick, the sidebar's
|
|
597
|
-
// Files / Walkthroughs tab picks: Files draws topology relation edges,
|
|
584
|
+
// One edge source at a time. When the caller doesn't pick, the sidebar's
|
|
585
|
+
// Files / Walkthroughs tab picks: Files draws graphify static edges,
|
|
598
586
|
// Walkthroughs draws runtime hop edges. Without visible tabs (no walkthroughs,
|
|
599
|
-
// or a sidebar-less embed) fall back to
|
|
600
|
-
// to walkthrough edges rather than empty, everything else to relations.
|
|
587
|
+
// or a sidebar-less embed) fall back to walkthrough edges.
|
|
601
588
|
const sidebarTabsVisible = !hideSidebar && (walkthroughs?.length ?? 0) > 0;
|
|
602
589
|
const resolvedEdgeView: SubsystemEdgeView =
|
|
603
|
-
edgeViewOverride ??
|
|
604
590
|
edgeView ??
|
|
605
591
|
(sidebarTabsVisible
|
|
606
|
-
? (sidebarView === 'walkthroughs' ? 'walkthroughs' : '
|
|
607
|
-
:
|
|
592
|
+
? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
|
|
593
|
+
: (walkthroughs?.length ?? 0) > 0
|
|
608
594
|
? 'walkthroughs'
|
|
609
|
-
:
|
|
595
|
+
: (graphifyRelations?.length ?? 0) > 0
|
|
596
|
+
? 'graphify'
|
|
597
|
+
: 'walkthroughs');
|
|
610
598
|
// Walkthrough flows the user has expanded (via the title row). Closed by
|
|
611
599
|
// default so a graph with several flows doesn't dump every step list at once.
|
|
612
600
|
// Restored from the persisted view state when a `persistKey` is set.
|
|
@@ -807,15 +795,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
807
795
|
// The pane stays hidden until Pass 2 completes. Key off layout-affecting
|
|
808
796
|
// fields only — declarationRef updates after verify must not re-run ELK.
|
|
809
797
|
const layoutKey = useMemo(
|
|
810
|
-
() => subsystemGraphLayoutKey({ components,
|
|
811
|
-
[components,
|
|
798
|
+
() => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }),
|
|
799
|
+
[components, walkthroughs, graphifyRelations],
|
|
812
800
|
);
|
|
813
801
|
const componentsRef = useRef(components);
|
|
814
|
-
const relationsRef = useRef(relations);
|
|
815
802
|
const walkthroughsRef = useRef(walkthroughs);
|
|
816
803
|
const graphifyRelationsRef = useRef(graphifyRelations);
|
|
817
804
|
componentsRef.current = components;
|
|
818
|
-
relationsRef.current = relations;
|
|
819
805
|
walkthroughsRef.current = walkthroughs;
|
|
820
806
|
graphifyRelationsRef.current = graphifyRelations;
|
|
821
807
|
|
|
@@ -836,7 +822,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
836
822
|
pass2GenRef.current += 1;
|
|
837
823
|
const doc = {
|
|
838
824
|
components: componentsRef.current,
|
|
839
|
-
relations: relationsRef.current,
|
|
840
825
|
walkthroughs: walkthroughsRef.current,
|
|
841
826
|
};
|
|
842
827
|
void buildSubsystemGraph(doc, {
|
|
@@ -893,7 +878,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
893
878
|
const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id)!.height]));
|
|
894
879
|
const gen = ++pass2GenRef.current;
|
|
895
880
|
void buildSubsystemGraph(
|
|
896
|
-
{ components,
|
|
881
|
+
{ components, walkthroughs },
|
|
897
882
|
{ maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine },
|
|
898
883
|
)
|
|
899
884
|
.then(({ nodes, edges: e, absoluteRects }) => {
|
|
@@ -907,7 +892,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
907
892
|
// Reveal Pass 1 layout rather than leaving the cover up forever.
|
|
908
893
|
setLayoutReady(true);
|
|
909
894
|
});
|
|
910
|
-
}, [built.nodes, components,
|
|
895
|
+
}, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
|
|
911
896
|
|
|
912
897
|
// After Pass 1 commits, try Pass 2 immediately with retained measurements.
|
|
913
898
|
// Same-id live updates often get no new React Flow `dimensions` events, so
|
|
@@ -991,7 +976,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
991
976
|
|
|
992
977
|
// Resolve an issue's component target to the matching node. `id` is the
|
|
993
978
|
// stable alias; `label` may be an alias, name, or symbol. Non-component
|
|
994
|
-
// targets (
|
|
979
|
+
// targets (module / flow / repo) have no single node → null.
|
|
995
980
|
const issueComponent = useCallback(
|
|
996
981
|
(issue: SubsystemIssue): SubsystemComponent | null => {
|
|
997
982
|
const target = issue.target;
|
|
@@ -1009,33 +994,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1009
994
|
[components],
|
|
1010
995
|
);
|
|
1011
996
|
|
|
1012
|
-
// Resolve a `relation` issue target to the display edge it flags. The target
|
|
1013
|
-
// names the two endpoints (`from → to`); match an edge joining both, in
|
|
1014
|
-
// either direction, preferring the target's `detail` mechanism when it names
|
|
1015
|
-
// one. Null when no display edge matches.
|
|
1016
|
-
const issueEdge = useCallback(
|
|
1017
|
-
(issue: SubsystemIssue): Edge | null => {
|
|
1018
|
-
const target = issue.target;
|
|
1019
|
-
if (target?.kind !== 'relation') return null;
|
|
1020
|
-
const parts = `${target.id ?? ''} → ${target.label}`
|
|
1021
|
-
.split('→')
|
|
1022
|
-
.map((p) => p.replace(/\(\)$/, '').trim())
|
|
1023
|
-
.filter(Boolean);
|
|
1024
|
-
const joins = (e: Edge): boolean =>
|
|
1025
|
-
parts.includes(e.source) && parts.includes(e.target);
|
|
1026
|
-
const matches = baseEdges.filter(joins);
|
|
1027
|
-
const byMechanism = target.detail
|
|
1028
|
-
? matches.filter(
|
|
1029
|
-
(e) =>
|
|
1030
|
-
(e.data as { mechanism?: string } | undefined)?.mechanism ===
|
|
1031
|
-
target.detail,
|
|
1032
|
-
)
|
|
1033
|
-
: matches;
|
|
1034
|
-
return byMechanism[0] ?? matches[0] ?? null;
|
|
1035
|
-
},
|
|
1036
|
-
[baseEdges],
|
|
1037
|
-
);
|
|
1038
|
-
|
|
1039
997
|
// A `module` issue target names a boundary frame, so focusing the card frames
|
|
1040
998
|
// that region on the canvas. Null when the target names no frame (a singleton
|
|
1041
999
|
// dropped from the layout, or a region key nothing declares).
|
|
@@ -1160,7 +1118,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1160
1118
|
// the canvas keeps while everything else dims. Derived from each finding's
|
|
1161
1119
|
// target:
|
|
1162
1120
|
// component → that node
|
|
1163
|
-
// relation → the edge it flags (+ its two endpoint nodes)
|
|
1164
1121
|
// module → every component in that module
|
|
1165
1122
|
// walkthrough → the flow's nodes + its hop edges
|
|
1166
1123
|
// repo → every component of that repo
|
|
@@ -1180,20 +1137,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1180
1137
|
if (t.kind === 'component') {
|
|
1181
1138
|
const a = resolveAlias(t.id ?? t.label);
|
|
1182
1139
|
if (a) nodeIds.add(a);
|
|
1183
|
-
} else if (t.kind === 'relation') {
|
|
1184
|
-
// The target names the two endpoints (`from → to`); light the edge and
|
|
1185
|
-
// its endpoints. Falls back to endpoints alone if no edge matches.
|
|
1186
|
-
const edge = issueEdge(issue);
|
|
1187
|
-
if (edge) {
|
|
1188
|
-
edgeIds.add(edge.id);
|
|
1189
|
-
nodeIds.add(edge.source);
|
|
1190
|
-
nodeIds.add(edge.target);
|
|
1191
|
-
} else {
|
|
1192
|
-
for (const part of `${t.id ?? ''} → ${t.label}`.split('→')) {
|
|
1193
|
-
const a = resolveAlias(part);
|
|
1194
|
-
if (a) nodeIds.add(a);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
1140
|
} else if (t.kind === 'module') {
|
|
1198
1141
|
const key = t.id ?? t.label;
|
|
1199
1142
|
const prefix = `${key.replace(/\/$/, '')}/`;
|
|
@@ -1232,7 +1175,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1232
1175
|
if (nodeIds.has(e.source) && nodeIds.has(e.target)) edgeIds.add(e.id);
|
|
1233
1176
|
}
|
|
1234
1177
|
return { nodeIds, edgeIds };
|
|
1235
|
-
}, [issues, expandedIssueCategories, components, walkthroughs,
|
|
1178
|
+
}, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
|
|
1236
1179
|
const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
|
|
1237
1180
|
const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
|
|
1238
1181
|
|
|
@@ -1479,15 +1422,15 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1479
1422
|
};
|
|
1480
1423
|
};
|
|
1481
1424
|
// Edges outside the selected view are hidden entirely (labels included).
|
|
1482
|
-
// graphify-native edges belong to the
|
|
1425
|
+
// graphify-native edges belong to the graphify view (they are static
|
|
1483
1426
|
// topology, not runtime hops) regardless of their verb.
|
|
1484
1427
|
const edgeInView = (e: Edge): boolean => {
|
|
1485
1428
|
const d = e.data as
|
|
1486
1429
|
| { mechanism?: string; provenance?: SubsystemEdgeProvenance }
|
|
1487
1430
|
| undefined;
|
|
1488
1431
|
const mechanism = d?.mechanism ?? 'uses';
|
|
1489
|
-
if (resolvedEdgeView === '
|
|
1490
|
-
return d?.provenance === 'graphify'
|
|
1432
|
+
if (resolvedEdgeView === 'graphify') {
|
|
1433
|
+
return d?.provenance === 'graphify';
|
|
1491
1434
|
}
|
|
1492
1435
|
return isWalkthroughMechanism(mechanism);
|
|
1493
1436
|
};
|
|
@@ -1586,7 +1529,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1586
1529
|
setSelectedEdgeId(null);
|
|
1587
1530
|
setFocusedWalkthroughId(null);
|
|
1588
1531
|
setFocusedStepIndex(null);
|
|
1589
|
-
setEdgeViewOverride(null);
|
|
1590
1532
|
issueModuleFocusRef.current = null;
|
|
1591
1533
|
}, []);
|
|
1592
1534
|
|
|
@@ -1612,7 +1554,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
1612
1554
|
useEffect(() => {
|
|
1613
1555
|
if (!issuesActive) {
|
|
1614
1556
|
setExpandedIssueCategories([]);
|
|
1615
|
-
setEdgeViewOverride(null);
|
|
1616
1557
|
}
|
|
1617
1558
|
}, [issuesActive]);
|
|
1618
1559
|
const toggleIssues = () => {
|
|
@@ -2081,11 +2022,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2081
2022
|
if (playable.length === 0) return;
|
|
2082
2023
|
// Only run the cycles that can actually be shown in the current edge view.
|
|
2083
2024
|
const inView = playable.filter((tl) =>
|
|
2084
|
-
tl.steps.some((s) =>
|
|
2085
|
-
resolvedEdgeView === 'relations'
|
|
2086
|
-
? isRelationMechanism(s.mechanism)
|
|
2087
|
-
: isWalkthroughMechanism(s.mechanism),
|
|
2088
|
-
),
|
|
2025
|
+
tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)),
|
|
2089
2026
|
);
|
|
2090
2027
|
if (inView.length === 0) return;
|
|
2091
2028
|
const cyc = inView;
|
|
@@ -2254,16 +2191,14 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2254
2191
|
);
|
|
2255
2192
|
|
|
2256
2193
|
// Issue click → select + frame the target on the canvas. Component targets
|
|
2257
|
-
// frame their node;
|
|
2258
|
-
//
|
|
2259
|
-
//
|
|
2260
|
-
//
|
|
2261
|
-
// still fires afterwards for its own detail.
|
|
2194
|
+
// frame their node; other kinds (module / flow / repo) have no single element
|
|
2195
|
+
// and just forward. Selections / focus that would hide the target are cleared
|
|
2196
|
+
// first, then the camera flies to it on the next frame. The host's
|
|
2197
|
+
// `onSelectIssue` still fires afterwards for its own detail.
|
|
2262
2198
|
const focusIssueTarget = useCallback(
|
|
2263
2199
|
(issue: SubsystemIssue) => {
|
|
2264
2200
|
const comp = issueComponent(issue);
|
|
2265
|
-
const
|
|
2266
|
-
const step = comp || edge ? null : issueStep(issue);
|
|
2201
|
+
const step = comp ? null : issueStep(issue);
|
|
2267
2202
|
if (comp) {
|
|
2268
2203
|
setSelected(comp);
|
|
2269
2204
|
setSelectedEdgeId(null);
|
|
@@ -2281,27 +2216,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2281
2216
|
maxZoom: 1.5,
|
|
2282
2217
|
});
|
|
2283
2218
|
});
|
|
2284
|
-
} else if (edge) {
|
|
2285
|
-
setSelected(null);
|
|
2286
|
-
setFocusedWalkthroughId(null);
|
|
2287
|
-
setFocusedStepIndex(null);
|
|
2288
|
-
setHoveredWalkthroughStep(null);
|
|
2289
|
-
setSelectedEdgeId(edge.id);
|
|
2290
|
-
issueModuleFocusRef.current = null;
|
|
2291
|
-
// Flip to the vocabulary this edge belongs to, so a hop flagged in the
|
|
2292
|
-
// relations view (or vice versa) is actually drawn while focused.
|
|
2293
|
-
const d = edge.data as
|
|
2294
|
-
| { mechanism?: string; provenance?: SubsystemEdgeProvenance }
|
|
2295
|
-
| undefined;
|
|
2296
|
-
const mechanism = d?.mechanism ?? 'uses';
|
|
2297
|
-
setEdgeViewOverride(
|
|
2298
|
-
d?.provenance === 'graphify' || isRelationMechanism(mechanism)
|
|
2299
|
-
? 'relations'
|
|
2300
|
-
: 'walkthroughs',
|
|
2301
|
-
);
|
|
2302
|
-
requestAnimationFrame(() => {
|
|
2303
|
-
fitFocusBounds(new Set([edge.id]));
|
|
2304
|
-
});
|
|
2305
2219
|
} else if (step) {
|
|
2306
2220
|
// A step finding focuses the step itself, through the same entry point
|
|
2307
2221
|
// the walkthrough panel uses — so the drawer, the step numbering, the
|
|
@@ -2329,14 +2243,12 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2329
2243
|
},
|
|
2330
2244
|
[
|
|
2331
2245
|
issueComponent,
|
|
2332
|
-
issueEdge,
|
|
2333
2246
|
issueStep,
|
|
2334
2247
|
issueModuleNodeId,
|
|
2335
2248
|
focusWalkthroughStep,
|
|
2336
2249
|
onSelect,
|
|
2337
2250
|
onSelectIssue,
|
|
2338
2251
|
fitView,
|
|
2339
|
-
fitFocusBounds,
|
|
2340
2252
|
fitNodeRects,
|
|
2341
2253
|
],
|
|
2342
2254
|
);
|
|
@@ -2355,14 +2267,9 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2355
2267
|
setSelected(null);
|
|
2356
2268
|
setSelectedEdgeId(null);
|
|
2357
2269
|
} else {
|
|
2358
|
-
const
|
|
2359
|
-
const
|
|
2360
|
-
|
|
2361
|
-
if (edge) {
|
|
2362
|
-
if (selectedEdgeIdRef.current !== edge.id) return;
|
|
2363
|
-
setSelectedEdgeId(null);
|
|
2364
|
-
setEdgeViewOverride(null);
|
|
2365
|
-
} else if (step) {
|
|
2270
|
+
const step = issueStep(issue);
|
|
2271
|
+
const moduleNodeId = step ? null : issueModuleNodeId(issue);
|
|
2272
|
+
if (step) {
|
|
2366
2273
|
// Only unwind a step focus this card still owns. A dim-mode graph
|
|
2367
2274
|
// keeps its step focus in hover state instead (owned by the pointer,
|
|
2368
2275
|
// not the card), and correctly leaves that alone here.
|
|
@@ -2388,7 +2295,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
|
|
|
2388
2295
|
fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
|
|
2389
2296
|
});
|
|
2390
2297
|
},
|
|
2391
|
-
[issueComponent,
|
|
2298
|
+
[issueComponent, issueStep, issueModuleNodeId, fitView],
|
|
2392
2299
|
);
|
|
2393
2300
|
|
|
2394
2301
|
// Construct tokens inside a walkthrough snippet. A step's line is an edge
|
|
@@ -3362,7 +3269,6 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
|
|
|
3362
3269
|
|
|
3363
3270
|
const constructsOnly = isConstructsOnlyModel({
|
|
3364
3271
|
components: props.components,
|
|
3365
|
-
relations: props.relations,
|
|
3366
3272
|
walkthroughs: props.walkthroughs,
|
|
3367
3273
|
graphifyRelations: props.graphifyRelations,
|
|
3368
3274
|
});
|