@principal-ai/subsystems-react 0.41.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.
Files changed (67) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/stories/Subsystem/C4Graph/c4Fixture.js +1 -1
  6. package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
  7. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +6 -9
  8. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  9. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +15 -50
  10. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  11. package/dist/subsystem/C4Graph.d.ts +2 -3
  12. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  13. package/dist/subsystem/C4Graph.js +2 -3
  14. package/dist/subsystem/C4Graph.js.map +1 -1
  15. package/dist/subsystem/IssueList.d.ts +5 -5
  16. package/dist/subsystem/IssueList.d.ts.map +1 -1
  17. package/dist/subsystem/IssueList.js +9 -18
  18. package/dist/subsystem/IssueList.js.map +1 -1
  19. package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -12
  20. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  21. package/dist/subsystem/SubsystemComponentGraph.js +42 -112
  22. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  23. package/dist/subsystem/model.d.ts +18 -51
  24. package/dist/subsystem/model.d.ts.map +1 -1
  25. package/dist/subsystem/model.js +2 -51
  26. package/dist/subsystem/model.js.map +1 -1
  27. package/dist/subsystem/toC4.d.ts +4 -4
  28. package/dist/subsystem/toC4.d.ts.map +1 -1
  29. package/dist/subsystem/toC4.js +3 -7
  30. package/dist/subsystem/toC4.js.map +1 -1
  31. package/package.json +3 -3
  32. package/src/index.ts +0 -4
  33. package/src/stories/Subsystem/AgentsPanel.stories.tsx +3 -5
  34. package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +1 -4
  35. package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
  36. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -4
  37. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
  38. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +19 -19
  39. package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +0 -1
  40. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
  41. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +0 -2
  42. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +2 -6
  43. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +21 -21
  44. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +0 -3
  45. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +30 -32
  46. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  47. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +1 -26
  48. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +3 -25
  49. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +7 -8
  50. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +9 -11
  51. package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +3 -6
  52. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  53. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +8 -6
  54. package/src/stories/Subsystem/ComponentGraph/ProposedWalkthroughs.stories.tsx +0 -1
  55. package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +0 -1
  56. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +26 -28
  57. package/src/stories/Subsystem/ComponentGraph/WalkthroughAutoplay.stories.tsx +0 -1
  58. package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +2 -3
  59. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +18 -58
  60. package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +0 -2
  61. package/src/subsystem/C4Graph.tsx +2 -3
  62. package/src/subsystem/IssueList.tsx +12 -22
  63. package/src/subsystem/SubsystemComponentGraph.tsx +55 -136
  64. package/src/subsystem/model.test.ts +37 -24
  65. package/src/subsystem/model.ts +18 -101
  66. package/src/subsystem/toC4.test.ts +5 -12
  67. 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. Static `relations` render as solid
7
- * edges; `walkthrough` hops render as dashed flow edges. Click a box to list
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 / relation / module / flow clusters together. Each
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 = relations; dynamic topology = process (runtime) +
52
- * package/module (containment).
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. `boundary_*` (module containment + process) is dynamic. */
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
- topology_broken_endpoint: 'static-topology',
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 (relation type, step number, …). */
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
- * Topology findings drop the `topology_`/`boundary_` prefix since the layer
235
- * already scopes them. Explicit `issue.kindLabel` wins.
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 topology (integrity → corroboration → membership) and the
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 — edge integrity → corroboration → membership
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';
@@ -137,15 +135,18 @@ type DrawerTarget =
137
135
 
138
136
  /**
139
137
  * Per-model UI state persisted to `localStorage`, keyed by `persistKey`.
140
- * Only the walkthrough working set is stored — which flows are expanded and
141
- * which flow/step is selected — so tabbing away from a model and back lands
142
- * you where you left off. Transient things (hover, drag, camera) are not saved.
138
+ * Stores the walkthrough working set (which flows are expanded and which
139
+ * flow/step is selected) and which sidebar/diagnostics tab is showing, so
140
+ * tabbing away from a model and back lands you where you left off. Transient
141
+ * things (hover, drag, camera) are not saved.
143
142
  */
144
143
  interface PersistedViewState {
145
144
  expandedWalkthroughs?: string[];
146
145
  focusedWalkthroughId?: string | null;
147
146
  focusedStepIndex?: number | null;
148
147
  sidebarWidth?: number;
148
+ sidebarView?: 'files' | 'walkthroughs';
149
+ diagnosticsTab?: 'issues' | 'agents';
149
150
  }
150
151
 
151
152
  const VIEW_STATE_PREFIX = 'principal.subsystems.viewState.';
@@ -179,8 +180,6 @@ function writeViewState(
179
180
 
180
181
  export interface SubsystemComponentGraphProps {
181
182
  components: SubsystemComponent[];
182
- /** Topology relations (structural / module / type). Display edges are derived with walkthrough hops. */
183
- relations: SubsystemRelation[];
184
183
  /**
185
184
  * Ordered runtime walkthroughs — one walkthrough per flow. When present the
186
185
  * sidebar's bottom half offers a Files/Walkthroughs toggle:
@@ -195,7 +194,7 @@ export interface SubsystemComponentGraphProps {
195
194
  * graphify-native relations (raw static-graph edges: `imports`, `contains`,
196
195
  * `re_exports`, …). Display-only, drawn with the separate
197
196
  * `GRAPHIFY_RELATION_COLOR` palette so they read as derived facts, distinct
198
- * from authored subsystem mechanisms. Visible in the `relations` edge view.
197
+ * from authored subsystem mechanisms. Visible in the `graphify` edge view.
199
198
  */
200
199
  graphifyRelations?: readonly SubsystemGraphifyRelation[];
201
200
  /**
@@ -236,16 +235,15 @@ export interface SubsystemComponentGraphProps {
236
235
  */
237
236
  showSingletonFrames?: boolean;
238
237
  /**
239
- * Which edge vocabulary the canvas draws. The relation and walkthrough
240
- * vocabularies are disjoint, so a graph carrying both shows one or the
241
- * other — never both. Edges outside the view are hidden (labels go too).
242
- * - `relations`: topology relation edges (`extends`, `implements`, …)
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`, …)
243
242
  * - `walkthroughs`: walkthrough hop edges (`calls`, `feeds`, …), including
244
243
  * step numbers when a flow is focused/hovered
245
244
  * Leave unset to let the sidebar's Files / Walkthroughs tab drive it: Files
246
- * draws topology relations, Walkthroughs draws runtime hops. Without visible
247
- * tabs, defaults to `walkthroughs` for a flow-only model (walkthroughs but
248
- * no relations), otherwise `relations`.
245
+ * draws graphify edges, Walkthroughs draws runtime hops. Without visible
246
+ * tabs, defaults to `walkthroughs`.
249
247
  */
250
248
  edgeView?: SubsystemEdgeView;
251
249
  /** Subsystem title displayed in the sidebar. */
@@ -345,7 +343,7 @@ export interface SubsystemComponentGraphProps {
345
343
  /**
346
344
  * Click an issue. The graph first focuses the target on the canvas itself —
347
345
  * a component target is selected and framed (any edge / walkthrough focus
348
- * that would hide it is cleared), a relation target frames its edge, and a
346
+ * that would hide it is cleared), and a
349
347
  * module target frames that boundary frame — then this fires so the host can
350
348
  * open its own detail. Collapsing the card again reverses that (deselect +
351
349
  * zoom out). Other target kinds (flow / repo) have no node to frame and just
@@ -519,7 +517,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
519
517
  measured: { w: number; h: number } | null;
520
518
  }
521
519
 
522
- function Inner({ components, relations, 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) {
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) {
523
521
  const { theme } = useTheme();
524
522
  const { fitView, fitBounds } = useReactFlow();
525
523
  const viewport = useViewport();
@@ -527,8 +525,8 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
527
525
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
528
526
  const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
529
527
  const graphEdges = useMemo(
530
- () => deriveGraphEdges({ relations, walkthroughs }),
531
- [relations, walkthroughs],
528
+ () => deriveGraphEdges({ walkthroughs }),
529
+ [walkthroughs],
532
530
  );
533
531
  const [built, setBuilt] = useState<{
534
532
  nodes: Node[];
@@ -577,31 +575,26 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
577
575
  } | null>(null);
578
576
  // Sidebar bottom half: which panel is shown when walkthroughs exist.
579
577
  const [sidebarView, setSidebarView] = useState<'files' | 'walkthroughs'>(() =>
580
- walkthroughs?.length ? 'walkthroughs' : 'files',
578
+ persisted.sidebarView ?? (walkthroughs?.length ? 'walkthroughs' : 'files'),
581
579
  );
582
580
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
583
- const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>('issues');
584
- // Temporary edge-vocabulary override. Focusing a relation finding whose edge
585
- // belongs to the *other* vocabulary flips the canvas so the edge is actually
586
- // visible; it reverts when the focus is undone (see `unfocusIssueTarget`) or
587
- // when the diagnostics list closes. Null = use the host-pinned / tab view.
588
- const [edgeViewOverride, setEdgeViewOverride] = useState<SubsystemEdgeView | null>(
589
- null,
581
+ const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>(
582
+ () => persisted.diagnosticsTab ?? 'issues',
590
583
  );
591
- // One edge vocabulary at a time. When the caller doesn't pick, the sidebar's
592
- // 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,
593
586
  // Walkthroughs draws runtime hop edges. Without visible tabs (no walkthroughs,
594
- // or a sidebar-less embed) fall back to the model: a flow-only graph defaults
595
- // to walkthrough edges rather than empty, everything else to relations.
587
+ // or a sidebar-less embed) fall back to walkthrough edges.
596
588
  const sidebarTabsVisible = !hideSidebar && (walkthroughs?.length ?? 0) > 0;
597
589
  const resolvedEdgeView: SubsystemEdgeView =
598
- edgeViewOverride ??
599
590
  edgeView ??
600
591
  (sidebarTabsVisible
601
- ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'relations')
602
- : relations.length === 0 && (walkthroughs?.length ?? 0) > 0
592
+ ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
593
+ : (walkthroughs?.length ?? 0) > 0
603
594
  ? 'walkthroughs'
604
- : 'relations');
595
+ : (graphifyRelations?.length ?? 0) > 0
596
+ ? 'graphify'
597
+ : 'walkthroughs');
605
598
  // Walkthrough flows the user has expanded (via the title row). Closed by
606
599
  // default so a graph with several flows doesn't dump every step list at once.
607
600
  // Restored from the persisted view state when a `persistKey` is set.
@@ -681,6 +674,14 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
681
674
  if (!persistKey) return;
682
675
  writeViewState(persistKey, { focusedStepIndex });
683
676
  }, [persistKey, focusedStepIndex]);
677
+ useEffect(() => {
678
+ if (!persistKey) return;
679
+ writeViewState(persistKey, { sidebarView });
680
+ }, [persistKey, sidebarView]);
681
+ useEffect(() => {
682
+ if (!persistKey) return;
683
+ writeViewState(persistKey, { diagnosticsTab });
684
+ }, [persistKey, diagnosticsTab]);
684
685
  // Ref mirror of `selected` so the SUBSYSTEM_CALLBACKS click handler (a
685
686
  // closure over the effect deps) can toggle without a stale value.
686
687
  const selectedRef = useRef<SubsystemComponent | null>(null);
@@ -794,15 +795,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
794
795
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
795
796
  // fields only — declarationRef updates after verify must not re-run ELK.
796
797
  const layoutKey = useMemo(
797
- () => subsystemGraphLayoutKey({ components, relations, walkthroughs, graphifyRelations }),
798
- [components, relations, walkthroughs, graphifyRelations],
798
+ () => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }),
799
+ [components, walkthroughs, graphifyRelations],
799
800
  );
800
801
  const componentsRef = useRef(components);
801
- const relationsRef = useRef(relations);
802
802
  const walkthroughsRef = useRef(walkthroughs);
803
803
  const graphifyRelationsRef = useRef(graphifyRelations);
804
804
  componentsRef.current = components;
805
- relationsRef.current = relations;
806
805
  walkthroughsRef.current = walkthroughs;
807
806
  graphifyRelationsRef.current = graphifyRelations;
808
807
 
@@ -823,7 +822,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
823
822
  pass2GenRef.current += 1;
824
823
  const doc = {
825
824
  components: componentsRef.current,
826
- relations: relationsRef.current,
827
825
  walkthroughs: walkthroughsRef.current,
828
826
  };
829
827
  void buildSubsystemGraph(doc, {
@@ -880,7 +878,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
880
878
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id)!.height]));
881
879
  const gen = ++pass2GenRef.current;
882
880
  void buildSubsystemGraph(
883
- { components, relations, walkthroughs },
881
+ { components, walkthroughs },
884
882
  { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine },
885
883
  )
886
884
  .then(({ nodes, edges: e, absoluteRects }) => {
@@ -894,7 +892,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
894
892
  // Reveal Pass 1 layout rather than leaving the cover up forever.
895
893
  setLayoutReady(true);
896
894
  });
897
- }, [built.nodes, components, relations, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
895
+ }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
898
896
 
899
897
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
900
898
  // Same-id live updates often get no new React Flow `dimensions` events, so
@@ -978,7 +976,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
978
976
 
979
977
  // Resolve an issue's component target to the matching node. `id` is the
980
978
  // stable alias; `label` may be an alias, name, or symbol. Non-component
981
- // targets (relation / module / flow / repo) have no single node → null.
979
+ // targets (module / flow / repo) have no single node → null.
982
980
  const issueComponent = useCallback(
983
981
  (issue: SubsystemIssue): SubsystemComponent | null => {
984
982
  const target = issue.target;
@@ -996,33 +994,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
996
994
  [components],
997
995
  );
998
996
 
999
- // Resolve a `relation` issue target to the display edge it flags. The target
1000
- // names the two endpoints (`from → to`); match an edge joining both, in
1001
- // either direction, preferring the target's `detail` mechanism when it names
1002
- // one. Null when no display edge matches.
1003
- const issueEdge = useCallback(
1004
- (issue: SubsystemIssue): Edge | null => {
1005
- const target = issue.target;
1006
- if (target?.kind !== 'relation') return null;
1007
- const parts = `${target.id ?? ''} → ${target.label}`
1008
- .split('→')
1009
- .map((p) => p.replace(/\(\)$/, '').trim())
1010
- .filter(Boolean);
1011
- const joins = (e: Edge): boolean =>
1012
- parts.includes(e.source) && parts.includes(e.target);
1013
- const matches = baseEdges.filter(joins);
1014
- const byMechanism = target.detail
1015
- ? matches.filter(
1016
- (e) =>
1017
- (e.data as { mechanism?: string } | undefined)?.mechanism ===
1018
- target.detail,
1019
- )
1020
- : matches;
1021
- return byMechanism[0] ?? matches[0] ?? null;
1022
- },
1023
- [baseEdges],
1024
- );
1025
-
1026
997
  // A `module` issue target names a boundary frame, so focusing the card frames
1027
998
  // that region on the canvas. Null when the target names no frame (a singleton
1028
999
  // dropped from the layout, or a region key nothing declares).
@@ -1147,7 +1118,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1147
1118
  // the canvas keeps while everything else dims. Derived from each finding's
1148
1119
  // target:
1149
1120
  // component → that node
1150
- // relation → the edge it flags (+ its two endpoint nodes)
1151
1121
  // module → every component in that module
1152
1122
  // walkthrough → the flow's nodes + its hop edges
1153
1123
  // repo → every component of that repo
@@ -1167,20 +1137,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1167
1137
  if (t.kind === 'component') {
1168
1138
  const a = resolveAlias(t.id ?? t.label);
1169
1139
  if (a) nodeIds.add(a);
1170
- } else if (t.kind === 'relation') {
1171
- // The target names the two endpoints (`from → to`); light the edge and
1172
- // its endpoints. Falls back to endpoints alone if no edge matches.
1173
- const edge = issueEdge(issue);
1174
- if (edge) {
1175
- edgeIds.add(edge.id);
1176
- nodeIds.add(edge.source);
1177
- nodeIds.add(edge.target);
1178
- } else {
1179
- for (const part of `${t.id ?? ''} → ${t.label}`.split('→')) {
1180
- const a = resolveAlias(part);
1181
- if (a) nodeIds.add(a);
1182
- }
1183
- }
1184
1140
  } else if (t.kind === 'module') {
1185
1141
  const key = t.id ?? t.label;
1186
1142
  const prefix = `${key.replace(/\/$/, '')}/`;
@@ -1219,7 +1175,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1219
1175
  if (nodeIds.has(e.source) && nodeIds.has(e.target)) edgeIds.add(e.id);
1220
1176
  }
1221
1177
  return { nodeIds, edgeIds };
1222
- }, [issues, expandedIssueCategories, components, walkthroughs, baseEdges, resolveAlias, issueEdge]);
1178
+ }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
1223
1179
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
1224
1180
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
1225
1181
 
@@ -1466,15 +1422,15 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1466
1422
  };
1467
1423
  };
1468
1424
  // Edges outside the selected view are hidden entirely (labels included).
1469
- // graphify-native edges belong to the relations view (they are static
1425
+ // graphify-native edges belong to the graphify view (they are static
1470
1426
  // topology, not runtime hops) regardless of their verb.
1471
1427
  const edgeInView = (e: Edge): boolean => {
1472
1428
  const d = e.data as
1473
1429
  | { mechanism?: string; provenance?: SubsystemEdgeProvenance }
1474
1430
  | undefined;
1475
1431
  const mechanism = d?.mechanism ?? 'uses';
1476
- if (resolvedEdgeView === 'relations') {
1477
- return d?.provenance === 'graphify' || isRelationMechanism(mechanism);
1432
+ if (resolvedEdgeView === 'graphify') {
1433
+ return d?.provenance === 'graphify';
1478
1434
  }
1479
1435
  return isWalkthroughMechanism(mechanism);
1480
1436
  };
@@ -1573,7 +1529,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1573
1529
  setSelectedEdgeId(null);
1574
1530
  setFocusedWalkthroughId(null);
1575
1531
  setFocusedStepIndex(null);
1576
- setEdgeViewOverride(null);
1577
1532
  issueModuleFocusRef.current = null;
1578
1533
  }, []);
1579
1534
 
@@ -1599,7 +1554,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1599
1554
  useEffect(() => {
1600
1555
  if (!issuesActive) {
1601
1556
  setExpandedIssueCategories([]);
1602
- setEdgeViewOverride(null);
1603
1557
  }
1604
1558
  }, [issuesActive]);
1605
1559
  const toggleIssues = () => {
@@ -2068,11 +2022,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2068
2022
  if (playable.length === 0) return;
2069
2023
  // Only run the cycles that can actually be shown in the current edge view.
2070
2024
  const inView = playable.filter((tl) =>
2071
- tl.steps.some((s) =>
2072
- resolvedEdgeView === 'relations'
2073
- ? isRelationMechanism(s.mechanism)
2074
- : isWalkthroughMechanism(s.mechanism),
2075
- ),
2025
+ tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)),
2076
2026
  );
2077
2027
  if (inView.length === 0) return;
2078
2028
  const cyc = inView;
@@ -2241,16 +2191,14 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2241
2191
  );
2242
2192
 
2243
2193
  // Issue click → select + frame the target on the canvas. Component targets
2244
- // frame their node; relation targets frame their edge (endpoints + the routed
2245
- // line); other kinds (module / flow / repo) have no single element and just
2246
- // forward. Selections / focus that would hide the target are cleared first,
2247
- // then the camera flies to it on the next frame. The host's `onSelectIssue`
2248
- // 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.
2249
2198
  const focusIssueTarget = useCallback(
2250
2199
  (issue: SubsystemIssue) => {
2251
2200
  const comp = issueComponent(issue);
2252
- const edge = comp ? null : issueEdge(issue);
2253
- const step = comp || edge ? null : issueStep(issue);
2201
+ const step = comp ? null : issueStep(issue);
2254
2202
  if (comp) {
2255
2203
  setSelected(comp);
2256
2204
  setSelectedEdgeId(null);
@@ -2268,27 +2216,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2268
2216
  maxZoom: 1.5,
2269
2217
  });
2270
2218
  });
2271
- } else if (edge) {
2272
- setSelected(null);
2273
- setFocusedWalkthroughId(null);
2274
- setFocusedStepIndex(null);
2275
- setHoveredWalkthroughStep(null);
2276
- setSelectedEdgeId(edge.id);
2277
- issueModuleFocusRef.current = null;
2278
- // Flip to the vocabulary this edge belongs to, so a hop flagged in the
2279
- // relations view (or vice versa) is actually drawn while focused.
2280
- const d = edge.data as
2281
- | { mechanism?: string; provenance?: SubsystemEdgeProvenance }
2282
- | undefined;
2283
- const mechanism = d?.mechanism ?? 'uses';
2284
- setEdgeViewOverride(
2285
- d?.provenance === 'graphify' || isRelationMechanism(mechanism)
2286
- ? 'relations'
2287
- : 'walkthroughs',
2288
- );
2289
- requestAnimationFrame(() => {
2290
- fitFocusBounds(new Set([edge.id]));
2291
- });
2292
2219
  } else if (step) {
2293
2220
  // A step finding focuses the step itself, through the same entry point
2294
2221
  // the walkthrough panel uses — so the drawer, the step numbering, the
@@ -2316,14 +2243,12 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2316
2243
  },
2317
2244
  [
2318
2245
  issueComponent,
2319
- issueEdge,
2320
2246
  issueStep,
2321
2247
  issueModuleNodeId,
2322
2248
  focusWalkthroughStep,
2323
2249
  onSelect,
2324
2250
  onSelectIssue,
2325
2251
  fitView,
2326
- fitFocusBounds,
2327
2252
  fitNodeRects,
2328
2253
  ],
2329
2254
  );
@@ -2342,14 +2267,9 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2342
2267
  setSelected(null);
2343
2268
  setSelectedEdgeId(null);
2344
2269
  } else {
2345
- const edge = issueEdge(issue);
2346
- const step = edge ? null : issueStep(issue);
2347
- const moduleNodeId = edge || step ? null : issueModuleNodeId(issue);
2348
- if (edge) {
2349
- if (selectedEdgeIdRef.current !== edge.id) return;
2350
- setSelectedEdgeId(null);
2351
- setEdgeViewOverride(null);
2352
- } else if (step) {
2270
+ const step = issueStep(issue);
2271
+ const moduleNodeId = step ? null : issueModuleNodeId(issue);
2272
+ if (step) {
2353
2273
  // Only unwind a step focus this card still owns. A dim-mode graph
2354
2274
  // keeps its step focus in hover state instead (owned by the pointer,
2355
2275
  // not the card), and correctly leaves that alone here.
@@ -2375,7 +2295,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
2375
2295
  fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
2376
2296
  });
2377
2297
  },
2378
- [issueComponent, issueEdge, issueStep, issueModuleNodeId, fitView],
2298
+ [issueComponent, issueStep, issueModuleNodeId, fitView],
2379
2299
  );
2380
2300
 
2381
2301
  // Construct tokens inside a walkthrough snippet. A step's line is an edge
@@ -3349,7 +3269,6 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3349
3269
 
3350
3270
  const constructsOnly = isConstructsOnlyModel({
3351
3271
  components: props.components,
3352
- relations: props.relations,
3353
3272
  walkthroughs: props.walkthroughs,
3354
3273
  graphifyRelations: props.graphifyRelations,
3355
3274
  });