@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
@@ -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: [],
@@ -165,24 +165,19 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
165
165
  const [sidebarView, setSidebarView] = useState(() => persisted.sidebarView ?? (walkthroughs?.length ? 'walkthroughs' : 'files'));
166
166
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
167
167
  const [diagnosticsTab, setDiagnosticsTab] = useState(() => persisted.diagnosticsTab ?? '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,
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.
@@ -381,13 +376,11 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
381
376
  // Pass 1: build with estimated widths so React Flow can measure the DOM.
382
377
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
383
378
  // fields only — declarationRef updates after verify must not re-run ELK.
384
- const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, relations, walkthroughs, graphifyRelations }), [components, relations, walkthroughs, graphifyRelations]);
379
+ const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }), [components, walkthroughs, graphifyRelations]);
385
380
  const componentsRef = useRef(components);
386
- const relationsRef = useRef(relations);
387
381
  const walkthroughsRef = useRef(walkthroughs);
388
382
  const graphifyRelationsRef = useRef(graphifyRelations);
389
383
  componentsRef.current = components;
390
- relationsRef.current = relations;
391
384
  walkthroughsRef.current = walkthroughs;
392
385
  graphifyRelationsRef.current = graphifyRelations;
393
386
  // Track measured dimensions from React Flow's dimension changes.
@@ -406,7 +399,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
406
399
  pass2GenRef.current += 1;
407
400
  const doc = {
408
401
  components: componentsRef.current,
409
- relations: relationsRef.current,
410
402
  walkthroughs: walkthroughsRef.current,
411
403
  };
412
404
  void buildSubsystemGraph(doc, {
@@ -465,7 +457,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
465
457
  const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
466
458
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
467
459
  const gen = ++pass2GenRef.current;
468
- void buildSubsystemGraph({ components, relations, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine })
460
+ void buildSubsystemGraph({ components, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, graphifyRelations, orderByLine })
469
461
  .then(({ nodes, edges: e, absoluteRects }) => {
470
462
  if (gen !== pass2GenRef.current)
471
463
  return;
@@ -479,7 +471,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
479
471
  // Reveal Pass 1 layout rather than leaving the cover up forever.
480
472
  setLayoutReady(true);
481
473
  });
482
- }, [built.nodes, components, relations, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
474
+ }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels]);
483
475
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
484
476
  // Same-id live updates often get no new React Flow `dimensions` events, so
485
477
  // waiting only on onNodesChange leaves the cover stuck.
@@ -559,7 +551,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
559
551
  const baseEdges = convertedEdges;
560
552
  // Resolve an issue's component target to the matching node. `id` is the
561
553
  // stable alias; `label` may be an alias, name, or symbol. Non-component
562
- // targets (relation / module / flow / repo) have no single node → null.
554
+ // targets (module / flow / repo) have no single node → null.
563
555
  const issueComponent = useCallback((issue) => {
564
556
  const target = issue.target;
565
557
  if (target?.kind !== 'component')
@@ -569,26 +561,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
569
561
  c.name === target.label ||
570
562
  c.symbol === target.label) ?? null);
571
563
  }, [components]);
572
- // Resolve a `relation` issue target to the display edge it flags. The target
573
- // names the two endpoints (`from → to`); match an edge joining both, in
574
- // either direction, preferring the target's `detail` mechanism when it names
575
- // one. Null when no display edge matches.
576
- const issueEdge = useCallback((issue) => {
577
- const target = issue.target;
578
- if (target?.kind !== 'relation')
579
- return null;
580
- const parts = `${target.id ?? ''} → ${target.label}`
581
- .split('→')
582
- .map((p) => p.replace(/\(\)$/, '').trim())
583
- .filter(Boolean);
584
- const joins = (e) => parts.includes(e.source) && parts.includes(e.target);
585
- const matches = baseEdges.filter(joins);
586
- const byMechanism = target.detail
587
- ? matches.filter((e) => e.data?.mechanism ===
588
- target.detail)
589
- : matches;
590
- return byMechanism[0] ?? matches[0] ?? null;
591
- }, [baseEdges]);
592
564
  // A `module` issue target names a boundary frame, so focusing the card frames
593
565
  // that region on the canvas. Null when the target names no frame (a singleton
594
566
  // dropped from the layout, or a region key nothing declares).
@@ -692,7 +664,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
692
664
  // the canvas keeps while everything else dims. Derived from each finding's
693
665
  // target:
694
666
  // component → that node
695
- // relation → the edge it flags (+ its two endpoint nodes)
696
667
  // module → every component in that module
697
668
  // walkthrough → the flow's nodes + its hop edges
698
669
  // repo → every component of that repo
@@ -717,23 +688,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
717
688
  if (a)
718
689
  nodeIds.add(a);
719
690
  }
720
- else if (t.kind === 'relation') {
721
- // The target names the two endpoints (`from → to`); light the edge and
722
- // its endpoints. Falls back to endpoints alone if no edge matches.
723
- const edge = issueEdge(issue);
724
- if (edge) {
725
- edgeIds.add(edge.id);
726
- nodeIds.add(edge.source);
727
- nodeIds.add(edge.target);
728
- }
729
- else {
730
- for (const part of `${t.id ?? ''} → ${t.label}`.split('→')) {
731
- const a = resolveAlias(part);
732
- if (a)
733
- nodeIds.add(a);
734
- }
735
- }
736
- }
737
691
  else if (t.kind === 'module') {
738
692
  const key = t.id ?? t.label;
739
693
  const prefix = `${key.replace(/\/$/, '')}/`;
@@ -773,7 +727,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
773
727
  edgeIds.add(e.id);
774
728
  }
775
729
  return { nodeIds, edgeIds };
776
- }, [issues, expandedIssueCategories, components, walkthroughs, baseEdges, resolveAlias, issueEdge]);
730
+ }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
777
731
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
778
732
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
779
733
  // When an edge is selected, dim every other edge + its label to focus it.
@@ -1016,13 +970,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1016
970
  };
1017
971
  };
1018
972
  // Edges outside the selected view are hidden entirely (labels included).
1019
- // graphify-native edges belong to the relations view (they are static
973
+ // graphify-native edges belong to the graphify view (they are static
1020
974
  // topology, not runtime hops) regardless of their verb.
1021
975
  const edgeInView = (e) => {
1022
976
  const d = e.data;
1023
977
  const mechanism = d?.mechanism ?? 'uses';
1024
- if (resolvedEdgeView === 'relations') {
1025
- return d?.provenance === 'graphify' || isRelationMechanism(mechanism);
978
+ if (resolvedEdgeView === 'graphify') {
979
+ return d?.provenance === 'graphify';
1026
980
  }
1027
981
  return isWalkthroughMechanism(mechanism);
1028
982
  };
@@ -1103,7 +1057,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1103
1057
  setSelectedEdgeId(null);
1104
1058
  setFocusedWalkthroughId(null);
1105
1059
  setFocusedStepIndex(null);
1106
- setEdgeViewOverride(null);
1107
1060
  issueModuleFocusRef.current = null;
1108
1061
  }, []);
1109
1062
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
@@ -1123,7 +1076,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1123
1076
  useEffect(() => {
1124
1077
  if (!issuesActive) {
1125
1078
  setExpandedIssueCategories([]);
1126
- setEdgeViewOverride(null);
1127
1079
  }
1128
1080
  }, [issuesActive]);
1129
1081
  const toggleIssues = () => {
@@ -1551,9 +1503,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1551
1503
  if (playable.length === 0)
1552
1504
  return;
1553
1505
  // Only run the cycles that can actually be shown in the current edge view.
1554
- const inView = playable.filter((tl) => tl.steps.some((s) => resolvedEdgeView === 'relations'
1555
- ? isRelationMechanism(s.mechanism)
1556
- : isWalkthroughMechanism(s.mechanism)));
1506
+ const inView = playable.filter((tl) => tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)));
1557
1507
  if (inView.length === 0)
1558
1508
  return;
1559
1509
  const cyc = inView;
@@ -1706,15 +1656,13 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1706
1656
  onSelect?.(comp.alias);
1707
1657
  }, [components, onSelect]);
1708
1658
  // Issue click → select + frame the target on the canvas. Component targets
1709
- // frame their node; relation targets frame their edge (endpoints + the routed
1710
- // line); other kinds (module / flow / repo) have no single element and just
1711
- // forward. Selections / focus that would hide the target are cleared first,
1712
- // then the camera flies to it on the next frame. The host's `onSelectIssue`
1713
- // 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.
1714
1663
  const focusIssueTarget = useCallback((issue) => {
1715
1664
  const comp = issueComponent(issue);
1716
- const edge = comp ? null : issueEdge(issue);
1717
- const step = comp || edge ? null : issueStep(issue);
1665
+ const step = comp ? null : issueStep(issue);
1718
1666
  if (comp) {
1719
1667
  setSelected(comp);
1720
1668
  setSelectedEdgeId(null);
@@ -1733,24 +1681,6 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1733
1681
  });
1734
1682
  });
1735
1683
  }
1736
- else if (edge) {
1737
- setSelected(null);
1738
- setFocusedWalkthroughId(null);
1739
- setFocusedStepIndex(null);
1740
- setHoveredWalkthroughStep(null);
1741
- setSelectedEdgeId(edge.id);
1742
- issueModuleFocusRef.current = null;
1743
- // Flip to the vocabulary this edge belongs to, so a hop flagged in the
1744
- // relations view (or vice versa) is actually drawn while focused.
1745
- const d = edge.data;
1746
- const mechanism = d?.mechanism ?? 'uses';
1747
- setEdgeViewOverride(d?.provenance === 'graphify' || isRelationMechanism(mechanism)
1748
- ? 'relations'
1749
- : 'walkthroughs');
1750
- requestAnimationFrame(() => {
1751
- fitFocusBounds(new Set([edge.id]));
1752
- });
1753
- }
1754
1684
  else if (step) {
1755
1685
  // A step finding focuses the step itself, through the same entry point
1756
1686
  // the walkthrough panel uses — so the drawer, the step numbering, the
@@ -1778,14 +1708,12 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1778
1708
  onSelectIssue?.(issue);
1779
1709
  }, [
1780
1710
  issueComponent,
1781
- issueEdge,
1782
1711
  issueStep,
1783
1712
  issueModuleNodeId,
1784
1713
  focusWalkthroughStep,
1785
1714
  onSelect,
1786
1715
  onSelectIssue,
1787
1716
  fitView,
1788
- fitFocusBounds,
1789
1717
  fitNodeRects,
1790
1718
  ]);
1791
1719
  // Collapsing the issue card again undoes the focus: drop the node / edge
@@ -1803,16 +1731,9 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1803
1731
  setSelectedEdgeId(null);
1804
1732
  }
1805
1733
  else {
1806
- const edge = issueEdge(issue);
1807
- const step = edge ? null : issueStep(issue);
1808
- const moduleNodeId = edge || step ? null : issueModuleNodeId(issue);
1809
- if (edge) {
1810
- if (selectedEdgeIdRef.current !== edge.id)
1811
- return;
1812
- setSelectedEdgeId(null);
1813
- setEdgeViewOverride(null);
1814
- }
1815
- else if (step) {
1734
+ const step = issueStep(issue);
1735
+ const moduleNodeId = step ? null : issueModuleNodeId(issue);
1736
+ if (step) {
1816
1737
  // Only unwind a step focus this card still owns. A dim-mode graph
1817
1738
  // keeps its step focus in hover state instead (owned by the pointer,
1818
1739
  // not the card), and correctly leaves that alone here.
@@ -1838,7 +1759,7 @@ function Inner({ components, relations, walkthroughs, graphifyRelations, orderBy
1838
1759
  requestAnimationFrame(() => {
1839
1760
  fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
1840
1761
  });
1841
- }, [issueComponent, issueEdge, issueStep, issueModuleNodeId, fitView]);
1762
+ }, [issueComponent, issueStep, issueModuleNodeId, fitView]);
1842
1763
  // Construct tokens inside a walkthrough snippet. A step's line is an edge
1843
1764
  // between its `from`/`to` components, so a token naming either of those
1844
1765
  // constructs should navigate to that construct's declaration. Index the
@@ -2326,7 +2247,6 @@ export function SubsystemComponentGraph(props) {
2326
2247
  }, []);
2327
2248
  const constructsOnly = isConstructsOnlyModel({
2328
2249
  components: props.components,
2329
- relations: props.relations,
2330
2250
  walkthroughs: props.walkthroughs,
2331
2251
  graphifyRelations: props.graphifyRelations,
2332
2252
  });