@principal-ai/subsystems-react 0.44.3 → 0.45.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 (129) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +8 -8
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/pierre/{PierreWalkthroughCodeView.d.ts → PierreTrailCodeView.d.ts} +11 -11
  7. package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -0
  8. package/dist/pierre/{PierreWalkthroughCodeView.js → PierreTrailCodeView.js} +36 -36
  9. package/dist/pierre/PierreTrailCodeView.js.map +1 -0
  10. package/dist/pierre/index.d.ts +2 -2
  11. package/dist/pierre/index.d.ts.map +1 -1
  12. package/dist/pierre/index.js +1 -1
  13. package/dist/pierre/index.js.map +1 -1
  14. package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.d.ts.map +1 -1
  15. package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.js +1 -1
  16. package/dist/stories/Subsystem/AggregateGraph/aggregateViewFixture.js.map +1 -1
  17. package/dist/stories/Subsystem/C4Graph/c4Fixture.js +1 -1
  18. package/dist/stories/Subsystem/C4Graph/c4Fixture.js.map +1 -1
  19. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +9 -9
  20. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  21. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +12 -12
  22. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  23. package/dist/subsystem/C4Graph.d.ts +2 -2
  24. package/dist/subsystem/C4Graph.js +2 -2
  25. package/dist/subsystem/ConstructsCatalog.d.ts +1 -1
  26. package/dist/subsystem/ConstructsCatalog.js +1 -1
  27. package/dist/subsystem/DiagnosticToggle.d.ts +1 -1
  28. package/dist/subsystem/DiagnosticToggle.js +1 -1
  29. package/dist/subsystem/FileDrawer.d.ts +2 -2
  30. package/dist/subsystem/FileDrawer.js +2 -2
  31. package/dist/subsystem/IssueList.d.ts +6 -6
  32. package/dist/subsystem/IssueList.d.ts.map +1 -1
  33. package/dist/subsystem/IssueList.js +9 -9
  34. package/dist/subsystem/IssueList.js.map +1 -1
  35. package/dist/subsystem/SubsystemAggregateGraph.d.ts +3 -3
  36. package/dist/subsystem/SubsystemAggregateGraph.d.ts.map +1 -1
  37. package/dist/subsystem/SubsystemAggregateGraph.js +2 -2
  38. package/dist/subsystem/SubsystemComponentGraph.d.ts +45 -45
  39. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  40. package/dist/subsystem/SubsystemComponentGraph.js +307 -309
  41. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  42. package/dist/subsystem/SubsystemModelTransition.d.ts +10 -10
  43. package/dist/subsystem/SubsystemModelTransition.d.ts.map +1 -1
  44. package/dist/subsystem/SubsystemModelTransition.js +42 -42
  45. package/dist/subsystem/SubsystemModelTransition.js.map +1 -1
  46. package/dist/subsystem/TrailsPanel.d.ts +55 -0
  47. package/dist/subsystem/TrailsPanel.d.ts.map +1 -0
  48. package/dist/subsystem/{WalkthroughsPanel.js → TrailsPanel.js} +51 -51
  49. package/dist/subsystem/TrailsPanel.js.map +1 -0
  50. package/dist/subsystem/declarationRef.d.ts +1 -1
  51. package/dist/subsystem/declarationRef.d.ts.map +1 -1
  52. package/dist/subsystem/model.d.ts +40 -40
  53. package/dist/subsystem/model.d.ts.map +1 -1
  54. package/dist/subsystem/model.js +17 -17
  55. package/dist/subsystem/model.js.map +1 -1
  56. package/dist/subsystem/nodes.d.ts +30 -0
  57. package/dist/subsystem/nodes.d.ts.map +1 -1
  58. package/dist/subsystem/nodes.js +29 -0
  59. package/dist/subsystem/nodes.js.map +1 -1
  60. package/dist/subsystem/toC4.d.ts +2 -2
  61. package/dist/subsystem/toC4.d.ts.map +1 -1
  62. package/dist/subsystem/toC4.js +2 -2
  63. package/dist/subsystem/toC4.js.map +1 -1
  64. package/dist/subsystem/trailBrief.d.ts +18 -0
  65. package/dist/subsystem/trailBrief.d.ts.map +1 -0
  66. package/dist/subsystem/{walkthroughBrief.js → trailBrief.js} +8 -8
  67. package/dist/subsystem/trailBrief.js.map +1 -0
  68. package/package.json +3 -3
  69. package/src/index.ts +15 -15
  70. package/src/pierre/{PierreWalkthroughCodeView.tsx → PierreTrailCodeView.tsx} +56 -56
  71. package/src/pierre/index.ts +2 -2
  72. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  73. package/src/stories/Subsystem/AggregateGraph/AggregateFrameGraph.stories.tsx +1 -1
  74. package/src/stories/Subsystem/AggregateGraph/aggregateViewFixture.ts +1 -1
  75. package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
  76. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +8 -8
  77. package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +13 -13
  78. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -2
  79. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +5 -5
  80. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +8 -8
  81. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
  82. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +6 -6
  83. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +12 -12
  84. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +36 -36
  85. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +7 -7
  86. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  87. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +8 -8
  88. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +4 -4
  89. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +3 -3
  90. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +5 -5
  91. package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +6 -6
  92. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  93. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +4 -4
  94. package/src/stories/Subsystem/ComponentGraph/{ProposedWalkthroughs.stories.tsx → ProposedTrails.stories.tsx} +9 -9
  95. package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +19 -19
  96. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +21 -21
  97. package/src/stories/Subsystem/ComponentGraph/{WalkthroughAutoplay.stories.tsx → TrailAutoplay.stories.tsx} +22 -22
  98. package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +3 -3
  99. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +19 -19
  100. package/src/subsystem/C4Graph.tsx +2 -2
  101. package/src/subsystem/ConstructsCatalog.tsx +1 -1
  102. package/src/subsystem/DiagnosticToggle.tsx +1 -1
  103. package/src/subsystem/FileDrawer.tsx +2 -2
  104. package/src/subsystem/IssueList.focus.test.tsx +4 -4
  105. package/src/subsystem/IssueList.icon.test.tsx +3 -3
  106. package/src/subsystem/IssueList.tsx +14 -14
  107. package/src/subsystem/SubsystemAggregateGraph.tsx +3 -3
  108. package/src/subsystem/SubsystemComponentGraph.tsx +400 -403
  109. package/src/subsystem/SubsystemModelTransition.tsx +61 -61
  110. package/src/subsystem/{WalkthroughsPanel.test.tsx → TrailsPanel.test.tsx} +16 -16
  111. package/src/subsystem/{WalkthroughsPanel.tsx → TrailsPanel.tsx} +81 -81
  112. package/src/subsystem/declarationRef.ts +1 -1
  113. package/src/subsystem/formatDeclaration.test.ts +6 -6
  114. package/src/subsystem/model.test.ts +25 -25
  115. package/src/subsystem/model.ts +51 -51
  116. package/src/subsystem/nodes.test.ts +42 -1
  117. package/src/subsystem/nodes.tsx +38 -0
  118. package/src/subsystem/toC4.test.ts +1 -1
  119. package/src/subsystem/toC4.ts +3 -3
  120. package/src/subsystem/{walkthroughBrief.test.ts → trailBrief.test.ts} +10 -10
  121. package/src/subsystem/{walkthroughBrief.ts → trailBrief.ts} +8 -8
  122. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +0 -1
  123. package/dist/pierre/PierreWalkthroughCodeView.js.map +0 -1
  124. package/dist/subsystem/WalkthroughsPanel.d.ts +0 -55
  125. package/dist/subsystem/WalkthroughsPanel.d.ts.map +0 -1
  126. package/dist/subsystem/WalkthroughsPanel.js.map +0 -1
  127. package/dist/subsystem/walkthroughBrief.d.ts +0 -18
  128. package/dist/subsystem/walkthroughBrief.d.ts.map +0 -1
  129. package/dist/subsystem/walkthroughBrief.js.map +0 -1
@@ -8,7 +8,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
8
8
  * inside one labeled boundary frame; nodes without one sit outside every
9
9
  * boundary. Clicking a component invokes `onSelect`.
10
10
  *
11
- * When the model has components but no topology or walkthrough edges, the
11
+ * When the model has components but no topology or trail edges, the
12
12
  * canvas is a constructs catalog (list + signature) instead of a graph.
13
13
  *
14
14
  * This is a focused fork of the package's `GraphRenderer` pipeline (same ELK
@@ -20,11 +20,11 @@ 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, isWalkthroughMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
23
+ import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, isTrailMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, trailStepGraphEdgeId, } from './model';
24
24
  import { ConstructsCatalog } from './ConstructsCatalog';
25
25
  import { SubsystemAgentsPanel, } from './AgentsPanel';
26
26
  import { MaintainLivePanel, } from '../components/maintain-events/MaintainLivePanel';
27
- import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
27
+ import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility, flowNodeVisibility } from './nodes';
28
28
  import { SubsystemDiagnosticToggle } from './DiagnosticToggle';
29
29
  import { SubsystemIssueList, issueCategory, issueKindOrder, issueRung, ISSUE_KIND_ICON, ISSUE_RUNG_ORDER, } from './IssueList';
30
30
  import { SubsystemFileTree } from './SubsystemFileTree';
@@ -33,7 +33,7 @@ import { GRAPH_CANVAS_CLASS, GRAPH_NAV_PROPS, GraphChrome, GraphLayerStyle } fro
33
33
  import { ComponentDeclaration } from './ComponentDeclaration';
34
34
  import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
35
35
  import { buildRepoGroups, repoAvatarUrl } from './paths';
36
- import { WalkthroughsPanel, WALKTHROUGH_PLAY_PAUSE_MS } from './WalkthroughsPanel';
36
+ import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
37
37
  import { EDGE_LABEL_WIDTH, EDGE_LABEL_HEIGHT, EDGE_LABEL_FONT_SIZE, EDGE_LABEL_CLOUD_PATH, EDGE_LABEL_CLOUD_EXTRA_TOP, } from '../utils/edgeLabel';
38
38
  /** Identifiers a token could match to name this component as a construct. */
39
39
  function constructIdentifiers(comp) {
@@ -112,9 +112,9 @@ function FileOverlayCloseButton({ onClose }) {
112
112
  transition: 'background 120ms ease, color 120ms ease',
113
113
  }, children: _jsx(X, { size: 14 }) }));
114
114
  }
115
- const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render, walkthrough, stepIndex, onOpenFile, proposedAliases, resolveSymbol, onSymbolClick, }) {
115
+ const TrailDrawerContent = memo(function TrailDrawerContent({ render, trail, stepIndex, onOpenFile, proposedAliases, resolveSymbol, onSymbolClick, }) {
116
116
  return (_jsx(_Fragment, { children: render({
117
- walkthrough,
117
+ trail,
118
118
  stepIndex,
119
119
  onOpenFile,
120
120
  proposedAliases,
@@ -122,14 +122,14 @@ const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render
122
122
  onSymbolClick,
123
123
  }) }));
124
124
  });
125
- function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, 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, trails, graphifyRelations, orderByLine, initialTrailId, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }) {
126
126
  const { theme } = useTheme();
127
127
  const { fitView, fitBounds, screenToFlowPosition } = 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({ walkthroughs }), [walkthroughs]);
132
+ const graphEdges = useMemo(() => deriveGraphEdges({ trails }), [trails]);
133
133
  const [built, setBuilt] = useState({
134
134
  nodes: [],
135
135
  edges: [],
@@ -137,9 +137,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
137
137
  });
138
138
  const [layoutReady, setLayoutReady] = useState(false);
139
139
  const [selected, setSelected] = useState(null);
140
- /** Bottom drawer: single file or walkthrough multi-snippet mode. */
140
+ /** Bottom drawer: single file or trail multi-snippet mode. */
141
141
  const [drawerTarget, setDrawerTarget] = useState(null);
142
- /** Full-file layer over an open walkthrough drawer — walkthrough stays mounted. */
142
+ /** Full-file layer over an open trail drawer — trail stays mounted. */
143
143
  const [fileOverlay, setFileOverlay] = useState(null);
144
144
  // Mirror so step-focus can decide whether to wait for the drawer open
145
145
  // transition before fitView (avoids framing against full-height canvas).
@@ -214,39 +214,46 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
214
214
  cancelAnimationFrame(raf);
215
215
  };
216
216
  }, [pathFrameRects, screenToFlowPosition]);
217
- // Walkthrough focus — selected flow (or step) is full strength; other
217
+ // Trail focus — selected flow (or step) is full strength; other
218
218
  // opened-flow members stay visible but dimmed; everything else is hidden.
219
219
  // Restored from the persisted view state when a `persistKey` is set.
220
- const [focusedWalkthroughId, setFocusedWalkthroughId] = useState(persisted.focusedWalkthroughId ?? null);
220
+ const [focusedTrailId, setFocusedTrailId] = useState(persisted.focusedTrailId ?? null);
221
221
  // `null` = whole flow focused; a number = that single step's edge focused.
222
222
  const [focusedStepIndex, setFocusedStepIndex] = useState(persisted.focusedStepIndex ?? null);
223
- // Hovered walkthrough in the flows panel: dims every canvas node/edge not
223
+ // Hovered trail in the flows panel: dims every canvas node/edge not
224
224
  // involved in the hover preview (or selected ∪ hovered when a step is
225
225
  // focused). `stepIndex: null` = whole flow (collapsed title hover);
226
226
  // a number = that step. Transient — no drawer. Camera only reframes when
227
227
  // a step is already selected.
228
- const [hoveredWalkthroughStep, setHoveredWalkthroughStep] = useState(null);
229
- // Sidebar bottom half: which panel is shown when walkthroughs exist.
230
- const [sidebarView, setSidebarView] = useState(() => persisted.sidebarView ?? (walkthroughs?.length ? 'walkthroughs' : 'files'));
228
+ const [hoveredTrailStep, setHoveredTrailStep] = useState(null);
229
+ // Sidebar bottom half: which panel is shown when trails exist.
230
+ // The persisted value is membership-checked rather than trusted: a stale
231
+ // localStorage entry from before the walkthrough->trail rename would match
232
+ // neither panel and render a blank sidebar with no selected tab.
233
+ const [sidebarView, setSidebarView] = useState(() => {
234
+ const stored = persisted.sidebarView;
235
+ const restored = stored === 'files' || stored === 'trails' ? stored : 'trails';
236
+ return trails?.length ? restored : 'files';
237
+ });
231
238
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
232
239
  const [diagnosticsTab, setDiagnosticsTab] = useState(() => persisted.diagnosticsTab ?? 'issues');
233
240
  // One edge source at a time. When the caller doesn't pick, the sidebar's
234
- // Files / Walkthroughs tab picks: Files draws graphify static edges,
235
- // Walkthroughs draws runtime hop edges. Without visible tabs (no walkthroughs,
236
- // or a sidebar-less embed) fall back to walkthrough edges.
237
- const sidebarTabsVisible = !hideSidebar && (walkthroughs?.length ?? 0) > 0;
241
+ // Files / Trails tab picks: Files draws graphify static edges,
242
+ // Trails draws runtime step edges. Without visible tabs (no trails,
243
+ // or a sidebar-less embed) fall back to trail edges.
244
+ const sidebarTabsVisible = !hideSidebar && (trails?.length ?? 0) > 0;
238
245
  const resolvedEdgeView = edgeView ??
239
246
  (sidebarTabsVisible
240
- ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
241
- : (walkthroughs?.length ?? 0) > 0
242
- ? 'walkthroughs'
247
+ ? (sidebarView === 'trails' ? 'trails' : 'graphify')
248
+ : (trails?.length ?? 0) > 0
249
+ ? 'trails'
243
250
  : (graphifyRelations?.length ?? 0) > 0
244
251
  ? 'graphify'
245
- : 'walkthroughs');
246
- // Walkthrough flows the user has expanded (via the title row). Closed by
252
+ : 'trails');
253
+ // Trail flows the user has expanded (via the title row). Closed by
247
254
  // default so a graph with several flows doesn't dump every step list at once.
248
255
  // Restored from the persisted view state when a `persistKey` is set.
249
- const [expandedWalkthroughs, setExpandedWalkthroughs] = useState(() => new Set(persisted.expandedWalkthroughs ?? []));
256
+ const [expandedTrails, setExpandedTrails] = useState(() => new Set(persisted.expandedTrails ?? []));
250
257
  // Sidebar description visibility. Hidden by default so the files/flows
251
258
  // panel gets the vertical room; the title-row toggle reveals it.
252
259
  const [descriptionVisible, setDescriptionVisible] = useState(false);
@@ -296,20 +303,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
296
303
  document.removeEventListener('mouseup', onUp);
297
304
  };
298
305
  }, [sidebarDrag, sidebarMaxWidth, fitView, persistKey]);
299
- // Persist the walkthrough working set per model. Each write merges into the
306
+ // Persist the trail working set per model. Each write merges into the
300
307
  // stored blob, so the three fields never clobber one another.
301
308
  useEffect(() => {
302
309
  if (!persistKey)
303
310
  return;
304
311
  writeViewState(persistKey, {
305
- expandedWalkthroughs: Array.from(expandedWalkthroughs),
312
+ expandedTrails: Array.from(expandedTrails),
306
313
  });
307
- }, [persistKey, expandedWalkthroughs]);
314
+ }, [persistKey, expandedTrails]);
308
315
  useEffect(() => {
309
316
  if (!persistKey)
310
317
  return;
311
- writeViewState(persistKey, { focusedWalkthroughId });
312
- }, [persistKey, focusedWalkthroughId]);
318
+ writeViewState(persistKey, { focusedTrailId });
319
+ }, [persistKey, focusedTrailId]);
313
320
  useEffect(() => {
314
321
  if (!persistKey)
315
322
  return;
@@ -343,60 +350,60 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
343
350
  : drawerTarget?.kind === 'file'
344
351
  ? { file: drawerTarget.file, startLine: drawerTarget.startLine }
345
352
  : null;
346
- const focusedWalkthrough = useMemo(() => {
347
- if (drawerTarget?.kind !== 'walkthrough' || !walkthroughs)
353
+ const focusedTrail = useMemo(() => {
354
+ if (drawerTarget?.kind !== 'trail' || !trails)
348
355
  return null;
349
- return walkthroughs.find((t) => t.id === drawerTarget.walkthroughId) ?? null;
350
- }, [drawerTarget, walkthroughs]);
356
+ return trails.find((t) => t.id === drawerTarget.trailId) ?? null;
357
+ }, [drawerTarget, trails]);
351
358
  // Autoplay focus: a sidebar-less embed has no expanded flow to "open", so
352
359
  // branding every step as a selection would hide all other nodes. Instead the
353
- // autoplay sets this (flow id + step) to frame the hop and dim the rest —
360
+ // autoplay sets this (flow id + step) to frame the step and dim the rest —
354
361
  // same treatment as hover — without hiding anything. Declared before the
355
362
  // overlay memos, which read it to title the canvas chip + step bar.
356
363
  const [autoPlayFocus, setAutoPlayFocus] = useState(null);
357
- // Ref mirror of the drawer's walkthrough id so the symbol resolver stays
364
+ // Ref mirror of the drawer's trail id so the symbol resolver stays
358
365
  // stable across graph re-renders (the drawer is memoized on callback identity).
359
- const drawerWalkthroughIdRef = useRef(null);
360
- drawerWalkthroughIdRef.current = focusedWalkthrough?.id ?? null;
361
- // Walkthrough shown on the canvas title chip (focus, hover highlight, or
366
+ const drawerTrailIdRef = useRef(null);
367
+ drawerTrailIdRef.current = focusedTrail?.id ?? null;
368
+ // Trail shown on the canvas title chip (focus, hover highlight, or
362
369
  // autoplay focus).
363
- const overlayWalkthroughTitle = useMemo(() => {
364
- if (!showWalkthroughTitle || !walkthroughs?.length)
370
+ const overlayTrailTitle = useMemo(() => {
371
+ if (!showTrailTitle || !trails?.length)
365
372
  return null;
366
- const id = focusedWalkthroughId ??
367
- autoPlayFocus?.walkthroughId ??
368
- hoveredWalkthroughStep?.walkthroughId;
373
+ const id = focusedTrailId ??
374
+ autoPlayFocus?.trailId ??
375
+ hoveredTrailStep?.trailId;
369
376
  if (!id)
370
377
  return null;
371
- return walkthroughs.find((t) => t.id === id)?.title ?? null;
378
+ return trails.find((t) => t.id === id)?.title ?? null;
372
379
  }, [
373
- showWalkthroughTitle,
374
- walkthroughs,
375
- focusedWalkthroughId,
380
+ showTrailTitle,
381
+ trails,
382
+ focusedTrailId,
376
383
  autoPlayFocus,
377
- hoveredWalkthroughStep,
384
+ hoveredTrailStep,
378
385
  ]);
379
386
  // Active step for the bottom-of-title progress + annotation chip.
380
- const overlayWalkthroughStep = useMemo(() => {
381
- if (!showWalkthroughTitle || !walkthroughs?.length)
387
+ const overlayTrailStep = useMemo(() => {
388
+ if (!showTrailTitle || !trails?.length)
382
389
  return null;
383
- const tlId = focusedWalkthroughId ??
384
- autoPlayFocus?.walkthroughId ??
385
- hoveredWalkthroughStep?.walkthroughId ??
390
+ const tlId = focusedTrailId ??
391
+ autoPlayFocus?.trailId ??
392
+ hoveredTrailStep?.trailId ??
386
393
  null;
387
394
  let stepIndex = null;
388
- if (focusedWalkthroughId != null) {
395
+ if (focusedTrailId != null) {
389
396
  stepIndex = focusedStepIndex;
390
397
  }
391
398
  else if (autoPlayFocus != null) {
392
399
  stepIndex = autoPlayFocus.stepIndex;
393
400
  }
394
- else if (hoveredWalkthroughStep != null) {
395
- stepIndex = hoveredWalkthroughStep.stepIndex;
401
+ else if (hoveredTrailStep != null) {
402
+ stepIndex = hoveredTrailStep.stepIndex;
396
403
  }
397
404
  if (tlId == null || stepIndex == null)
398
405
  return null;
399
- const tl = walkthroughs.find((t) => t.id === tlId);
406
+ const tl = trails.find((t) => t.id === tlId);
400
407
  const step = tl?.steps[stepIndex];
401
408
  if (!step || !tl)
402
409
  return null;
@@ -406,19 +413,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
406
413
  annotation: step.annotation,
407
414
  };
408
415
  }, [
409
- showWalkthroughTitle,
410
- walkthroughs,
411
- focusedWalkthroughId,
416
+ showTrailTitle,
417
+ trails,
418
+ focusedTrailId,
412
419
  focusedStepIndex,
413
420
  autoPlayFocus,
414
- hoveredWalkthroughStep,
421
+ hoveredTrailStep,
415
422
  ]);
416
423
  const drawerTitle = useMemo(() => {
417
424
  if (!drawerTarget)
418
425
  return null;
419
426
  if (drawerTarget.kind === 'file')
420
427
  return drawerTarget.file;
421
- const tl = focusedWalkthrough;
428
+ const tl = focusedTrail;
422
429
  if (!tl)
423
430
  return null;
424
431
  if (drawerTarget.stepIndex == null)
@@ -428,7 +435,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
428
435
  return tl.title;
429
436
  const site = `${step.file.split('/').pop() ?? step.file}:${step.line}`;
430
437
  return `${tl.title} · ${site}`;
431
- }, [drawerTarget, focusedWalkthrough]);
438
+ }, [drawerTarget, focusedTrail]);
432
439
  // Refresh selected component when the components list updates (e.g. verify
433
440
  // writes back declarationRef).
434
441
  useEffect(() => {
@@ -441,12 +448,12 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
441
448
  // Pass 1: build with estimated widths so React Flow can measure the DOM.
442
449
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
443
450
  // fields only — declarationRef updates after verify must not re-run ELK.
444
- const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }), [components, walkthroughs, graphifyRelations]);
451
+ const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, trails, graphifyRelations }), [components, trails, graphifyRelations]);
445
452
  const componentsRef = useRef(components);
446
- const walkthroughsRef = useRef(walkthroughs);
453
+ const trailsRef = useRef(trails);
447
454
  const graphifyRelationsRef = useRef(graphifyRelations);
448
455
  componentsRef.current = components;
449
- walkthroughsRef.current = walkthroughs;
456
+ trailsRef.current = trails;
450
457
  graphifyRelationsRef.current = graphifyRelations;
451
458
  // Track measured dimensions from React Flow's dimension changes.
452
459
  // These arrive as { type: 'dimensions', id, dimensions } in onNodesChange.
@@ -464,7 +471,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
464
471
  pass2GenRef.current += 1;
465
472
  const doc = {
466
473
  components: componentsRef.current,
467
- walkthroughs: walkthroughsRef.current,
474
+ trails: trailsRef.current,
468
475
  };
469
476
  void buildSubsystemGraph(doc, {
470
477
  maxNodeWidth,
@@ -523,7 +530,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
523
530
  const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
524
531
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
525
532
  const gen = ++pass2GenRef.current;
526
- void buildSubsystemGraph({ components, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine })
533
+ void buildSubsystemGraph({ components, trails }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine })
527
534
  .then(({ nodes, edges: e, absoluteRects }) => {
528
535
  if (gen !== pass2GenRef.current)
529
536
  return;
@@ -537,7 +544,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
537
544
  // Reveal Pass 1 layout rather than leaving the cover up forever.
538
545
  setLayoutReady(true);
539
546
  });
540
- }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
547
+ }, [built.nodes, components, trails, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
541
548
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
542
549
  // Same-id live updates often get no new React Flow `dimensions` events, so
543
550
  // waiting only on onNodesChange leaves the cover stuck.
@@ -578,8 +585,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
578
585
  }
579
586
  const src = edgeById.get(edgeId);
580
587
  setSelectedEdgeId(edgeId);
581
- // A direct edge selection on the canvas supersedes any walkthrough focus.
582
- setFocusedWalkthroughId(null);
588
+ // A direct edge selection on the canvas supersedes any trail focus.
589
+ setFocusedTrailId(null);
583
590
  setFocusedStepIndex(null);
584
591
  if (src)
585
592
  onEdgeSelect?.(src);
@@ -597,7 +604,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
597
604
  }
598
605
  setSelected(comp);
599
606
  setSelectedEdgeId(null);
600
- setFocusedWalkthroughId(null);
607
+ setFocusedTrailId(null);
601
608
  setFocusedStepIndex(null);
602
609
  onSelect?.(alias);
603
610
  }
@@ -639,9 +646,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
639
646
  return null;
640
647
  return moduleGroupNodeId(key);
641
648
  }, [components]);
642
- // A `step` issue target names ONE step of a walkthrough — the walkthrough id in
649
+ // A `step` issue target names ONE step of a trail — the trail id in
643
650
  // `id`, the 0-based step position in `stepIndex`. Validated against the loaded
644
- // walkthroughs so a finding left over from an edited flow resolves to null
651
+ // trails so a finding left over from an edited flow resolves to null
645
652
  // rather than framing whatever now happens to sit at that index.
646
653
  const issueStep = useCallback((issue) => {
647
654
  const target = issue.target;
@@ -649,11 +656,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
649
656
  return null;
650
657
  if (target.id == null || target.stepIndex == null)
651
658
  return null;
652
- const walkthrough = walkthroughs?.find((t) => t.id === target.id);
653
- if (!walkthrough?.steps[target.stepIndex])
659
+ const trail = trails?.find((t) => t.id === target.id);
660
+ if (!trail?.steps[target.stepIndex])
654
661
  return null;
655
- return { walkthrough, stepIndex: target.stepIndex };
656
- }, [walkthroughs]);
662
+ return { trail, stepIndex: target.stepIndex };
663
+ }, [trails]);
657
664
  // Per-node diagnostics badge: fold each component-targeted finding that maps
658
665
  // to a verification rung into one badge per node — worst severity wins, the
659
666
  // chip shows the earliest failing rung, and the count tallies the findings.
@@ -731,7 +738,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
731
738
  // target:
732
739
  // component → that node
733
740
  // module → every component in that module
734
- // walkthrough → the flow's nodes + its hop edges
741
+ // trail → the flow's nodes + its step edges
735
742
  // repo → every component of that repo
736
743
  // graph → whole-graph finding; implicates nothing specific
737
744
  // Returns null when nothing is expanded, no expanded layer has findings, or
@@ -766,13 +773,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
766
773
  }
767
774
  }
768
775
  }
769
- else if (t.kind === 'walkthrough') {
770
- const wt = (walkthroughs ?? []).find((w) => w.id === t.id || w.title === t.label);
776
+ else if (t.kind === 'trail') {
777
+ const wt = (trails ?? []).find((w) => w.id === t.id || w.title === t.label);
771
778
  if (wt) {
772
779
  for (const s of wt.steps) {
773
780
  nodeIds.add(s.from);
774
781
  nodeIds.add(s.to);
775
- edgeIds.add(walkthroughStepGraphEdgeId(s));
782
+ edgeIds.add(trailStepGraphEdgeId(s));
776
783
  }
777
784
  }
778
785
  }
@@ -793,7 +800,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
793
800
  edgeIds.add(e.id);
794
801
  }
795
802
  return { nodeIds, edgeIds };
796
- }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
803
+ }, [issues, expandedIssueCategories, components, trails, resolveAlias]);
797
804
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
798
805
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
799
806
  // When an edge is selected, dim every other edge + its label to focus it.
@@ -804,61 +811,61 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
804
811
  selectedEdgeIdRef.current = selectedEdgeId;
805
812
  // Ref mirrors of the focused step, so `unfocusIssueTarget` (a useCallback
806
813
  // over early deps) can tell a step focus it still owns from one the user has
807
- // since moved elsewhere in the walkthrough panel.
814
+ // since moved elsewhere in the trail panel.
808
815
  const focusedStepRef = useRef(null);
809
816
  focusedStepRef.current =
810
- focusedWalkthroughId != null && focusedStepIndex != null
811
- ? { walkthroughId: focusedWalkthroughId, stepIndex: focusedStepIndex }
817
+ focusedTrailId != null && focusedStepIndex != null
818
+ ? { trailId: focusedTrailId, stepIndex: focusedStepIndex }
812
819
  : null;
813
- // Edge ids in walkthrough focus (an active flow's edge set, or a single
814
- // step's edge). Used to frame the camera. `null` = no walkthrough focus.
820
+ // Edge ids in trail focus (an active flow's edge set, or a single
821
+ // step's edge). Used to frame the camera. `null` = no trail focus.
815
822
  const focusEdgeIds = useMemo(() => {
816
823
  if (autoPlayFocus) {
817
- const tl = walkthroughs?.find((t) => t.id === autoPlayFocus.walkthroughId);
824
+ const tl = trails?.find((t) => t.id === autoPlayFocus.trailId);
818
825
  const step = tl?.steps[autoPlayFocus.stepIndex];
819
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
826
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
820
827
  }
821
- if (focusedWalkthroughId == null || !walkthroughs)
828
+ if (focusedTrailId == null || !trails)
822
829
  return null;
823
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
830
+ const tl = trails.find((t) => t.id === focusedTrailId);
824
831
  if (!tl)
825
832
  return null;
826
833
  if (focusedStepIndex != null) {
827
834
  const step = tl.steps[focusedStepIndex];
828
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
835
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
829
836
  }
830
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
831
- }, [walkthroughs, focusedWalkthroughId, focusedStepIndex, autoPlayFocus]);
837
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
838
+ }, [trails, focusedTrailId, focusedStepIndex, autoPlayFocus]);
832
839
  // 1-based step numbers per edge of the active flow (focused or
833
840
  // hover/autoplay-highlighted). An edge can appear in more than one step.
834
841
  const selectedFlowStepNos = useMemo(() => {
835
- const activeId = focusedWalkthroughId ?? hoveredWalkthroughStep?.walkthroughId;
836
- if (activeId == null || !walkthroughs)
842
+ const activeId = focusedTrailId ?? hoveredTrailStep?.trailId;
843
+ if (activeId == null || !trails)
837
844
  return null;
838
- const tl = walkthroughs.find((t) => t.id === activeId);
845
+ const tl = trails.find((t) => t.id === activeId);
839
846
  if (!tl)
840
847
  return null;
841
848
  const map = new Map();
842
849
  tl.steps.forEach((s, i) => {
843
- const list = map.get(walkthroughStepGraphEdgeId(s)) ?? [];
850
+ const list = map.get(trailStepGraphEdgeId(s)) ?? [];
844
851
  list.push(i + 1);
845
- map.set(walkthroughStepGraphEdgeId(s), list);
852
+ map.set(trailStepGraphEdgeId(s), list);
846
853
  });
847
854
  return map;
848
- }, [walkthroughs, focusedWalkthroughId, hoveredWalkthroughStep]);
849
- // Union of every expanded (opened) walkthrough's edges — the visible set.
855
+ }, [trails, focusedTrailId, hoveredTrailStep]);
856
+ // Union of every expanded (opened) trail's edges — the visible set.
850
857
  const openedEdgeIds = useMemo(() => {
851
- if (!walkthroughs || expandedWalkthroughs.size === 0)
858
+ if (!trails || expandedTrails.size === 0)
852
859
  return null;
853
860
  const ids = new Set();
854
- for (const tl of walkthroughs) {
855
- if (!expandedWalkthroughs.has(tl.id))
861
+ for (const tl of trails) {
862
+ if (!expandedTrails.has(tl.id))
856
863
  continue;
857
864
  for (const s of tl.steps)
858
- ids.add(walkthroughStepGraphEdgeId(s));
865
+ ids.add(trailStepGraphEdgeId(s));
859
866
  }
860
867
  return ids.size > 0 ? ids : null;
861
- }, [walkthroughs, expandedWalkthroughs]);
868
+ }, [trails, expandedTrails]);
862
869
  const endpointsOf = (edgeIds) => {
863
870
  if (!edgeIds)
864
871
  return null;
@@ -873,22 +880,22 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
873
880
  };
874
881
  // Edge/nodes involved in the hovered step or whole flow.
875
882
  const hoverEdgeIds = useMemo(() => {
876
- if (!hoveredWalkthroughStep || !walkthroughs)
883
+ if (!hoveredTrailStep || !trails)
877
884
  return null;
878
- const tl = walkthroughs.find((t) => t.id === hoveredWalkthroughStep.walkthroughId);
885
+ const tl = trails.find((t) => t.id === hoveredTrailStep.trailId);
879
886
  if (!tl)
880
887
  return null;
881
- if (hoveredWalkthroughStep.stepIndex == null) {
882
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
888
+ if (hoveredTrailStep.stepIndex == null) {
889
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
883
890
  }
884
- const step = tl.steps[hoveredWalkthroughStep.stepIndex];
885
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
886
- }, [walkthroughs, hoveredWalkthroughStep]);
891
+ const step = tl.steps[hoveredTrailStep.stepIndex];
892
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
893
+ }, [trails, hoveredTrailStep]);
887
894
  // While hovering with a *step* already selected, brighten the union of
888
895
  // selected + hovered participants. Whole-flow focus (or no focus) keeps
889
896
  // the old hover-replace preview so a step hover still dims the rest.
890
897
  const previewEdgeIds = useMemo(() => {
891
- // Autoplay drives focus without a hover, so its hop is the preview set.
898
+ // Autoplay drives focus without a hover, so its step is the preview set.
892
899
  if (autoPlayFocus)
893
900
  return focusEdgeIds;
894
901
  if (!hoverEdgeIds)
@@ -935,36 +942,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
935
942
  // `isSelected` rides in data because the node's stopPropagation() keeps
936
943
  // React Flow's own selection state from updating.
937
944
  const dispNodes = useMemo(() => {
938
- // While a step/flow is hovered, selected + hovered participants stay
939
- // bright; everything else among the currently-visible (opened-walkthrough)
940
- // nodes is dimmed. Nodes outside the opened-walkthrough set stay hidden.
941
- // Exception: with no expanded flow at all (a sidebar-less embed), there is
942
- // nothing to hide toward — everything shows, and focus/hover only dims.
943
- const noOpenedFlow = openedEdgeIds == null;
945
+ // While a step/flow is hovered, hovered participants stay bright; every
946
+ // other node is dimmed (and, when a flow is expanded, hidden). Exception:
947
+ // with no expanded flow at all (a sidebar-less embed) there is nothing to
948
+ // hide toward — everything shows, and focus/hover only dims.
944
949
  return xyflowNodesBase.map((n) => {
945
950
  // Boundary frames follow their members: hidden when no member is
946
951
  // visible, dimmed when members are dimmed. Never selectable.
947
952
  if (n.type === 'subsystem-group') {
948
953
  const region = n.data?.region;
949
954
  const memberAliases = region?.memberAliases ?? [];
950
- // Hover/autoplay dim-only: never hide a frame (see the node branch).
951
- const vis = flowElementVisibility({
955
+ // `brightNodeIds` is the focused step's endpoints. It has to be a
956
+ // spotlight member as well as a bright set: without an expanded flow
957
+ // there is no opened set, so dimming has to be driven from the
958
+ // participants or nothing outside the focused step would dim.
959
+ const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
960
+ const { hidden, dimmed } = flowNodeVisibility({
952
961
  inOpened: memberAliases.some((alias) => openedNodeIds?.has(alias) === true),
953
962
  inSelected: memberAliases.some((alias) => brightNodeIds?.has(alias) === true),
963
+ inSpotlight: memberAliases.some((alias) => dimSource?.has(alias) === true),
954
964
  anyOpened: openedNodeIds != null,
955
- anySelected: brightNodeIds != null || previewNodeIds != null,
965
+ anySelected: brightNodeIds != null,
966
+ anySpotlight: dimSource != null,
956
967
  });
957
- // `brightNodeIds` is the focused step's endpoints. It has to be a dim
958
- // source, not just a bright set: without an expanded flow,
959
- // `flowElementVisibility` reports non-participants as *hidden*, and the
960
- // noOpenedFlow escape below discards that — leaving them neither hidden
961
- // nor dimmed. Naming the participants as the dim source dims everything
962
- // outside the focused step however the flow got focused.
963
- const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
964
- const dimmed = dimSource
965
- ? vis.hidden || !memberAliases.some((alias) => dimSource.has(alias))
966
- : vis.dimmed;
967
- const hidden = noOpenedFlow ? false : vis.hidden;
968
968
  // Host override wins over the library's derived frame color.
969
969
  const color = region?.key != null ? boundaryColors?.[region.key] : undefined;
970
970
  // Boundary findings badge the FRAME, not a member — a region's shape is
@@ -984,22 +984,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
984
984
  }
985
985
  const comp = n.data?.component;
986
986
  const fileMatch = fileMatchForNode(comp?.file, openFile, focusNodeIds?.has(n.id) === true);
987
- // Hover/autoplay is a dim-only signal: never hide non-participants (there
988
- // may be no "opened" flow to reveal them, e.g. a sidebar-less autoplay
989
- // embed), only dim them. Bases selection on the focused step, not the
990
- // hover preview, so hovering doesn't strip the frame's `isSelected`.
987
+ // Bases selection on the focused step, not the hover preview, so
988
+ // hovering doesn't strip the frame's `isSelected`.
991
989
  const isSelected = selected?.alias !== undefined && comp?.alias === selected.alias;
992
- const vis = flowElementVisibility({
990
+ // `brightNodeIds` (the focused step's endpoints) is a spotlight member as
991
+ // well as a bright set — see the group branch above.
992
+ const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
993
+ const { hidden, dimmed } = flowNodeVisibility({
993
994
  inOpened: openedNodeIds?.has(n.id) === true,
994
995
  inSelected: brightNodeIds?.has(n.id) === true,
996
+ inSpotlight: dimSource?.has(n.id) === true,
995
997
  anyOpened: openedNodeIds != null,
996
- anySelected: brightNodeIds != null || previewNodeIds != null,
998
+ anySelected: brightNodeIds != null,
999
+ anySpotlight: dimSource != null,
997
1000
  });
998
- // `brightNodeIds` (the focused step's endpoints) is a dim source as well
999
- // as a bright set — see the group branch above.
1000
- const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
1001
- const dimmed = dimSource ? (vis.hidden || !dimSource.has(n.id)) : vis.dimmed;
1002
- const hidden = noOpenedFlow ? false : vis.hidden;
1003
1001
  // Component findings key by alias, boundary findings by region node id.
1004
1002
  // The two id spaces are disjoint (`module:` / `process:` are prefixed), so
1005
1003
  // one lookup covers both node kinds.
@@ -1037,14 +1035,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1037
1035
  };
1038
1036
  // Edges outside the selected view are hidden entirely (labels included).
1039
1037
  // graphify-native edges belong to the graphify view (they are static
1040
- // topology, not runtime hops) regardless of their verb.
1038
+ // topology, not runtime steps) regardless of their verb.
1041
1039
  const edgeInView = (e) => {
1042
1040
  const d = e.data;
1043
1041
  const mechanism = d?.mechanism ?? 'uses';
1044
1042
  if (resolvedEdgeView === 'graphify') {
1045
1043
  return d?.provenance === 'graphify';
1046
1044
  }
1047
- return isWalkthroughMechanism(mechanism);
1045
+ return isTrailMechanism(mechanism);
1048
1046
  };
1049
1047
  if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
1050
1048
  return baseEdges.map((e) => {
@@ -1099,7 +1097,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1099
1097
  const onNodeClick = useCallback((_e, node) => {
1100
1098
  const comp = node.data?.component;
1101
1099
  setSelectedEdgeId(null);
1102
- setFocusedWalkthroughId(null);
1100
+ setFocusedTrailId(null);
1103
1101
  setFocusedStepIndex(null);
1104
1102
  if (node.type === 'subsystem-component' && comp) {
1105
1103
  // Clicking the already-selected node unselects it (toggle off).
@@ -1121,15 +1119,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1121
1119
  const onPaneClick = useCallback(() => {
1122
1120
  setSelected(null);
1123
1121
  setSelectedEdgeId(null);
1124
- setFocusedWalkthroughId(null);
1122
+ setFocusedTrailId(null);
1125
1123
  setFocusedStepIndex(null);
1126
1124
  issueModuleFocusRef.current = null;
1127
1125
  }, []);
1128
1126
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
1129
1127
  // own owner-avatar header. Clicking a header collapses that repo's tree.
1130
1128
  const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
1131
- const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
1132
- // Aliases of proposed components — the flows panel tints walkthrough titles
1129
+ const hasTrails = useMemo(() => (trails?.length ?? 0) > 0, [trails]);
1130
+ // Aliases of proposed components — the flows panel tints trail titles
1133
1131
  // (and steps) that touch one.
1134
1132
  const proposedAliases = useMemo(() => new Set(components.filter((c) => c.proposed).map((c) => c.alias)), [components]);
1135
1133
  // Diagnostics view in the sidebar. Uncontrolled unless the host pins
@@ -1190,7 +1188,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1190
1188
  const [focusedBoundaryKey, setFocusedBoundaryKey] = useState(null);
1191
1189
  const onNodeDoubleClick = useCallback((_e, node) => {
1192
1190
  // Boundary frame → frame its members on the canvas; double-click again to
1193
- // zoom back out. User-initiated, so it ignores the walkthrough zoom gate.
1191
+ // zoom back out. User-initiated, so it ignores the trail zoom gate.
1194
1192
  if (node.type === 'subsystem-group') {
1195
1193
  const key = node.data?.region?.key;
1196
1194
  if (!key)
@@ -1225,7 +1223,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1225
1223
  const startLine = comp.declarationRef?.startLine;
1226
1224
  onOpenDeclarationFile(comp.file, startLine != null ? { startLine } : undefined);
1227
1225
  }, [onOpenDeclarationFile, fitView, focusedBoundaryKey]);
1228
- const onOpenFileFromWalkthrough = useCallback((file, opts) => {
1226
+ const onOpenFileFromTrail = useCallback((file, opts) => {
1229
1227
  setFileOverlay({ file, startLine: opts?.startLine });
1230
1228
  onFileSelect?.(file);
1231
1229
  }, [onFileSelect]);
@@ -1239,14 +1237,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1239
1237
  const drawerFillHeight = drawerTarget?.kind === 'file' && drawerTarget.startLine == null;
1240
1238
  const fileOverlayOpen = fileOverlay != null;
1241
1239
  const collapseCanvas = drawerFillHeight || fileOverlayOpen;
1242
- // Drop the full-file overlay when the underlying walkthrough drawer changes
1240
+ // Drop the full-file overlay when the underlying trail drawer changes
1243
1241
  // or closes — open-file keeps the same drawerTarget so the snippets stay mounted.
1244
- const walkthroughDrawerKey = drawerTarget?.kind === 'walkthrough'
1245
- ? `${drawerTarget.walkthroughId}:${drawerTarget.stepIndex}`
1242
+ const trailDrawerKey = drawerTarget?.kind === 'trail'
1243
+ ? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
1246
1244
  : null;
1247
1245
  useEffect(() => {
1248
1246
  setFileOverlay(null);
1249
- }, [walkthroughDrawerKey]);
1247
+ }, [trailDrawerKey]);
1250
1248
  useEffect(() => {
1251
1249
  if (!fileOverlayOpen)
1252
1250
  return;
@@ -1257,15 +1255,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1257
1255
  window.addEventListener('keydown', onKey);
1258
1256
  return () => window.removeEventListener('keydown', onKey);
1259
1257
  }, [fileOverlayOpen, closeFileOverlay]);
1260
- // Camera helper shared by the walkthrough interactions. Fits the union of
1258
+ // Camera helper shared by the trail interactions. Fits the union of
1261
1259
  // the focused edges' endpoint rects AND their routed waypoints — the edge
1262
1260
  // carries its full polyline (`elkPathPoints`, absolute flow coords), so the
1263
- // frame is the line the hop actually traces. That keeps a hop whose route
1261
+ // frame is the line the step actually traces. That keeps a step whose route
1264
1262
  // bulges out around intervening nodes from being clipped at the viewport
1265
1263
  // edge, and it is the edge's own geometry doing the guiding, not fudge
1266
1264
  // padding.
1267
1265
  const fitFocusBounds = useCallback((ids) => {
1268
- if (!zoomOnWalkthroughFocus)
1266
+ if (!zoomOnTrailFocus)
1269
1267
  return;
1270
1268
  const nodeIds = new Set();
1271
1269
  const points = [];
@@ -1315,14 +1313,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1315
1313
  };
1316
1314
  fitBounds(bounds, {
1317
1315
  padding: 0.15,
1318
- duration: walkthroughFocusDurationMs,
1316
+ duration: trailFocusDurationMs,
1319
1317
  });
1320
1318
  }, [
1321
1319
  baseEdges,
1322
1320
  built.absoluteRects,
1323
1321
  fitBounds,
1324
- zoomOnWalkthroughFocus,
1325
- walkthroughFocusDurationMs,
1322
+ zoomOnTrailFocus,
1323
+ trailFocusDurationMs,
1326
1324
  ]);
1327
1325
  // A node's laid-out rect in absolute flow coords. `absoluteRects` is the
1328
1326
  // authority; the fallback covers an unparented group node, whose `position` is
@@ -1368,12 +1366,12 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1368
1366
  y: minY,
1369
1367
  width: Math.max(1, maxX - minX),
1370
1368
  height: Math.max(1, maxY - minY),
1371
- }, { padding, duration: walkthroughFocusDurationMs });
1372
- }, [rectForNode, fitBounds, walkthroughFocusDurationMs]);
1373
- // Zoom back out to the full diagram after the last expanded walkthrough
1369
+ }, { padding, duration: trailFocusDurationMs });
1370
+ }, [rectForNode, fitBounds, trailFocusDurationMs]);
1371
+ // Zoom back out to the full diagram after the last expanded trail
1374
1372
  // closes (visibility restores non-flow nodes that were hidden).
1375
1373
  const fitOverview = useCallback(() => {
1376
- if (!zoomOnWalkthroughFocus)
1374
+ if (!zoomOnTrailFocus)
1377
1375
  return;
1378
1376
  fitView({
1379
1377
  padding: 0.1,
@@ -1382,109 +1380,109 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1382
1380
  maxZoom: 2,
1383
1381
  duration: 300,
1384
1382
  });
1385
- }, [fitView, zoomOnWalkthroughFocus]);
1383
+ }, [fitView, zoomOnTrailFocus]);
1386
1384
  // Focus an entire flow: hide everything but the flow's nodes and edges, and
1387
1385
  // frame the flow on the canvas. Selection state is cleared — the graph now
1388
1386
  // reads as the narrative. No drawer: the code view only opens on a step
1389
- // click. A stale walkthrough drawer (from a previously focused flow's step)
1387
+ // click. A stale trail drawer (from a previously focused flow's step)
1390
1388
  // closes; an explicitly opened file drawer stays.
1391
1389
  // In `dim` mode: clear step highlight and leave the full graph visible
1392
1390
  // (no zoom / hide) — matching "step away from a hovered step".
1393
- const focusWalkthroughEdges = useCallback((tl) => {
1391
+ const focusTrailEdges = useCallback((tl) => {
1394
1392
  setSelected(null);
1395
1393
  setSelectedEdgeId(null);
1396
- setHoveredWalkthroughStep(null);
1397
- if (walkthroughStepMode === 'dim') {
1394
+ setHoveredTrailStep(null);
1395
+ if (trailStepMode === 'dim') {
1398
1396
  setFocusedStepIndex(null);
1399
- setFocusedWalkthroughId(null);
1400
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1397
+ setFocusedTrailId(null);
1398
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1401
1399
  return;
1402
1400
  }
1403
1401
  setFocusedStepIndex(null);
1404
- setFocusedWalkthroughId(tl.id);
1405
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1406
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1407
- }, [fitFocusBounds, walkthroughStepMode]);
1408
- const clearWalkthroughFocus = useCallback(() => {
1409
- setFocusedWalkthroughId(null);
1402
+ setFocusedTrailId(tl.id);
1403
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1404
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1405
+ }, [fitFocusBounds, trailStepMode]);
1406
+ const clearTrailFocus = useCallback(() => {
1407
+ setFocusedTrailId(null);
1410
1408
  setFocusedStepIndex(null);
1411
- setHoveredWalkthroughStep(null);
1412
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1409
+ setHoveredTrailStep(null);
1410
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1413
1411
  }, []);
1414
- // Host deep-link: opening the graph from a walkthrough row selects that
1412
+ // Host deep-link: opening the graph from a trail row selects that
1415
1413
  // flow — expand its steps, focus its edges, and switch the sidebar to
1416
- // Walkthroughs. Guarded by a ref so a later in-tab selection isn't yanked
1414
+ // Trails. Guarded by a ref so a later in-tab selection isn't yanked
1417
1415
  // back; a remount (or a new id) re-applies it.
1418
- const appliedInitialWalkthroughRef = useRef(null);
1416
+ const appliedInitialTrailRef = useRef(null);
1419
1417
  useEffect(() => {
1420
- if (!initialWalkthroughId)
1418
+ if (!initialTrailId)
1421
1419
  return;
1422
- if (appliedInitialWalkthroughRef.current === initialWalkthroughId)
1420
+ if (appliedInitialTrailRef.current === initialTrailId)
1423
1421
  return;
1424
- if (!layoutReady || !walkthroughs?.length)
1422
+ if (!layoutReady || !trails?.length)
1425
1423
  return;
1426
- const tl = walkthroughs.find((t) => t.id === initialWalkthroughId);
1424
+ const tl = trails.find((t) => t.id === initialTrailId);
1427
1425
  if (!tl)
1428
1426
  return;
1429
- appliedInitialWalkthroughRef.current = initialWalkthroughId;
1430
- setSidebarView('walkthroughs');
1431
- setExpandedWalkthroughs((prev) => new Set(prev).add(tl.id));
1432
- focusWalkthroughEdges(tl);
1433
- }, [initialWalkthroughId, layoutReady, walkthroughs, focusWalkthroughEdges]);
1427
+ appliedInitialTrailRef.current = initialTrailId;
1428
+ setSidebarView('trails');
1429
+ setExpandedTrails((prev) => new Set(prev).add(tl.id));
1430
+ focusTrailEdges(tl);
1431
+ }, [initialTrailId, layoutReady, trails, focusTrailEdges]);
1434
1432
  // Switching sidebar panels also switches the edge vocabulary. Leaving the
1435
- // Walkthroughs panel drops its canvas state (focus, expanded flows, selected
1433
+ // Trails panel drops its canvas state (focus, expanded flows, selected
1436
1434
  // edge) so the Files view's topology edges aren't gated by a flow the user
1437
1435
  // can no longer see or un-dim.
1438
1436
  const changeSidebarView = useCallback((view) => {
1439
1437
  setSidebarView(view);
1440
- if (view === 'walkthroughs')
1438
+ if (view === 'trails')
1441
1439
  return;
1442
- clearWalkthroughFocus();
1443
- setExpandedWalkthroughs(new Set());
1440
+ clearTrailFocus();
1441
+ setExpandedTrails(new Set());
1444
1442
  setSelectedEdgeId(null);
1445
- }, [clearWalkthroughFocus]);
1446
- // Focus a single step's edge on the canvas and open/scroll the walkthrough
1443
+ }, [clearTrailFocus]);
1444
+ // Focus a single step's edge on the canvas and open/scroll the trail
1447
1445
  // drawer to that step's snippet. Clicking the already-focused step clears
1448
1446
  // step focus and returns to whole-flow (high-level) framing.
1449
1447
  // In `dim` mode: only dim non-participants (same as hovering a step) —
1450
1448
  // no camera move, no drawer, no hide.
1451
- const focusWalkthroughStep = useCallback((tl, stepIndex) => {
1449
+ const focusTrailStep = useCallback((tl, stepIndex) => {
1452
1450
  const step = tl.steps[stepIndex];
1453
1451
  if (!step)
1454
1452
  return;
1455
1453
  setSelected(null);
1456
1454
  setSelectedEdgeId(null);
1457
- if (walkthroughStepMode === 'dim') {
1455
+ if (trailStepMode === 'dim') {
1458
1456
  setFocusedStepIndex(null);
1459
- setFocusedWalkthroughId(null);
1460
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1461
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1457
+ setFocusedTrailId(null);
1458
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1459
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1462
1460
  return;
1463
1461
  }
1464
1462
  // Toggle: clicking the selected step unselects it (back to whole flow).
1465
- if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
1463
+ if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
1466
1464
  setFocusedStepIndex(null);
1467
1465
  // Pointer is still over the row — keep hover preview so dimming doesn't
1468
1466
  // flash off until mouseleave.
1469
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1470
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1467
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1468
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1471
1469
  if (pendingFocusFitRef.current != null) {
1472
1470
  window.clearTimeout(pendingFocusFitRef.current);
1473
1471
  pendingFocusFitRef.current = null;
1474
1472
  }
1475
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1473
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1476
1474
  return;
1477
1475
  }
1478
1476
  setFocusedStepIndex(stepIndex);
1479
- setFocusedWalkthroughId(tl.id);
1480
- setHoveredWalkthroughStep(null);
1477
+ setFocusedTrailId(tl.id);
1478
+ setHoveredTrailStep(null);
1481
1479
  // Open the drawer before fitting. If it was closed, wait for its height
1482
1480
  // transition so fitView uses the reduced canvas — not full height.
1483
1481
  const drawerWasOpen = drawerOpenRef.current;
1484
- if (renderWalkthroughViewer) {
1482
+ if (renderTrailViewer) {
1485
1483
  setDrawerTarget({
1486
- kind: 'walkthrough',
1487
- walkthroughId: tl.id,
1484
+ kind: 'trail',
1485
+ trailId: tl.id,
1488
1486
  stepIndex,
1489
1487
  });
1490
1488
  }
@@ -1495,7 +1493,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1495
1493
  startLine: step.line,
1496
1494
  });
1497
1495
  }
1498
- const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
1496
+ const edgeIds = new Set([trailStepGraphEdgeId(step)]);
1499
1497
  if (pendingFocusFitRef.current != null) {
1500
1498
  window.clearTimeout(pendingFocusFitRef.current);
1501
1499
  pendingFocusFitRef.current = null;
@@ -1512,9 +1510,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1512
1510
  }, [
1513
1511
  fitFocusBounds,
1514
1512
  focusedStepIndex,
1515
- focusedWalkthroughId,
1516
- renderWalkthroughViewer,
1517
- walkthroughStepMode,
1513
+ focusedTrailId,
1514
+ renderTrailViewer,
1515
+ trailStepMode,
1518
1516
  ]);
1519
1517
  useEffect(() => {
1520
1518
  return () => {
@@ -1532,11 +1530,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1532
1530
  useEffect(() => {
1533
1531
  const prev = prevPreviewEdgeIdsRef.current;
1534
1532
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
1535
- if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim')
1533
+ if (!zoomOnTrailFocus || trailStepMode === 'dim')
1536
1534
  return;
1537
1535
  // Camera follows hover only when a specific step is already focused (or
1538
1536
  // autoplay is driving an auto-focus).
1539
- if (!autoPlayFocus && (focusedWalkthroughId == null || focusedStepIndex == null)) {
1537
+ if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
1540
1538
  return;
1541
1539
  }
1542
1540
  if (previewEdgeIds) {
@@ -1549,27 +1547,27 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1549
1547
  }, [
1550
1548
  previewEdgeIds,
1551
1549
  focusEdgeIds,
1552
- focusedWalkthroughId,
1550
+ focusedTrailId,
1553
1551
  focusedStepIndex,
1554
1552
  autoPlayFocus,
1555
1553
  fitFocusBounds,
1556
- zoomOnWalkthroughFocus,
1557
- walkthroughStepMode,
1554
+ zoomOnTrailFocus,
1555
+ trailStepMode,
1558
1556
  ]);
1559
- // Graph-only embeds: cycle walkthrough steps. In `focus` mode each step is
1560
- // selected (camera frames it via fitFocusBounds) and, when a walkthrough
1557
+ // Graph-only embeds: cycle trail steps. In `focus` mode each step is
1558
+ // selected (camera frames it via fitFocusBounds) and, when a trail
1561
1559
  // viewer is supplied, its snippet drawer opens; in `dim` mode it only
1562
1560
  // dim-highlights the step (hover-style) with no camera move.
1563
1561
  useEffect(() => {
1564
- if (!autoPlayWalkthroughs || walkthroughs == null || walkthroughs.length === 0)
1562
+ if (!autoPlayTrails || trails == null || trails.length === 0)
1565
1563
  return;
1566
1564
  if (!layoutReady)
1567
1565
  return;
1568
- const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
1566
+ const playable = trails.filter((tl) => tl.steps.length > 0);
1569
1567
  if (playable.length === 0)
1570
1568
  return;
1571
1569
  // Only run the cycles that can actually be shown in the current edge view.
1572
- const inView = playable.filter((tl) => tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)));
1570
+ const inView = playable.filter((tl) => tl.steps.some((s) => isTrailMechanism(s.mechanism)));
1573
1571
  if (inView.length === 0)
1574
1572
  return;
1575
1573
  const cyc = inView;
@@ -1577,8 +1575,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1577
1575
  let tlIdx = 0;
1578
1576
  let stepIdx = 0;
1579
1577
  let timer = null;
1580
- const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
1581
- const focusMode = walkthroughStepMode !== 'dim';
1578
+ const interval = Math.max(400, trailAutoPlayIntervalMs);
1579
+ const focusMode = trailStepMode !== 'dim';
1582
1580
  let cleanup = () => { };
1583
1581
  const tick = () => {
1584
1582
  if (cancelled)
@@ -1587,15 +1585,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1587
1585
  setSelected(null);
1588
1586
  setSelectedEdgeId(null);
1589
1587
  if (focusMode) {
1590
- setHoveredWalkthroughStep(null);
1591
- setFocusedWalkthroughId(null);
1588
+ setHoveredTrailStep(null);
1589
+ setFocusedTrailId(null);
1592
1590
  setFocusedStepIndex(null);
1593
- setAutoPlayFocus({ walkthroughId: tl.id, stepIndex: stepIdx });
1591
+ setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
1594
1592
  if (!hideDrawer) {
1595
1593
  const step = tl.steps[stepIdx];
1596
1594
  if (step) {
1597
- if (renderWalkthroughViewer) {
1598
- setDrawerTarget({ kind: 'walkthrough', walkthroughId: tl.id, stepIndex: stepIdx });
1595
+ if (renderTrailViewer) {
1596
+ setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
1599
1597
  }
1600
1598
  else {
1601
1599
  setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
@@ -1605,9 +1603,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1605
1603
  }
1606
1604
  else {
1607
1605
  setAutoPlayFocus(null);
1608
- setFocusedWalkthroughId(null);
1606
+ setFocusedTrailId(null);
1609
1607
  setFocusedStepIndex(null);
1610
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
1608
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
1611
1609
  }
1612
1610
  stepIdx += 1;
1613
1611
  if (stepIdx >= tl.steps.length) {
@@ -1621,28 +1619,28 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1621
1619
  cancelled = true;
1622
1620
  if (timer != null)
1623
1621
  window.clearTimeout(timer);
1624
- setHoveredWalkthroughStep(null);
1625
- setFocusedWalkthroughId(null);
1622
+ setHoveredTrailStep(null);
1623
+ setFocusedTrailId(null);
1626
1624
  setFocusedStepIndex(null);
1627
1625
  setAutoPlayFocus(null);
1628
1626
  };
1629
1627
  return cleanup;
1630
1628
  }, [
1631
- autoPlayWalkthroughs,
1632
- walkthroughs,
1633
- walkthroughAutoPlayIntervalMs,
1629
+ autoPlayTrails,
1630
+ trails,
1631
+ trailAutoPlayIntervalMs,
1634
1632
  layoutReady,
1635
- walkthroughStepMode,
1636
- renderWalkthroughViewer,
1633
+ trailStepMode,
1634
+ renderTrailViewer,
1637
1635
  hideDrawer,
1638
1636
  ]);
1639
- // Arrow keys step through the focused walkthrough once a step is active
1637
+ // Arrow keys step through the focused trail once a step is active
1640
1638
  // (sidebar click or drawer open). Ignores typing targets and chords.
1641
1639
  useEffect(() => {
1642
- if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
1640
+ if (focusedTrailId == null || focusedStepIndex == null || !trails) {
1643
1641
  return;
1644
1642
  }
1645
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
1643
+ const tl = trails.find((t) => t.id === focusedTrailId);
1646
1644
  if (!tl || tl.steps.length === 0)
1647
1645
  return;
1648
1646
  const onKeyDown = (e) => {
@@ -1669,19 +1667,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1669
1667
  if (next === focusedStepIndex)
1670
1668
  return;
1671
1669
  e.preventDefault();
1672
- focusWalkthroughStep(tl, next);
1670
+ focusTrailStep(tl, next);
1673
1671
  };
1674
1672
  window.addEventListener('keydown', onKeyDown);
1675
1673
  return () => window.removeEventListener('keydown', onKeyDown);
1676
1674
  }, [
1677
- focusedWalkthroughId,
1675
+ focusedTrailId,
1678
1676
  focusedStepIndex,
1679
- walkthroughs,
1680
- focusWalkthroughStep,
1677
+ trails,
1678
+ focusTrailStep,
1681
1679
  ]);
1682
- const toggleWalkthroughCollapsed = useCallback((tlId) => {
1683
- const collapsingLast = expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
1684
- setExpandedWalkthroughs((prev) => {
1680
+ const toggleTrailCollapsed = useCallback((tlId) => {
1681
+ const collapsingLast = expandedTrails.has(tlId) && expandedTrails.size === 1;
1682
+ setExpandedTrails((prev) => {
1685
1683
  const next = new Set(prev);
1686
1684
  if (next.has(tlId))
1687
1685
  next.delete(tlId);
@@ -1689,7 +1687,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1689
1687
  next.add(tlId);
1690
1688
  return next;
1691
1689
  });
1692
- if (!collapsingLast || !zoomOnWalkthroughFocus)
1690
+ if (!collapsingLast || !zoomOnTrailFocus)
1693
1691
  return;
1694
1692
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
1695
1693
  if (pendingFocusFitRef.current != null) {
@@ -1704,7 +1702,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1704
1702
  pendingFocusFitRef.current = null;
1705
1703
  requestAnimationFrame(() => fitOverview());
1706
1704
  }, drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20);
1707
- }, [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus]);
1705
+ }, [expandedTrails, fitOverview, zoomOnTrailFocus]);
1708
1706
  // Detail-panel links: related-name clicks select the matching component —
1709
1707
  // resolved by alias, name, or symbol (call labels may carry a trailing `()`).
1710
1708
  const resolveRelatedComponent = useCallback((ref) => {
@@ -1717,7 +1715,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1717
1715
  return;
1718
1716
  setSelected(comp);
1719
1717
  setSelectedEdgeId(null);
1720
- setFocusedWalkthroughId(null);
1718
+ setFocusedTrailId(null);
1721
1719
  setFocusedStepIndex(null);
1722
1720
  onSelect?.(comp.alias);
1723
1721
  }, [components, onSelect]);
@@ -1732,9 +1730,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1732
1730
  if (comp) {
1733
1731
  setSelected(comp);
1734
1732
  setSelectedEdgeId(null);
1735
- setFocusedWalkthroughId(null);
1733
+ setFocusedTrailId(null);
1736
1734
  setFocusedStepIndex(null);
1737
- setHoveredWalkthroughStep(null);
1735
+ setHoveredTrailStep(null);
1738
1736
  issueModuleFocusRef.current = null;
1739
1737
  onSelect?.(comp.alias);
1740
1738
  requestAnimationFrame(() => {
@@ -1749,11 +1747,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1749
1747
  }
1750
1748
  else if (step) {
1751
1749
  // A step finding focuses the step itself, through the same entry point
1752
- // the walkthrough panel uses — so the drawer, the step numbering, the
1750
+ // the trail panel uses — so the drawer, the step numbering, the
1753
1751
  // dim-mode hover preview, and the camera all behave identically whether
1754
1752
  // the step was reached from the panel or from a diagnostics card.
1755
1753
  issueModuleFocusRef.current = null;
1756
- focusWalkthroughStep(step.walkthrough, step.stepIndex);
1754
+ focusTrailStep(step.trail, step.stepIndex);
1757
1755
  }
1758
1756
  else {
1759
1757
  // Nothing selectable — but a module target names a boundary frame, so
@@ -1762,9 +1760,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1762
1760
  if (moduleNodeId) {
1763
1761
  setSelected(null);
1764
1762
  setSelectedEdgeId(null);
1765
- setFocusedWalkthroughId(null);
1763
+ setFocusedTrailId(null);
1766
1764
  setFocusedStepIndex(null);
1767
- setHoveredWalkthroughStep(null);
1765
+ setHoveredTrailStep(null);
1768
1766
  issueModuleFocusRef.current = moduleNodeId;
1769
1767
  requestAnimationFrame(() => {
1770
1768
  fitNodeRects(new Set([moduleNodeId]), 0.4);
@@ -1776,7 +1774,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1776
1774
  issueComponent,
1777
1775
  issueStep,
1778
1776
  issueModuleNodeId,
1779
- focusWalkthroughStep,
1777
+ focusTrailStep,
1780
1778
  onSelect,
1781
1779
  onSelectIssue,
1782
1780
  fitView,
@@ -1804,13 +1802,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1804
1802
  // keeps its step focus in hover state instead (owned by the pointer,
1805
1803
  // not the card), and correctly leaves that alone here.
1806
1804
  const owned = focusedStepRef.current;
1807
- if (owned?.walkthroughId !== step.walkthrough.id ||
1805
+ if (owned?.trailId !== step.trail.id ||
1808
1806
  owned?.stepIndex !== step.stepIndex) {
1809
1807
  return;
1810
1808
  }
1811
- setFocusedWalkthroughId(null);
1809
+ setFocusedTrailId(null);
1812
1810
  setFocusedStepIndex(null);
1813
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1811
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1814
1812
  }
1815
1813
  else if (moduleNodeId) {
1816
1814
  // Only unwind framing this card still owns.
@@ -1826,14 +1824,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1826
1824
  fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
1827
1825
  });
1828
1826
  }, [issueComponent, issueStep, issueModuleNodeId, fitView]);
1829
- // Construct tokens inside a walkthrough snippet. A step's line is an edge
1827
+ // Construct tokens inside a trail snippet. A step's line is an edge
1830
1828
  // between its `from`/`to` components, so a token naming either of those
1831
1829
  // constructs should navigate to that construct's declaration. Index the
1832
- // matchable identifiers per step (`walkthroughId:index`).
1833
- const walkthroughSymbolIndex = useMemo(() => {
1830
+ // matchable identifiers per step (`trailId:index`).
1831
+ const trailSymbolIndex = useMemo(() => {
1834
1832
  const byAlias = new Map(components.map((c) => [c.alias, c]));
1835
1833
  const index = new Map();
1836
- for (const wt of walkthroughs ?? []) {
1834
+ for (const wt of trails ?? []) {
1837
1835
  wt.steps.forEach((step, i) => {
1838
1836
  const tokens = new Map();
1839
1837
  for (const alias of [step.from, step.to]) {
@@ -1847,17 +1845,17 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1847
1845
  });
1848
1846
  }
1849
1847
  return index;
1850
- }, [components, walkthroughs]);
1851
- const walkthroughSymbolIndexRef = useRef(walkthroughSymbolIndex);
1852
- walkthroughSymbolIndexRef.current = walkthroughSymbolIndex;
1853
- // Stable resolver: reads the live index + focused walkthrough from refs so
1854
- // the memoized walkthrough drawer isn't rebuilt on every graph render.
1855
- const resolveWalkthroughSymbol = useCallback((query) => {
1856
- const walkthroughId = drawerWalkthroughIdRef.current;
1857
- if (walkthroughId == null)
1848
+ }, [components, trails]);
1849
+ const trailSymbolIndexRef = useRef(trailSymbolIndex);
1850
+ trailSymbolIndexRef.current = trailSymbolIndex;
1851
+ // Stable resolver: reads the live index + focused trail from refs so
1852
+ // the memoized trail drawer isn't rebuilt on every graph render.
1853
+ const resolveTrailSymbol = useCallback((query) => {
1854
+ const trailId = drawerTrailIdRef.current;
1855
+ if (trailId == null)
1858
1856
  return null;
1859
- return (walkthroughSymbolIndexRef.current
1860
- .get(`${walkthroughId}:${query.stepIndex}`)
1857
+ return (trailSymbolIndexRef.current
1858
+ .get(`${trailId}:${query.stepIndex}`)
1861
1859
  ?.get(query.tokenText) ?? null);
1862
1860
  }, []);
1863
1861
  // A construct token click navigates to that construct's declaration: open
@@ -1920,10 +1918,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1920
1918
  const fileViewerRef = useRef(fileViewer);
1921
1919
  fileViewerRef.current = fileViewer;
1922
1920
  const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
1923
- const walkthroughViewerRef = useRef(renderWalkthroughViewer);
1924
- walkthroughViewerRef.current = renderWalkthroughViewer;
1925
- const renderWalkthroughDrawerContent = useCallback((ctx) => walkthroughViewerRef.current?.(ctx) ?? null, []);
1926
- return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1921
+ const trailViewerRef = useRef(renderTrailViewer);
1922
+ trailViewerRef.current = renderTrailViewer;
1923
+ const renderTrailDrawerContent = useCallback((ctx) => trailViewerRef.current?.(ctx) ?? null, []);
1924
+ return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (_jsxs("div", { style: {
1927
1925
  width: sidebarWidth,
1928
1926
  minWidth: sidebarWidth,
1929
1927
  borderRight: `1px solid ${theme.colors.border}`,
@@ -1969,7 +1967,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1969
1967
  color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
1970
1968
  cursor: 'pointer',
1971
1969
  transition: 'background 120ms ease, color 120ms ease',
1972
- }, children: _jsx(FileText, { size: 14 }) }))] })] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (_jsx("div", { style: {
1970
+ }, children: _jsx(FileText, { size: 14 }) }))] })] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasTrails || issuesActive) && (_jsx("div", { style: {
1973
1971
  ...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
1974
1972
  minHeight: 160,
1975
1973
  flexShrink: 0,
@@ -1995,13 +1993,13 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1995
1993
  fontFamily: theme.fonts.monospace,
1996
1994
  textTransform: 'capitalize',
1997
1995
  cursor: 'pointer',
1998
- }, children: tab === 'issues' ? 'Issues' : 'Agents' }, tab))) }), diagnosticsTab === 'agents' ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))] })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : (_jsxs(_Fragment, { children: [hasWalkthroughs && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1996
+ }, children: tab === 'issues' ? 'Issues' : 'Agents' }, tab))) }), diagnosticsTab === 'agents' ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))] })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : (_jsxs(_Fragment, { children: [hasTrails && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1999
1997
  display: 'flex',
2000
1998
  width: '100%',
2001
1999
  flexShrink: 0,
2002
2000
  borderBottom: `1px solid ${theme.colors.border}`,
2003
2001
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2004
- }, children: ['walkthroughs', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => changeSidebarView(view), style: {
2002
+ }, children: ['trails', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => changeSidebarView(view), style: {
2005
2003
  flex: 1,
2006
2004
  minWidth: 0,
2007
2005
  padding: '8px 8px',
@@ -2015,7 +2013,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2015
2013
  fontFamily: theme.fonts.monospace,
2016
2014
  textTransform: 'capitalize',
2017
2015
  cursor: 'pointer',
2018
- }, children: view === 'walkthroughs' ? 'Walkthroughs' : 'Files' }, view))) })), sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (_jsx(WalkthroughsPanel, { walkthroughs: walkthroughs, expandedWalkthroughs: expandedWalkthroughs, focusedWalkthroughId: focusedWalkthroughId, focusedStepIndex: focusedStepIndex, hoveredWalkthroughStep: hoveredWalkthroughStep, onToggleCollapsed: toggleWalkthroughCollapsed, onFocusFlow: focusWalkthroughEdges, onClearFocus: clearWalkthroughFocus, onFocusStep: focusWalkthroughStep, onHoverStep: (tl, i) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredWalkthroughStep(null), onReorder: onReorderWalkthroughs, proposedAliases: proposedAliases })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
2016
+ }, children: view === 'trails' ? 'Trails' : 'Files' }, view))) })), sidebarView === 'trails' && trails && trails.length > 0 ? (_jsx(TrailsPanel, { trails: trails, expandedTrails: expandedTrails, focusedTrailId: focusedTrailId, focusedStepIndex: focusedStepIndex, hoveredTrailStep: hoveredTrailStep, onToggleCollapsed: toggleTrailCollapsed, onFocusFlow: focusTrailEdges, onClearFocus: clearTrailFocus, onFocusStep: focusTrailStep, onHoverStep: (tl, i) => setHoveredTrailStep({ trailId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredTrailStep({ trailId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredTrailStep(null), onReorder: onReorderTrails, proposedAliases: proposedAliases })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
2019
2017
  const groupKey = group.repoKey ?? '__no-repo__';
2020
2018
  const collapsed = collapsedRepos.has(groupKey);
2021
2019
  return (_jsxs("div", { style: {
@@ -2025,7 +2023,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2025
2023
  flexDirection: 'column',
2026
2024
  borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
2027
2025
  }, children: [repoGroups.multiRepo && (_jsx(RepoGroupHeader, { group: group, collapsed: collapsed, onToggle: () => toggleRepoCollapsed(groupKey) })), !collapsed && (_jsx(SubsystemFileTree, { files: group.entries.map((e) => e.displayPath), selectedFile: selected?.file ?? openFile, hoveredFile: hoveredFile, onSelectFile: onTreeSelectFile, hoveredFolder: hoveredBoundaryPath, headerless: repoGroups.multiRepo }))] }, groupKey));
2028
- }) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
2026
+ }) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
2029
2027
  width: 3,
2030
2028
  flexShrink: 0,
2031
2029
  cursor: 'col-resize',
@@ -2118,7 +2116,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2118
2116
  flex: 1,
2119
2117
  minHeight: 0,
2120
2118
  background: theme.colors.background,
2121
- }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayWalkthroughTitle || overlayWalkthroughStep) && (_jsxs("div", { style: {
2119
+ }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
2122
2120
  position: 'absolute',
2123
2121
  top: 10,
2124
2122
  left: '50%',
@@ -2144,9 +2142,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2144
2142
  border: `1px solid ${theme.colors.border}`,
2145
2143
  borderRadius: 6,
2146
2144
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2147
- }, children: graphTitle })), overlayWalkthroughTitle && (_jsxs("div", { style: {
2145
+ }, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
2148
2146
  maxWidth: '100%',
2149
- minWidth: overlayWalkthroughStep ? 160 : undefined,
2147
+ minWidth: overlayTrailStep ? 160 : undefined,
2150
2148
  display: 'flex',
2151
2149
  flexDirection: 'column',
2152
2150
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -2155,9 +2153,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2155
2153
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2156
2154
  overflow: 'hidden',
2157
2155
  opacity: 0.95,
2158
- }, "aria-label": overlayWalkthroughStep
2159
- ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
2160
- : overlayWalkthroughTitle, children: [_jsx("div", { style: {
2156
+ }, "aria-label": overlayTrailStep
2157
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
2158
+ : overlayTrailTitle, children: [_jsx("div", { style: {
2161
2159
  padding: '5px 14px',
2162
2160
  fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
2163
2161
  fontWeight: 600,
@@ -2167,14 +2165,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2167
2165
  textOverflow: 'ellipsis',
2168
2166
  whiteSpace: 'nowrap',
2169
2167
  textAlign: 'center',
2170
- }, children: overlayWalkthroughTitle }), overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (_jsx("div", { style: {
2168
+ }, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
2171
2169
  display: 'flex',
2172
2170
  gap: 3,
2173
2171
  padding: '0 6px 5px',
2174
- }, "aria-hidden": "true", children: Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
2172
+ }, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
2175
2173
  const n = i + 1;
2176
- const active = n === overlayWalkthroughStep.index;
2177
- const done = n < overlayWalkthroughStep.index;
2174
+ const active = n === overlayTrailStep.index;
2175
+ const done = n < overlayTrailStep.index;
2178
2176
  return (_jsx("span", { style: {
2179
2177
  flex: 1,
2180
2178
  height: 2,
@@ -2187,7 +2185,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2187
2185
  opacity: active ? 1 : done ? 0.75 : 0.4,
2188
2186
  transition: 'opacity 120ms ease, background 120ms ease',
2189
2187
  } }, n));
2190
- }) }))] })), overlayWalkthroughStep?.annotation && (_jsx("div", { style: {
2188
+ }) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
2191
2189
  maxWidth: '100%',
2192
2190
  padding: '6px 14px',
2193
2191
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -2199,7 +2197,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2199
2197
  fontFamily: theme.fonts.body,
2200
2198
  color: theme.colors.textMuted ?? theme.colors.textSecondary,
2201
2199
  lineHeight: 1.35,
2202
- }, children: overlayWalkthroughStep.annotation }))] })), selected && (_jsx("div", { style: {
2200
+ }, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
2203
2201
  position: 'absolute',
2204
2202
  top: 10,
2205
2203
  right: 10,
@@ -2224,9 +2222,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2224
2222
  drawerTarget.startLine != null &&
2225
2223
  !!selected.file &&
2226
2224
  selected.file === drawerTarget.file &&
2227
- selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, children: drawerTarget?.kind === 'walkthrough' &&
2228
- focusedWalkthrough &&
2229
- renderWalkthroughViewer ? (_jsx(WalkthroughDrawerContent, { render: renderWalkthroughDrawerContent, walkthrough: focusedWalkthrough, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromWalkthrough, proposedAliases: proposedAliases, resolveSymbol: resolveWalkthroughSymbol, onSymbolClick: openConstructDeclaration })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), fileOverlay && (_jsxs("div", { style: {
2225
+ selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, children: drawerTarget?.kind === 'trail' &&
2226
+ focusedTrail &&
2227
+ renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, proposedAliases: proposedAliases, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), fileOverlay && (_jsxs("div", { style: {
2230
2228
  position: 'absolute',
2231
2229
  inset: 0,
2232
2230
  zIndex: 8,
@@ -2313,7 +2311,7 @@ export function SubsystemComponentGraph(props) {
2313
2311
  }, []);
2314
2312
  const constructsOnly = isConstructsOnlyModel({
2315
2313
  components: props.components,
2316
- walkthroughs: props.walkthroughs,
2314
+ trails: props.trails,
2317
2315
  graphifyRelations: props.graphifyRelations,
2318
2316
  });
2319
2317
  return (_jsx("div", { ref: wrapRef, style: {