@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.
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 +30 -110
  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 +38 -132
  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';
@@ -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 `relations` edge view.
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 vocabulary the canvas draws. The relation and walkthrough
243
- * vocabularies are disjoint, so a graph carrying both shows one or the
244
- * other — never both. Edges outside the view are hidden (labels go too).
245
- * - `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`, …)
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 topology relations, Walkthroughs draws runtime hops. Without visible
250
- * tabs, defaults to `walkthroughs` for a flow-only model (walkthroughs but
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), a relation target frames its edge, and a
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, 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) {
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({ relations, walkthroughs }),
534
- [relations, walkthroughs],
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
- // Temporary edge-vocabulary override. Focusing a relation finding whose edge
590
- // belongs to the *other* vocabulary flips the canvas so the edge is actually
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 the model: a flow-only graph defaults
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' : 'relations')
607
- : relations.length === 0 && (walkthroughs?.length ?? 0) > 0
592
+ ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
593
+ : (walkthroughs?.length ?? 0) > 0
608
594
  ? 'walkthroughs'
609
- : 'relations');
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, relations, walkthroughs, graphifyRelations }),
811
- [components, relations, walkthroughs, graphifyRelations],
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, relations, walkthroughs },
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, relations, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
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 (relation / module / flow / repo) have no single node → null.
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, baseEdges, resolveAlias, issueEdge]);
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 relations view (they are static
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 === 'relations') {
1490
- return d?.provenance === 'graphify' || isRelationMechanism(mechanism);
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; relation targets frame their edge (endpoints + the routed
2258
- // line); other kinds (module / flow / repo) have no single element and just
2259
- // forward. Selections / focus that would hide the target are cleared first,
2260
- // then the camera flies to it on the next frame. The host's `onSelectIssue`
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 edge = comp ? null : issueEdge(issue);
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 edge = issueEdge(issue);
2359
- const step = edge ? null : issueStep(issue);
2360
- const moduleNodeId = edge || step ? null : issueModuleNodeId(issue);
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, issueEdge, issueStep, issueModuleNodeId, fitView],
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
  });