@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
@@ -20,7 +20,7 @@ import { ReactFlow, ReactFlowProvider, useReactFlow, useViewport, applyNodeChang
20
20
  import { useTheme } from '@principal-ade/industry-theme';
21
21
  import { FileText, X } from 'lucide-react';
22
22
  import { IndustryMarkdownSlide } from 'themed-markdown';
23
- import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, isRelationMechanism, isWalkthroughMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
23
+ import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, isWalkthroughMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
24
24
  import { ConstructsCatalog } from './ConstructsCatalog';
25
25
  import { SubsystemAgentsPanel, } from './AgentsPanel';
26
26
  import { MaintainLivePanel, } from '../components/maintain-events/MaintainLivePanel';
@@ -122,14 +122,14 @@ const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render
122
122
  onSymbolClick,
123
123
  }) }));
124
124
  });
125
- 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 }) {
125
+ 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 }) {
126
126
  const { theme } = useTheme();
127
127
  const { fitView, fitBounds } = useReactFlow();
128
128
  const viewport = useViewport();
129
129
  // Restored once per mount from `localStorage` (see `readViewState`). Each
130
130
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
131
131
  const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
132
- const graphEdges = useMemo(() => deriveGraphEdges({ relations, walkthroughs }), [relations, walkthroughs]);
132
+ const graphEdges = useMemo(() => deriveGraphEdges({ walkthroughs }), [walkthroughs]);
133
133
  const [built, setBuilt] = useState({
134
134
  nodes: [],
135
135
  edges: [],
@@ -162,27 +162,22 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
162
162
  // a step is already selected.
163
163
  const [hoveredWalkthroughStep, setHoveredWalkthroughStep] = useState(null);
164
164
  // Sidebar bottom half: which panel is shown when walkthroughs exist.
165
- const [sidebarView, setSidebarView] = useState(() => walkthroughs?.length ? 'walkthroughs' : 'files');
165
+ const [sidebarView, setSidebarView] = useState(() => persisted.sidebarView ?? (walkthroughs?.length ? 'walkthroughs' : 'files'));
166
166
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
167
- const [diagnosticsTab, setDiagnosticsTab] = useState('issues');
168
- // Temporary edge-vocabulary override. Focusing a relation finding whose edge
169
- // belongs to the *other* vocabulary flips the canvas so the edge is actually
170
- // visible; it reverts when the focus is undone (see `unfocusIssueTarget`) or
171
- // when the diagnostics list closes. Null = use the host-pinned / tab view.
172
- const [edgeViewOverride, setEdgeViewOverride] = useState(null);
173
- // One edge vocabulary at a time. When the caller doesn't pick, the sidebar's
174
- // Files / Walkthroughs tab picks: Files draws topology relation edges,
167
+ const [diagnosticsTab, setDiagnosticsTab] = useState(() => persisted.diagnosticsTab ?? 'issues');
168
+ // One edge source at a time. When the caller doesn't pick, the sidebar's
169
+ // Files / Walkthroughs tab picks: Files draws graphify static edges,
175
170
  // Walkthroughs draws runtime hop edges. Without visible tabs (no walkthroughs,
176
- // or a sidebar-less embed) fall back to the model: a flow-only graph defaults
177
- // to walkthrough edges rather than empty, everything else to relations.
171
+ // or a sidebar-less embed) fall back to walkthrough edges.
178
172
  const sidebarTabsVisible = !hideSidebar && (walkthroughs?.length ?? 0) > 0;
179
- const resolvedEdgeView = edgeViewOverride ??
180
- edgeView ??
173
+ const resolvedEdgeView = edgeView ??
181
174
  (sidebarTabsVisible
182
- ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'relations')
183
- : relations.length === 0 && (walkthroughs?.length ?? 0) > 0
175
+ ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
176
+ : (walkthroughs?.length ?? 0) > 0
184
177
  ? 'walkthroughs'
185
- : 'relations');
178
+ : (graphifyRelations?.length ?? 0) > 0
179
+ ? 'graphify'
180
+ : 'walkthroughs');
186
181
  // Walkthrough flows the user has expanded (via the title row). Closed by
187
182
  // default so a graph with several flows doesn't dump every step list at once.
188
183
  // Restored from the persisted view state when a `persistKey` is set.
@@ -255,6 +250,16 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
255
250
  return;
256
251
  writeViewState(persistKey, { focusedStepIndex });
257
252
  }, [persistKey, focusedStepIndex]);
253
+ useEffect(() => {
254
+ if (!persistKey)
255
+ return;
256
+ writeViewState(persistKey, { sidebarView });
257
+ }, [persistKey, sidebarView]);
258
+ useEffect(() => {
259
+ if (!persistKey)
260
+ return;
261
+ writeViewState(persistKey, { diagnosticsTab });
262
+ }, [persistKey, diagnosticsTab]);
258
263
  // Ref mirror of `selected` so the SUBSYSTEM_CALLBACKS click handler (a
259
264
  // closure over the effect deps) can toggle without a stale value.
260
265
  const selectedRef = useRef(null);
@@ -371,13 +376,11 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
371
376
  // Pass 1: build with estimated widths so React Flow can measure the DOM.
372
377
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
373
378
  // fields only — declarationRef updates after verify must not re-run ELK.
374
- const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, relations, walkthroughs, graphifyRelations }), [components, relations, walkthroughs, graphifyRelations]);
379
+ const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }), [components, walkthroughs, graphifyRelations]);
375
380
  const componentsRef = useRef(components);
376
- const relationsRef = useRef(relations);
377
381
  const walkthroughsRef = useRef(walkthroughs);
378
382
  const graphifyRelationsRef = useRef(graphifyRelations);
379
383
  componentsRef.current = components;
380
- relationsRef.current = relations;
381
384
  walkthroughsRef.current = walkthroughs;
382
385
  graphifyRelationsRef.current = graphifyRelations;
383
386
  // Track measured dimensions from React Flow's dimension changes.
@@ -396,7 +399,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
396
399
  pass2GenRef.current += 1;
397
400
  const doc = {
398
401
  components: componentsRef.current,
399
- relations: relationsRef.current,
400
402
  walkthroughs: walkthroughsRef.current,
401
403
  };
402
404
  void buildSubsystemGraph(doc, {
@@ -455,7 +457,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
455
457
  const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
456
458
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
457
459
  const gen = ++pass2GenRef.current;
458
- void buildSubsystemGraph({ components, relations, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine })
460
+ void buildSubsystemGraph({ components, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine })
459
461
  .then(({ nodes, edges: e, absoluteRects }) => {
460
462
  if (gen !== pass2GenRef.current)
461
463
  return;
@@ -469,7 +471,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
469
471
  // Reveal Pass 1 layout rather than leaving the cover up forever.
470
472
  setLayoutReady(true);
471
473
  });
472
- }, [built.nodes, components, relations, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
474
+ }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
473
475
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
474
476
  // Same-id live updates often get no new React Flow `dimensions` events, so
475
477
  // waiting only on onNodesChange leaves the cover stuck.
@@ -549,7 +551,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
549
551
  const baseEdges = convertedEdges;
550
552
  // Resolve an issue's component target to the matching node. `id` is the
551
553
  // stable alias; `label` may be an alias, name, or symbol. Non-component
552
- // targets (relation / module / flow / repo) have no single node → null.
554
+ // targets (module / flow / repo) have no single node → null.
553
555
  const issueComponent = useCallback((issue) => {
554
556
  const target = issue.target;
555
557
  if (target?.kind !== 'component')
@@ -559,26 +561,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
559
561
  c.name === target.label ||
560
562
  c.symbol === target.label) ?? null);
561
563
  }, [components]);
562
- // Resolve a `relation` issue target to the display edge it flags. The target
563
- // names the two endpoints (`from → to`); match an edge joining both, in
564
- // either direction, preferring the target's `detail` mechanism when it names
565
- // one. Null when no display edge matches.
566
- const issueEdge = useCallback((issue) => {
567
- const target = issue.target;
568
- if (target?.kind !== 'relation')
569
- return null;
570
- const parts = `${target.id ?? ''} → ${target.label}`
571
- .split('→')
572
- .map((p) => p.replace(/\(\)$/, '').trim())
573
- .filter(Boolean);
574
- const joins = (e) => parts.includes(e.source) && parts.includes(e.target);
575
- const matches = baseEdges.filter(joins);
576
- const byMechanism = target.detail
577
- ? matches.filter((e) => e.data?.mechanism ===
578
- target.detail)
579
- : matches;
580
- return byMechanism[0] ?? matches[0] ?? null;
581
- }, [baseEdges]);
582
564
  // A `module` issue target names a boundary frame, so focusing the card frames
583
565
  // that region on the canvas. Null when the target names no frame (a singleton
584
566
  // dropped from the layout, or a region key nothing declares).
@@ -682,7 +664,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
682
664
  // the canvas keeps while everything else dims. Derived from each finding's
683
665
  // target:
684
666
  // component → that node
685
- // relation → the edge it flags (+ its two endpoint nodes)
686
667
  // module → every component in that module
687
668
  // walkthrough → the flow's nodes + its hop edges
688
669
  // repo → every component of that repo
@@ -707,23 +688,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
707
688
  if (a)
708
689
  nodeIds.add(a);
709
690
  }
710
- else if (t.kind === 'relation') {
711
- // The target names the two endpoints (`from → to`); light the edge and
712
- // its endpoints. Falls back to endpoints alone if no edge matches.
713
- const edge = issueEdge(issue);
714
- if (edge) {
715
- edgeIds.add(edge.id);
716
- nodeIds.add(edge.source);
717
- nodeIds.add(edge.target);
718
- }
719
- else {
720
- for (const part of `${t.id ?? ''} → ${t.label}`.split('→')) {
721
- const a = resolveAlias(part);
722
- if (a)
723
- nodeIds.add(a);
724
- }
725
- }
726
- }
727
691
  else if (t.kind === 'module') {
728
692
  const key = t.id ?? t.label;
729
693
  const prefix = `${key.replace(/\/$/, '')}/`;
@@ -763,7 +727,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
763
727
  edgeIds.add(e.id);
764
728
  }
765
729
  return { nodeIds, edgeIds };
766
- }, [issues, expandedIssueCategories, components, walkthroughs, baseEdges, resolveAlias, issueEdge]);
730
+ }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
767
731
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
768
732
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
769
733
  // When an edge is selected, dim every other edge + its label to focus it.
@@ -1006,13 +970,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1006
970
  };
1007
971
  };
1008
972
  // Edges outside the selected view are hidden entirely (labels included).
1009
- // graphify-native edges belong to the relations view (they are static
973
+ // graphify-native edges belong to the graphify view (they are static
1010
974
  // topology, not runtime hops) regardless of their verb.
1011
975
  const edgeInView = (e) => {
1012
976
  const d = e.data;
1013
977
  const mechanism = d?.mechanism ?? 'uses';
1014
- if (resolvedEdgeView === 'relations') {
1015
- return d?.provenance === 'graphify' || isRelationMechanism(mechanism);
978
+ if (resolvedEdgeView === 'graphify') {
979
+ return d?.provenance === 'graphify';
1016
980
  }
1017
981
  return isWalkthroughMechanism(mechanism);
1018
982
  };
@@ -1093,7 +1057,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1093
1057
  setSelectedEdgeId(null);
1094
1058
  setFocusedWalkthroughId(null);
1095
1059
  setFocusedStepIndex(null);
1096
- setEdgeViewOverride(null);
1097
1060
  issueModuleFocusRef.current = null;
1098
1061
  }, []);
1099
1062
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
@@ -1113,7 +1076,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1113
1076
  useEffect(() => {
1114
1077
  if (!issuesActive) {
1115
1078
  setExpandedIssueCategories([]);
1116
- setEdgeViewOverride(null);
1117
1079
  }
1118
1080
  }, [issuesActive]);
1119
1081
  const toggleIssues = () => {
@@ -1541,9 +1503,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1541
1503
  if (playable.length === 0)
1542
1504
  return;
1543
1505
  // Only run the cycles that can actually be shown in the current edge view.
1544
- const inView = playable.filter((tl) => tl.steps.some((s) => resolvedEdgeView === 'relations'
1545
- ? isRelationMechanism(s.mechanism)
1546
- : isWalkthroughMechanism(s.mechanism)));
1506
+ const inView = playable.filter((tl) => tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)));
1547
1507
  if (inView.length === 0)
1548
1508
  return;
1549
1509
  const cyc = inView;
@@ -1696,15 +1656,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1696
1656
  onSelect?.(comp.alias);
1697
1657
  }, [components, onSelect]);
1698
1658
  // Issue click → select + frame the target on the canvas. Component targets
1699
- // frame their node; relation targets frame their edge (endpoints + the routed
1700
- // line); other kinds (module / flow / repo) have no single element and just
1701
- // forward. Selections / focus that would hide the target are cleared first,
1702
- // then the camera flies to it on the next frame. The host's `onSelectIssue`
1703
- // still fires afterwards for its own detail.
1659
+ // frame their node; other kinds (module / flow / repo) have no single element
1660
+ // and just forward. Selections / focus that would hide the target are cleared
1661
+ // first, then the camera flies to it on the next frame. The host's
1662
+ // `onSelectIssue` still fires afterwards for its own detail.
1704
1663
  const focusIssueTarget = useCallback((issue) => {
1705
1664
  const comp = issueComponent(issue);
1706
- const edge = comp ? null : issueEdge(issue);
1707
- const step = comp || edge ? null : issueStep(issue);
1665
+ const step = comp ? null : issueStep(issue);
1708
1666
  if (comp) {
1709
1667
  setSelected(comp);
1710
1668
  setSelectedEdgeId(null);
@@ -1723,24 +1681,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1723
1681
  });
1724
1682
  });
1725
1683
  }
1726
- else if (edge) {
1727
- setSelected(null);
1728
- setFocusedWalkthroughId(null);
1729
- setFocusedStepIndex(null);
1730
- setHoveredWalkthroughStep(null);
1731
- setSelectedEdgeId(edge.id);
1732
- issueModuleFocusRef.current = null;
1733
- // Flip to the vocabulary this edge belongs to, so a hop flagged in the
1734
- // relations view (or vice versa) is actually drawn while focused.
1735
- const d = edge.data;
1736
- const mechanism = d?.mechanism ?? 'uses';
1737
- setEdgeViewOverride(d?.provenance === 'graphify' || isRelationMechanism(mechanism)
1738
- ? 'relations'
1739
- : 'walkthroughs');
1740
- requestAnimationFrame(() => {
1741
- fitFocusBounds(new Set([edge.id]));
1742
- });
1743
- }
1744
1684
  else if (step) {
1745
1685
  // A step finding focuses the step itself, through the same entry point
1746
1686
  // the walkthrough panel uses — so the drawer, the step numbering, the
@@ -1768,14 +1708,12 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1768
1708
  onSelectIssue?.(issue);
1769
1709
  }, [
1770
1710
  issueComponent,
1771
- issueEdge,
1772
1711
  issueStep,
1773
1712
  issueModuleNodeId,
1774
1713
  focusWalkthroughStep,
1775
1714
  onSelect,
1776
1715
  onSelectIssue,
1777
1716
  fitView,
1778
- fitFocusBounds,
1779
1717
  fitNodeRects,
1780
1718
  ]);
1781
1719
  // Collapsing the issue card again undoes the focus: drop the node / edge
@@ -1793,16 +1731,9 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1793
1731
  setSelectedEdgeId(null);
1794
1732
  }
1795
1733
  else {
1796
- const edge = issueEdge(issue);
1797
- const step = edge ? null : issueStep(issue);
1798
- const moduleNodeId = edge || step ? null : issueModuleNodeId(issue);
1799
- if (edge) {
1800
- if (selectedEdgeIdRef.current !== edge.id)
1801
- return;
1802
- setSelectedEdgeId(null);
1803
- setEdgeViewOverride(null);
1804
- }
1805
- else if (step) {
1734
+ const step = issueStep(issue);
1735
+ const moduleNodeId = step ? null : issueModuleNodeId(issue);
1736
+ if (step) {
1806
1737
  // Only unwind a step focus this card still owns. A dim-mode graph
1807
1738
  // keeps its step focus in hover state instead (owned by the pointer,
1808
1739
  // not the card), and correctly leaves that alone here.
@@ -1828,7 +1759,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1828
1759
  requestAnimationFrame(() => {
1829
1760
  fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
1830
1761
  });
1831
- }, [issueComponent, issueEdge, issueStep, issueModuleNodeId, fitView]);
1762
+ }, [issueComponent, issueStep, issueModuleNodeId, fitView]);
1832
1763
  // Construct tokens inside a walkthrough snippet. A step's line is an edge
1833
1764
  // between its `from`/`to` components, so a token naming either of those
1834
1765
  // constructs should navigate to that construct's declaration. Index the
@@ -2316,7 +2247,6 @@ export function SubsystemComponentGraph(props) {
2316
2247
  }, []);
2317
2248
  const constructsOnly = isConstructsOnlyModel({
2318
2249
  components: props.components,
2319
- relations: props.relations,
2320
2250
  walkthroughs: props.walkthroughs,
2321
2251
  graphifyRelations: props.graphifyRelations,
2322
2252
  });