@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
@@ -7,7 +7,7 @@
7
7
  * inside one labeled boundary frame; nodes without one sit outside every
8
8
  * boundary. Clicking a component invokes `onSelect`.
9
9
  *
10
- * When the model has components but no topology or walkthrough edges, the
10
+ * When the model has components but no topology or trail edges, the
11
11
  * canvas is a constructs catalog (list + signature) instead of a graph.
12
12
  *
13
13
  * This is a focused fork of the package's `GraphRenderer` pipeline (same ELK
@@ -39,11 +39,11 @@ import {
39
39
  deriveGraphEdges,
40
40
  isConstructsOnlyModel,
41
41
  moduleGroupNodeId,
42
- isWalkthroughMechanism,
42
+ isTrailMechanism,
43
43
  edgeColor,
44
44
  MECHANISM_DESCRIPTIONS,
45
45
  subsystemGraphLayoutKey,
46
- walkthroughStepGraphEdgeId,
46
+ trailStepGraphEdgeId,
47
47
  type SubsystemComponentEdge,
48
48
  type SubsystemComponent,
49
49
  type SubsystemEdgeProvenance,
@@ -51,7 +51,7 @@ import {
51
51
  type SubsystemGraphifyRelation,
52
52
  type SubsystemNodeIssue,
53
53
  type SubsystemRegionIssue,
54
- type SubsystemWalkthrough,
54
+ type SubsystemTrail,
55
55
  } from './model';
56
56
  import { ConstructsCatalog } from './ConstructsCatalog';
57
57
  import type { SubsystemOpenFileOptions } from './declarationRef';
@@ -63,8 +63,8 @@ import {
63
63
  MaintainLivePanel,
64
64
  type MaintainLivePanelProps,
65
65
  } from '../components/maintain-events/MaintainLivePanel';
66
- import type { WalkthroughSymbolQuery } from '../pierre/PierreWalkthroughCodeView';
67
- import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
66
+ import type { TrailSymbolQuery } from '../pierre/PierreTrailCodeView';
67
+ import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility, flowNodeVisibility } from './nodes';
68
68
  import { SubsystemDiagnosticToggle, type SubsystemDiagnostic } from './DiagnosticToggle';
69
69
  import {
70
70
  SubsystemIssueList,
@@ -84,7 +84,7 @@ import type { ComponentVerificationState } from './ComponentDeclaration';
84
84
  import type { DeclarationSymbolRef, SymbolInspection } from './symbolRefs';
85
85
  import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
86
86
  import { buildRepoGroups, repoAvatarUrl, type RepoGroup } from './paths';
87
- import { WalkthroughsPanel, WALKTHROUGH_PLAY_PAUSE_MS } from './WalkthroughsPanel';
87
+ import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
88
88
  import {
89
89
  EDGE_LABEL_WIDTH,
90
90
  EDGE_LABEL_HEIGHT,
@@ -93,12 +93,12 @@ import {
93
93
  EDGE_LABEL_CLOUD_EXTRA_TOP,
94
94
  } from '../utils/edgeLabel';
95
95
 
96
- /** Context passed to `renderWalkthroughViewer` when a flow/step is focused. */
97
- export interface WalkthroughViewerContext {
98
- walkthrough: SubsystemWalkthrough;
96
+ /** Context passed to `renderTrailViewer` when a flow/step is focused. */
97
+ export interface TrailViewerContext {
98
+ trail: SubsystemTrail;
99
99
  /** Focused step index; `null` means the whole flow (no specific step). */
100
100
  stepIndex: number | null;
101
- /** Open a step's full source file over the walkthrough drawer (keeps snippets mounted). */
101
+ /** Open a step's full source file over the trail drawer (keeps snippets mounted). */
102
102
  onOpenFile: (path: string, opts?: SubsystemOpenFileOptions) => void;
103
103
  /**
104
104
  * Aliases of components marked `proposed`. A step whose file can't be read
@@ -111,9 +111,9 @@ export interface WalkthroughViewerContext {
111
111
  * token names no touched construct. Forward to the code view so constructs
112
112
  * read as clickable.
113
113
  */
114
- resolveSymbol?: (query: WalkthroughSymbolQuery) => string | null;
114
+ resolveSymbol?: (query: TrailSymbolQuery) => string | null;
115
115
  /** A clicked construct token — open that construct's declaration line. */
116
- onSymbolClick?: (symbol: string, query: WalkthroughSymbolQuery) => void;
116
+ onSymbolClick?: (symbol: string, query: TrailSymbolQuery) => void;
117
117
  }
118
118
 
119
119
  /** Identifiers a token could match to name this component as a construct. */
@@ -131,21 +131,21 @@ function constructIdentifiers(comp: SubsystemComponent): string[] {
131
131
 
132
132
  type DrawerTarget =
133
133
  | { kind: 'file'; file: string; startLine?: number }
134
- | { kind: 'walkthrough'; walkthroughId: string; stepIndex: number | null };
134
+ | { kind: 'trail'; trailId: string; stepIndex: number | null };
135
135
 
136
136
  /**
137
137
  * Per-model UI state persisted to `localStorage`, keyed by `persistKey`.
138
- * Stores the walkthrough working set (which flows are expanded and which
138
+ * Stores the trail working set (which flows are expanded and which
139
139
  * flow/step is selected) and which sidebar/diagnostics tab is showing, so
140
140
  * tabbing away from a model and back lands you where you left off. Transient
141
141
  * things (hover, drag, camera) are not saved.
142
142
  */
143
143
  interface PersistedViewState {
144
- expandedWalkthroughs?: string[];
145
- focusedWalkthroughId?: string | null;
144
+ expandedTrails?: string[];
145
+ focusedTrailId?: string | null;
146
146
  focusedStepIndex?: number | null;
147
147
  sidebarWidth?: number;
148
- sidebarView?: 'files' | 'walkthroughs';
148
+ sidebarView?: 'files' | 'trails';
149
149
  diagnosticsTab?: 'issues' | 'agents';
150
150
  }
151
151
 
@@ -181,15 +181,15 @@ function writeViewState(
181
181
  export interface SubsystemComponentGraphProps {
182
182
  components: SubsystemComponent[];
183
183
  /**
184
- * Ordered runtime walkthroughs — one walkthrough per flow. When present the
185
- * sidebar's bottom half offers a Files/Walkthroughs toggle:
186
- * the flows panel lists each walkthrough's steps (`symbol` or `file:line`);
184
+ * Ordered runtime trails — one trail per flow. When present the
185
+ * sidebar's bottom half offers a Files/Trails toggle:
186
+ * the flows panel lists each trail's steps (`symbol` or `file:line`);
187
187
  * clicking a flow row toggles its steps; clicking a step focuses that
188
- * step's edge and (when `renderWalkthroughViewer` is set) opens the bottom
188
+ * step's edge and (when `renderTrailViewer` is set) opens the bottom
189
189
  * drawer on that flow's snippets. Opened flows stay on the canvas
190
190
  * (unselected ones dimmed); everything else is hidden.
191
191
  */
192
- walkthroughs?: SubsystemWalkthrough[];
192
+ trails?: SubsystemTrail[];
193
193
  /**
194
194
  * graphify-native relations (raw static-graph edges: `imports`, `contains`,
195
195
  * `re_exports`, …). Display-only, drawn with the separate
@@ -204,20 +204,20 @@ export interface SubsystemComponentGraphProps {
204
204
  */
205
205
  orderByLine?: boolean;
206
206
  /**
207
- * Deep-link target: when set, the matching walkthrough is selected on mount
207
+ * Deep-link target: when set, the matching trail is selected on mount
208
208
  * — its steps expanded and its flow focused on the canvas. Hosts use this
209
- * when opening the graph from a walkthrough row in a list. Re-applies on a
209
+ * when opening the graph from a trail row in a list. Re-applies on a
210
210
  * new id (or a remount); in-tab selection afterwards stays owned by the
211
211
  * graph.
212
212
  */
213
- initialWalkthroughId?: string | null;
213
+ initialTrailId?: string | null;
214
214
  /**
215
- * Called with the next walkthrough order after a drag in the sidebar's
215
+ * Called with the next trail order after a drag in the sidebar's
216
216
  * flows panel. When set, each row grows a drag grip and a drop emits the
217
217
  * reordered array — the host owns persisting it (the graph stays controlled
218
218
  * and never reorders its own prop).
219
219
  */
220
- onReorderWalkthroughs?: (next: SubsystemWalkthrough[]) => void;
220
+ onReorderTrails?: (next: SubsystemTrail[]) => void;
221
221
  onSelect?: (componentAlias: string) => void;
222
222
  /** Called when an edge is clicked (relationship / mechanism + refs seam). */
223
223
  onEdgeSelect?: (edge: SubsystemComponentEdge) => void;
@@ -242,50 +242,50 @@ export interface SubsystemComponentGraphProps {
242
242
  */
243
243
  moduleNesting?: 'exact' | 'path';
244
244
  /**
245
- * Which edge source the canvas draws. The graphify and walkthrough sources
245
+ * Which edge source the canvas draws. The graphify and trail sources
246
246
  * are disjoint, so a graph carrying both shows one or the other — never
247
247
  * both. Edges outside the view are hidden (labels go too).
248
248
  * - `graphify`: graphify-native static edges (`imports`, `contains`, …)
249
- * - `walkthroughs`: walkthrough hop edges (`calls`, `feeds`, …), including
249
+ * - `trails`: trail step edges (`calls`, `feeds`, …), including
250
250
  * step numbers when a flow is focused/hovered
251
- * Leave unset to let the sidebar's Files / Walkthroughs tab drive it: Files
252
- * draws graphify edges, Walkthroughs draws runtime hops. Without visible
253
- * tabs, defaults to `walkthroughs`.
251
+ * Leave unset to let the sidebar's Files / Trails tab drive it: Files
252
+ * draws graphify edges, Trails draws runtime steps. Without visible
253
+ * tabs, defaults to `trails`.
254
254
  */
255
255
  edgeView?: SubsystemEdgeView;
256
256
  /** Subsystem title displayed in the sidebar. */
257
257
  title?: string;
258
258
  /**
259
259
  * Suppresses the sidebar entirely (title, description, file tree,
260
- * walkthroughs) for graph-only embeds. Pair with `graphTitle` to keep the
260
+ * trails) for graph-only embeds. Pair with `graphTitle` to keep the
261
261
  * subsystem name visible as an overlay on the canvas.
262
262
  */
263
263
  hideSidebar?: boolean;
264
264
  /**
265
- * How walkthrough step highlighting behaves on the canvas.
265
+ * How trail step highlighting behaves on the canvas.
266
266
  * - `focus` (default): zoom to the step, hide non-participants, open drawer
267
267
  * - `dim`: keep the full graph, dim non-participants (same as hovering a step)
268
268
  */
269
- walkthroughStepMode?: 'focus' | 'dim';
269
+ trailStepMode?: 'focus' | 'dim';
270
270
  /**
271
- * When true, cycles walkthrough steps automatically using `dim` highlighting
272
- * (no zoom, no drawer). Loops across all walkthroughs that have steps.
271
+ * When true, cycles trail steps automatically using `dim` highlighting
272
+ * (no zoom, no drawer). Loops across all trails that have steps.
273
273
  * Useful for graph-only embeds (`hideSidebar`).
274
274
  */
275
- autoPlayWalkthroughs?: boolean;
275
+ autoPlayTrails?: boolean;
276
276
  /** Pause between autoplay steps in ms. @default 2500 */
277
- walkthroughAutoPlayIntervalMs?: number;
277
+ trailAutoPlayIntervalMs?: number;
278
278
  /**
279
- * When false, focusing a walkthrough/step does not call `fitView`.
279
+ * When false, focusing a trail/step does not call `fitView`.
280
280
  * @default true
281
281
  */
282
- zoomOnWalkthroughFocus?: boolean;
282
+ zoomOnTrailFocus?: boolean;
283
283
  /**
284
- * Duration (ms) of the camera pan/zoom when a walkthrough step or flow is
284
+ * Duration (ms) of the camera pan/zoom when a trail step or flow is
285
285
  * focused. Higher = a slower, more legible flight between steps.
286
286
  * @default 300
287
287
  */
288
- walkthroughFocusDurationMs?: number;
288
+ trailFocusDurationMs?: number;
289
289
  /**
290
290
  * Subsystem title rendered as a non-interactive overlay chip on the graph
291
291
  * canvas (top-center). Does not trigger the sidebar — for graph-only
@@ -293,11 +293,11 @@ export interface SubsystemComponentGraphProps {
293
293
  */
294
294
  graphTitle?: string;
295
295
  /**
296
- * When true, shows the active walkthrough's title as a non-interactive
296
+ * When true, shows the active trail's title as a non-interactive
297
297
  * overlay chip on the canvas (under `graphTitle` when both are set).
298
- * Uses the focused or hover-highlighted walkthrough.
298
+ * Uses the focused or hover-highlighted trail.
299
299
  */
300
- showWalkthroughTitle?: boolean;
300
+ showTrailTitle?: boolean;
301
301
  /** Markdown description rendered in the sidebar. */
302
302
  description?: string;
303
303
  /**
@@ -327,13 +327,13 @@ export interface SubsystemComponentGraphProps {
327
327
  * Diagnostics status chip in the sidebar title row (beside the description
328
328
  * toggle). Shows the last verification pass's state as an icon color + count,
329
329
  * and toggles the sidebar between the diagnostics list and the normal
330
- * files/walkthroughs view. Omit to hide the chip.
330
+ * files/trails view. Omit to hide the chip.
331
331
  */
332
332
  diagnostic?: SubsystemDiagnostic;
333
333
  /**
334
334
  * Verification issues for this graph (audit findings). When diagnostics are
335
335
  * active the sidebar's bottom panel becomes the issue list instead of the
336
- * file tree / walkthroughs.
336
+ * file tree / trails.
337
337
  */
338
338
  issues?: SubsystemIssue[];
339
339
  /**
@@ -349,7 +349,7 @@ export interface SubsystemComponentGraphProps {
349
349
  focusIssueCategory?: SubsystemIssueCategory;
350
350
  /**
351
351
  * Click an issue. The graph first focuses the target on the canvas itself —
352
- * a component target is selected and framed (any edge / walkthrough focus
352
+ * a component target is selected and framed (any edge / trail focus
353
353
  * that would hide it is cleared), and a
354
354
  * module target frames that boundary frame — then this fires so the host can
355
355
  * open its own detail. Collapsing the card again reverses that (deselect +
@@ -368,13 +368,13 @@ export interface SubsystemComponentGraphProps {
368
368
  */
369
369
  renderFileViewer?: (file: string, opts?: SubsystemOpenFileOptions) => ReactNode;
370
370
  /**
371
- * Host-injected multi-snippet viewer for a focused walkthrough. When set,
371
+ * Host-injected multi-snippet viewer for a focused trail. When set,
372
372
  * clicking a flow step opens the bottom drawer with this content and
373
373
  * updates it as the focused step changes.
374
374
  */
375
- renderWalkthroughViewer?: (ctx: WalkthroughViewerContext) => ReactNode;
375
+ renderTrailViewer?: (ctx: TrailViewerContext) => ReactNode;
376
376
  /**
377
- * Suppress the bottom file/walkthrough drawer entirely. Focusing a step then
377
+ * Suppress the bottom file/trail drawer entirely. Focusing a step then
378
378
  * only frames it on the canvas (and dims the rest) without dropping a snippet
379
379
  * panel below — for embeds that want the camera to tell the story. Defaults
380
380
  * to false.
@@ -406,7 +406,7 @@ export interface SubsystemComponentGraphProps {
406
406
  ref: DeclarationSymbolRef;
407
407
  }) => Promise<SymbolInspection | null> | SymbolInspection | null;
408
408
  /**
409
- * When set, the graph's walkthrough working set — expanded flows and the
409
+ * When set, the graph's trail working set — expanded flows and the
410
410
  * selected flow/step — is persisted to `localStorage` under this key and
411
411
  * restored on mount. Hosts key it by the model id so each model remembers
412
412
  * where the user left off. Omit to keep the state purely in-memory.
@@ -489,27 +489,27 @@ function FileOverlayCloseButton({ onClose }: { onClose: () => void }) {
489
489
  );
490
490
  }
491
491
 
492
- const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({
492
+ const TrailDrawerContent = memo(function TrailDrawerContent({
493
493
  render,
494
- walkthrough,
494
+ trail,
495
495
  stepIndex,
496
496
  onOpenFile,
497
497
  proposedAliases,
498
498
  resolveSymbol,
499
499
  onSymbolClick,
500
500
  }: {
501
- render: (ctx: WalkthroughViewerContext) => ReactNode;
502
- walkthrough: SubsystemWalkthrough;
501
+ render: (ctx: TrailViewerContext) => ReactNode;
502
+ trail: SubsystemTrail;
503
503
  stepIndex: number | null;
504
504
  onOpenFile: (path: string, opts?: SubsystemOpenFileOptions) => void;
505
505
  proposedAliases: ReadonlySet<string>;
506
- resolveSymbol?: (query: WalkthroughSymbolQuery) => string | null;
507
- onSymbolClick?: (symbol: string, query: WalkthroughSymbolQuery) => void;
506
+ resolveSymbol?: (query: TrailSymbolQuery) => string | null;
507
+ onSymbolClick?: (symbol: string, query: TrailSymbolQuery) => void;
508
508
  }) {
509
509
  return (
510
510
  <>
511
511
  {render({
512
- walkthrough,
512
+ trail,
513
513
  stepIndex,
514
514
  onOpenFile,
515
515
  proposedAliases,
@@ -524,7 +524,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
524
524
  measured: { w: number; h: number } | null;
525
525
  }
526
526
 
527
- 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 }: InnerProps) {
527
+ 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 }: InnerProps) {
528
528
  const { theme } = useTheme();
529
529
  const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
530
530
  const viewport = useViewport();
@@ -532,8 +532,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
532
532
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
533
533
  const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
534
534
  const graphEdges = useMemo(
535
- () => deriveGraphEdges({ walkthroughs }),
536
- [walkthroughs],
535
+ () => deriveGraphEdges({ trails }),
536
+ [trails],
537
537
  );
538
538
  const [built, setBuilt] = useState<{
539
539
  nodes: Node[];
@@ -546,9 +546,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
546
546
  });
547
547
  const [layoutReady, setLayoutReady] = useState(false);
548
548
  const [selected, setSelected] = useState<SubsystemComponent | null>(null);
549
- /** Bottom drawer: single file or walkthrough multi-snippet mode. */
549
+ /** Bottom drawer: single file or trail multi-snippet mode. */
550
550
  const [drawerTarget, setDrawerTarget] = useState<DrawerTarget | null>(null);
551
- /** Full-file layer over an open walkthrough drawer — walkthrough stays mounted. */
551
+ /** Full-file layer over an open trail drawer — trail stays mounted. */
552
552
  const [fileOverlay, setFileOverlay] = useState<{
553
553
  file: string;
554
554
  startLine?: number;
@@ -628,52 +628,58 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
628
628
  if (raf) cancelAnimationFrame(raf);
629
629
  };
630
630
  }, [pathFrameRects, screenToFlowPosition]);
631
- // Walkthrough focus — selected flow (or step) is full strength; other
631
+ // Trail focus — selected flow (or step) is full strength; other
632
632
  // opened-flow members stay visible but dimmed; everything else is hidden.
633
633
  // Restored from the persisted view state when a `persistKey` is set.
634
- const [focusedWalkthroughId, setFocusedWalkthroughId] = useState<string | null>(
635
- persisted.focusedWalkthroughId ?? null,
634
+ const [focusedTrailId, setFocusedTrailId] = useState<string | null>(
635
+ persisted.focusedTrailId ?? null,
636
636
  );
637
637
  // `null` = whole flow focused; a number = that single step's edge focused.
638
638
  const [focusedStepIndex, setFocusedStepIndex] = useState<number | null>(
639
639
  persisted.focusedStepIndex ?? null,
640
640
  );
641
- // Hovered walkthrough in the flows panel: dims every canvas node/edge not
641
+ // Hovered trail in the flows panel: dims every canvas node/edge not
642
642
  // involved in the hover preview (or selected ∪ hovered when a step is
643
643
  // focused). `stepIndex: null` = whole flow (collapsed title hover);
644
644
  // a number = that step. Transient — no drawer. Camera only reframes when
645
645
  // a step is already selected.
646
- const [hoveredWalkthroughStep, setHoveredWalkthroughStep] = useState<{
647
- walkthroughId: string;
646
+ const [hoveredTrailStep, setHoveredTrailStep] = useState<{
647
+ trailId: string;
648
648
  stepIndex: number | null;
649
649
  } | null>(null);
650
- // Sidebar bottom half: which panel is shown when walkthroughs exist.
651
- const [sidebarView, setSidebarView] = useState<'files' | 'walkthroughs'>(() =>
652
- persisted.sidebarView ?? (walkthroughs?.length ? 'walkthroughs' : 'files'),
653
- );
650
+ // Sidebar bottom half: which panel is shown when trails exist.
651
+ // The persisted value is membership-checked rather than trusted: a stale
652
+ // localStorage entry from before the walkthrough->trail rename would match
653
+ // neither panel and render a blank sidebar with no selected tab.
654
+ const [sidebarView, setSidebarView] = useState<'files' | 'trails'>(() => {
655
+ const stored = persisted.sidebarView;
656
+ const restored: 'files' | 'trails' =
657
+ stored === 'files' || stored === 'trails' ? stored : 'trails';
658
+ return trails?.length ? restored : 'files';
659
+ });
654
660
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
655
661
  const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>(
656
662
  () => persisted.diagnosticsTab ?? 'issues',
657
663
  );
658
664
  // One edge source at a time. When the caller doesn't pick, the sidebar's
659
- // Files / Walkthroughs tab picks: Files draws graphify static edges,
660
- // Walkthroughs draws runtime hop edges. Without visible tabs (no walkthroughs,
661
- // or a sidebar-less embed) fall back to walkthrough edges.
662
- const sidebarTabsVisible = !hideSidebar && (walkthroughs?.length ?? 0) > 0;
665
+ // Files / Trails tab picks: Files draws graphify static edges,
666
+ // Trails draws runtime step edges. Without visible tabs (no trails,
667
+ // or a sidebar-less embed) fall back to trail edges.
668
+ const sidebarTabsVisible = !hideSidebar && (trails?.length ?? 0) > 0;
663
669
  const resolvedEdgeView: SubsystemEdgeView =
664
670
  edgeView ??
665
671
  (sidebarTabsVisible
666
- ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
667
- : (walkthroughs?.length ?? 0) > 0
668
- ? 'walkthroughs'
672
+ ? (sidebarView === 'trails' ? 'trails' : 'graphify')
673
+ : (trails?.length ?? 0) > 0
674
+ ? 'trails'
669
675
  : (graphifyRelations?.length ?? 0) > 0
670
676
  ? 'graphify'
671
- : 'walkthroughs');
672
- // Walkthrough flows the user has expanded (via the title row). Closed by
677
+ : 'trails');
678
+ // Trail flows the user has expanded (via the title row). Closed by
673
679
  // default so a graph with several flows doesn't dump every step list at once.
674
680
  // Restored from the persisted view state when a `persistKey` is set.
675
- const [expandedWalkthroughs, setExpandedWalkthroughs] = useState<Set<string>>(
676
- () => new Set(persisted.expandedWalkthroughs ?? []),
681
+ const [expandedTrails, setExpandedTrails] = useState<Set<string>>(
682
+ () => new Set(persisted.expandedTrails ?? []),
677
683
  );
678
684
  // Sidebar description visibility. Hidden by default so the files/flows
679
685
  // panel gets the vertical room; the title-row toggle reveals it.
@@ -732,18 +738,18 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
732
738
  document.removeEventListener('mouseup', onUp);
733
739
  };
734
740
  }, [sidebarDrag, sidebarMaxWidth, fitView, persistKey]);
735
- // Persist the walkthrough working set per model. Each write merges into the
741
+ // Persist the trail working set per model. Each write merges into the
736
742
  // stored blob, so the three fields never clobber one another.
737
743
  useEffect(() => {
738
744
  if (!persistKey) return;
739
745
  writeViewState(persistKey, {
740
- expandedWalkthroughs: Array.from(expandedWalkthroughs),
746
+ expandedTrails: Array.from(expandedTrails),
741
747
  });
742
- }, [persistKey, expandedWalkthroughs]);
748
+ }, [persistKey, expandedTrails]);
743
749
  useEffect(() => {
744
750
  if (!persistKey) return;
745
- writeViewState(persistKey, { focusedWalkthroughId });
746
- }, [persistKey, focusedWalkthroughId]);
751
+ writeViewState(persistKey, { focusedTrailId });
752
+ }, [persistKey, focusedTrailId]);
747
753
  useEffect(() => {
748
754
  if (!persistKey) return;
749
755
  writeViewState(persistKey, { focusedStepIndex });
@@ -776,59 +782,59 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
776
782
  ? { file: drawerTarget.file, startLine: drawerTarget.startLine }
777
783
  : null;
778
784
 
779
- const focusedWalkthrough = useMemo(() => {
780
- if (drawerTarget?.kind !== 'walkthrough' || !walkthroughs) return null;
781
- return walkthroughs.find((t) => t.id === drawerTarget.walkthroughId) ?? null;
782
- }, [drawerTarget, walkthroughs]);
785
+ const focusedTrail = useMemo(() => {
786
+ if (drawerTarget?.kind !== 'trail' || !trails) return null;
787
+ return trails.find((t) => t.id === drawerTarget.trailId) ?? null;
788
+ }, [drawerTarget, trails]);
783
789
  // Autoplay focus: a sidebar-less embed has no expanded flow to "open", so
784
790
  // branding every step as a selection would hide all other nodes. Instead the
785
- // autoplay sets this (flow id + step) to frame the hop and dim the rest —
791
+ // autoplay sets this (flow id + step) to frame the step and dim the rest —
786
792
  // same treatment as hover — without hiding anything. Declared before the
787
793
  // overlay memos, which read it to title the canvas chip + step bar.
788
794
  const [autoPlayFocus, setAutoPlayFocus] = useState<
789
- { walkthroughId: string; stepIndex: number } | null
795
+ { trailId: string; stepIndex: number } | null
790
796
  >(null);
791
- // Ref mirror of the drawer's walkthrough id so the symbol resolver stays
797
+ // Ref mirror of the drawer's trail id so the symbol resolver stays
792
798
  // stable across graph re-renders (the drawer is memoized on callback identity).
793
- const drawerWalkthroughIdRef = useRef<string | null>(null);
794
- drawerWalkthroughIdRef.current = focusedWalkthrough?.id ?? null;
799
+ const drawerTrailIdRef = useRef<string | null>(null);
800
+ drawerTrailIdRef.current = focusedTrail?.id ?? null;
795
801
 
796
- // Walkthrough shown on the canvas title chip (focus, hover highlight, or
802
+ // Trail shown on the canvas title chip (focus, hover highlight, or
797
803
  // autoplay focus).
798
- const overlayWalkthroughTitle = useMemo(() => {
799
- if (!showWalkthroughTitle || !walkthroughs?.length) return null;
804
+ const overlayTrailTitle = useMemo(() => {
805
+ if (!showTrailTitle || !trails?.length) return null;
800
806
  const id =
801
- focusedWalkthroughId ??
802
- autoPlayFocus?.walkthroughId ??
803
- hoveredWalkthroughStep?.walkthroughId;
807
+ focusedTrailId ??
808
+ autoPlayFocus?.trailId ??
809
+ hoveredTrailStep?.trailId;
804
810
  if (!id) return null;
805
- return walkthroughs.find((t) => t.id === id)?.title ?? null;
811
+ return trails.find((t) => t.id === id)?.title ?? null;
806
812
  }, [
807
- showWalkthroughTitle,
808
- walkthroughs,
809
- focusedWalkthroughId,
813
+ showTrailTitle,
814
+ trails,
815
+ focusedTrailId,
810
816
  autoPlayFocus,
811
- hoveredWalkthroughStep,
817
+ hoveredTrailStep,
812
818
  ]);
813
819
 
814
820
  // Active step for the bottom-of-title progress + annotation chip.
815
- const overlayWalkthroughStep = useMemo(() => {
816
- if (!showWalkthroughTitle || !walkthroughs?.length) return null;
821
+ const overlayTrailStep = useMemo(() => {
822
+ if (!showTrailTitle || !trails?.length) return null;
817
823
  const tlId =
818
- focusedWalkthroughId ??
819
- autoPlayFocus?.walkthroughId ??
820
- hoveredWalkthroughStep?.walkthroughId ??
824
+ focusedTrailId ??
825
+ autoPlayFocus?.trailId ??
826
+ hoveredTrailStep?.trailId ??
821
827
  null;
822
828
  let stepIndex: number | null = null;
823
- if (focusedWalkthroughId != null) {
829
+ if (focusedTrailId != null) {
824
830
  stepIndex = focusedStepIndex;
825
831
  } else if (autoPlayFocus != null) {
826
832
  stepIndex = autoPlayFocus.stepIndex;
827
- } else if (hoveredWalkthroughStep != null) {
828
- stepIndex = hoveredWalkthroughStep.stepIndex;
833
+ } else if (hoveredTrailStep != null) {
834
+ stepIndex = hoveredTrailStep.stepIndex;
829
835
  }
830
836
  if (tlId == null || stepIndex == null) return null;
831
- const tl = walkthroughs.find((t) => t.id === tlId);
837
+ const tl = trails.find((t) => t.id === tlId);
832
838
  const step = tl?.steps[stepIndex];
833
839
  if (!step || !tl) return null;
834
840
  return {
@@ -837,25 +843,25 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
837
843
  annotation: step.annotation,
838
844
  };
839
845
  }, [
840
- showWalkthroughTitle,
841
- walkthroughs,
842
- focusedWalkthroughId,
846
+ showTrailTitle,
847
+ trails,
848
+ focusedTrailId,
843
849
  focusedStepIndex,
844
850
  autoPlayFocus,
845
- hoveredWalkthroughStep,
851
+ hoveredTrailStep,
846
852
  ]);
847
853
 
848
854
  const drawerTitle = useMemo(() => {
849
855
  if (!drawerTarget) return null;
850
856
  if (drawerTarget.kind === 'file') return drawerTarget.file;
851
- const tl = focusedWalkthrough;
857
+ const tl = focusedTrail;
852
858
  if (!tl) return null;
853
859
  if (drawerTarget.stepIndex == null) return tl.title;
854
860
  const step = tl.steps[drawerTarget.stepIndex];
855
861
  if (!step) return tl.title;
856
862
  const site = `${step.file.split('/').pop() ?? step.file}:${step.line}`;
857
863
  return `${tl.title} · ${site}`;
858
- }, [drawerTarget, focusedWalkthrough]);
864
+ }, [drawerTarget, focusedTrail]);
859
865
 
860
866
  // Refresh selected component when the components list updates (e.g. verify
861
867
  // writes back declarationRef).
@@ -869,14 +875,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
869
875
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
870
876
  // fields only — declarationRef updates after verify must not re-run ELK.
871
877
  const layoutKey = useMemo(
872
- () => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }),
873
- [components, walkthroughs, graphifyRelations],
878
+ () => subsystemGraphLayoutKey({ components, trails, graphifyRelations }),
879
+ [components, trails, graphifyRelations],
874
880
  );
875
881
  const componentsRef = useRef(components);
876
- const walkthroughsRef = useRef(walkthroughs);
882
+ const trailsRef = useRef(trails);
877
883
  const graphifyRelationsRef = useRef(graphifyRelations);
878
884
  componentsRef.current = components;
879
- walkthroughsRef.current = walkthroughs;
885
+ trailsRef.current = trails;
880
886
  graphifyRelationsRef.current = graphifyRelations;
881
887
 
882
888
  // Track measured dimensions from React Flow's dimension changes.
@@ -896,7 +902,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
896
902
  pass2GenRef.current += 1;
897
903
  const doc = {
898
904
  components: componentsRef.current,
899
- walkthroughs: walkthroughsRef.current,
905
+ trails: trailsRef.current,
900
906
  };
901
907
  void buildSubsystemGraph(doc, {
902
908
  maxNodeWidth,
@@ -953,7 +959,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
953
959
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id)!.height]));
954
960
  const gen = ++pass2GenRef.current;
955
961
  void buildSubsystemGraph(
956
- { components, walkthroughs },
962
+ { components, trails },
957
963
  { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine },
958
964
  )
959
965
  .then(({ nodes, edges: e, absoluteRects }) => {
@@ -967,7 +973,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
967
973
  // Reveal Pass 1 layout rather than leaving the cover up forever.
968
974
  setLayoutReady(true);
969
975
  });
970
- }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
976
+ }, [built.nodes, components, trails, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
971
977
 
972
978
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
973
979
  // Same-id live updates often get no new React Flow `dimensions` events, so
@@ -1008,8 +1014,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1008
1014
  }
1009
1015
  const src = edgeById.get(edgeId);
1010
1016
  setSelectedEdgeId(edgeId);
1011
- // A direct edge selection on the canvas supersedes any walkthrough focus.
1012
- setFocusedWalkthroughId(null);
1017
+ // A direct edge selection on the canvas supersedes any trail focus.
1018
+ setFocusedTrailId(null);
1013
1019
  setFocusedStepIndex(null);
1014
1020
  if (src) onEdgeSelect?.(src);
1015
1021
  },
@@ -1029,7 +1035,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1029
1035
  }
1030
1036
  setSelected(comp);
1031
1037
  setSelectedEdgeId(null);
1032
- setFocusedWalkthroughId(null);
1038
+ setFocusedTrailId(null);
1033
1039
  setFocusedStepIndex(null);
1034
1040
  onSelect?.(alias);
1035
1041
  }
@@ -1083,22 +1089,22 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1083
1089
  [components],
1084
1090
  );
1085
1091
 
1086
- // A `step` issue target names ONE step of a walkthrough — the walkthrough id in
1092
+ // A `step` issue target names ONE step of a trail — the trail id in
1087
1093
  // `id`, the 0-based step position in `stepIndex`. Validated against the loaded
1088
- // walkthroughs so a finding left over from an edited flow resolves to null
1094
+ // trails so a finding left over from an edited flow resolves to null
1089
1095
  // rather than framing whatever now happens to sit at that index.
1090
1096
  const issueStep = useCallback(
1091
1097
  (
1092
1098
  issue: SubsystemIssue,
1093
- ): { walkthrough: SubsystemWalkthrough; stepIndex: number } | null => {
1099
+ ): { trail: SubsystemTrail; stepIndex: number } | null => {
1094
1100
  const target = issue.target;
1095
1101
  if (target?.kind !== 'step') return null;
1096
1102
  if (target.id == null || target.stepIndex == null) return null;
1097
- const walkthrough = walkthroughs?.find((t) => t.id === target.id);
1098
- if (!walkthrough?.steps[target.stepIndex]) return null;
1099
- return { walkthrough, stepIndex: target.stepIndex };
1103
+ const trail = trails?.find((t) => t.id === target.id);
1104
+ if (!trail?.steps[target.stepIndex]) return null;
1105
+ return { trail, stepIndex: target.stepIndex };
1100
1106
  },
1101
- [walkthroughs],
1107
+ [trails],
1102
1108
  );
1103
1109
 
1104
1110
  // Per-node diagnostics badge: fold each component-targeted finding that maps
@@ -1194,7 +1200,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1194
1200
  // target:
1195
1201
  // component → that node
1196
1202
  // module → every component in that module
1197
- // walkthrough → the flow's nodes + its hop edges
1203
+ // trail → the flow's nodes + its step edges
1198
1204
  // repo → every component of that repo
1199
1205
  // graph → whole-graph finding; implicates nothing specific
1200
1206
  // Returns null when nothing is expanded, no expanded layer has findings, or
@@ -1225,15 +1231,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1225
1231
  nodeIds.add(c.alias);
1226
1232
  }
1227
1233
  }
1228
- } else if (t.kind === 'walkthrough') {
1229
- const wt = (walkthroughs ?? []).find(
1234
+ } else if (t.kind === 'trail') {
1235
+ const wt = (trails ?? []).find(
1230
1236
  (w) => w.id === t.id || w.title === t.label,
1231
1237
  );
1232
1238
  if (wt) {
1233
1239
  for (const s of wt.steps) {
1234
1240
  nodeIds.add(s.from);
1235
1241
  nodeIds.add(s.to);
1236
- edgeIds.add(walkthroughStepGraphEdgeId(s));
1242
+ edgeIds.add(trailStepGraphEdgeId(s));
1237
1243
  }
1238
1244
  }
1239
1245
  } else if (t.kind === 'repo') {
@@ -1250,7 +1256,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1250
1256
  if (nodeIds.has(e.source) && nodeIds.has(e.target)) edgeIds.add(e.id);
1251
1257
  }
1252
1258
  return { nodeIds, edgeIds };
1253
- }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
1259
+ }, [issues, expandedIssueCategories, components, trails, resolveAlias]);
1254
1260
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
1255
1261
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
1256
1262
 
@@ -1262,58 +1268,58 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1262
1268
  selectedEdgeIdRef.current = selectedEdgeId;
1263
1269
  // Ref mirrors of the focused step, so `unfocusIssueTarget` (a useCallback
1264
1270
  // over early deps) can tell a step focus it still owns from one the user has
1265
- // since moved elsewhere in the walkthrough panel.
1266
- const focusedStepRef = useRef<{ walkthroughId: string; stepIndex: number } | null>(
1271
+ // since moved elsewhere in the trail panel.
1272
+ const focusedStepRef = useRef<{ trailId: string; stepIndex: number } | null>(
1267
1273
  null,
1268
1274
  );
1269
1275
  focusedStepRef.current =
1270
- focusedWalkthroughId != null && focusedStepIndex != null
1271
- ? { walkthroughId: focusedWalkthroughId, stepIndex: focusedStepIndex }
1276
+ focusedTrailId != null && focusedStepIndex != null
1277
+ ? { trailId: focusedTrailId, stepIndex: focusedStepIndex }
1272
1278
  : null;
1273
1279
 
1274
- // Edge ids in walkthrough focus (an active flow's edge set, or a single
1275
- // step's edge). Used to frame the camera. `null` = no walkthrough focus.
1280
+ // Edge ids in trail focus (an active flow's edge set, or a single
1281
+ // step's edge). Used to frame the camera. `null` = no trail focus.
1276
1282
  const focusEdgeIds = useMemo(() => { if (autoPlayFocus) {
1277
- const tl = walkthroughs?.find((t) => t.id === autoPlayFocus.walkthroughId);
1283
+ const tl = trails?.find((t) => t.id === autoPlayFocus.trailId);
1278
1284
  const step = tl?.steps[autoPlayFocus.stepIndex];
1279
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1285
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1280
1286
  }
1281
- if (focusedWalkthroughId == null || !walkthroughs) return null;
1282
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
1287
+ if (focusedTrailId == null || !trails) return null;
1288
+ const tl = trails.find((t) => t.id === focusedTrailId);
1283
1289
  if (!tl) return null;
1284
1290
  if (focusedStepIndex != null) {
1285
1291
  const step = tl.steps[focusedStepIndex];
1286
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1292
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1287
1293
  }
1288
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
1289
- }, [walkthroughs, focusedWalkthroughId, focusedStepIndex, autoPlayFocus]);
1294
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
1295
+ }, [trails, focusedTrailId, focusedStepIndex, autoPlayFocus]);
1290
1296
 
1291
1297
  // 1-based step numbers per edge of the active flow (focused or
1292
1298
  // hover/autoplay-highlighted). An edge can appear in more than one step.
1293
1299
  const selectedFlowStepNos = useMemo(() => {
1294
- const activeId = focusedWalkthroughId ?? hoveredWalkthroughStep?.walkthroughId;
1295
- if (activeId == null || !walkthroughs) return null;
1296
- const tl = walkthroughs.find((t) => t.id === activeId);
1300
+ const activeId = focusedTrailId ?? hoveredTrailStep?.trailId;
1301
+ if (activeId == null || !trails) return null;
1302
+ const tl = trails.find((t) => t.id === activeId);
1297
1303
  if (!tl) return null;
1298
1304
  const map = new Map<string, number[]>();
1299
1305
  tl.steps.forEach((s, i) => {
1300
- const list = map.get(walkthroughStepGraphEdgeId(s)) ?? [];
1306
+ const list = map.get(trailStepGraphEdgeId(s)) ?? [];
1301
1307
  list.push(i + 1);
1302
- map.set(walkthroughStepGraphEdgeId(s), list);
1308
+ map.set(trailStepGraphEdgeId(s), list);
1303
1309
  });
1304
1310
  return map;
1305
- }, [walkthroughs, focusedWalkthroughId, hoveredWalkthroughStep]);
1311
+ }, [trails, focusedTrailId, hoveredTrailStep]);
1306
1312
 
1307
- // Union of every expanded (opened) walkthrough's edges — the visible set.
1313
+ // Union of every expanded (opened) trail's edges — the visible set.
1308
1314
  const openedEdgeIds = useMemo(() => {
1309
- if (!walkthroughs || expandedWalkthroughs.size === 0) return null;
1315
+ if (!trails || expandedTrails.size === 0) return null;
1310
1316
  const ids = new Set<string>();
1311
- for (const tl of walkthroughs) {
1312
- if (!expandedWalkthroughs.has(tl.id)) continue;
1313
- for (const s of tl.steps) ids.add(walkthroughStepGraphEdgeId(s));
1317
+ for (const tl of trails) {
1318
+ if (!expandedTrails.has(tl.id)) continue;
1319
+ for (const s of tl.steps) ids.add(trailStepGraphEdgeId(s));
1314
1320
  }
1315
1321
  return ids.size > 0 ? ids : null;
1316
- }, [walkthroughs, expandedWalkthroughs]);
1322
+ }, [trails, expandedTrails]);
1317
1323
 
1318
1324
  const endpointsOf = (edgeIds: ReadonlySet<string> | null): Set<string> | null => {
1319
1325
  if (!edgeIds) return null;
@@ -1327,20 +1333,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1327
1333
  };
1328
1334
  // Edge/nodes involved in the hovered step or whole flow.
1329
1335
  const hoverEdgeIds = useMemo(() => {
1330
- if (!hoveredWalkthroughStep || !walkthroughs) return null;
1331
- const tl = walkthroughs.find((t) => t.id === hoveredWalkthroughStep.walkthroughId);
1336
+ if (!hoveredTrailStep || !trails) return null;
1337
+ const tl = trails.find((t) => t.id === hoveredTrailStep.trailId);
1332
1338
  if (!tl) return null;
1333
- if (hoveredWalkthroughStep.stepIndex == null) {
1334
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
1339
+ if (hoveredTrailStep.stepIndex == null) {
1340
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
1335
1341
  }
1336
- const step = tl.steps[hoveredWalkthroughStep.stepIndex];
1337
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1338
- }, [walkthroughs, hoveredWalkthroughStep]);
1342
+ const step = tl.steps[hoveredTrailStep.stepIndex];
1343
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1344
+ }, [trails, hoveredTrailStep]);
1339
1345
  // While hovering with a *step* already selected, brighten the union of
1340
1346
  // selected + hovered participants. Whole-flow focus (or no focus) keeps
1341
1347
  // the old hover-replace preview so a step hover still dims the rest.
1342
1348
  const previewEdgeIds = useMemo(() => {
1343
- // Autoplay drives focus without a hover, so its hop is the preview set.
1349
+ // Autoplay drives focus without a hover, so its step is the preview set.
1344
1350
  if (autoPlayFocus) return focusEdgeIds;
1345
1351
  if (!hoverEdgeIds) return null;
1346
1352
  // While hovering with a *step* already selected, brighten the union of
@@ -1389,36 +1395,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1389
1395
  // `isSelected` rides in data because the node's stopPropagation() keeps
1390
1396
  // React Flow's own selection state from updating.
1391
1397
  const dispNodes = useMemo(() => {
1392
- // While a step/flow is hovered, selected + hovered participants stay
1393
- // bright; everything else among the currently-visible (opened-walkthrough)
1394
- // nodes is dimmed. Nodes outside the opened-walkthrough set stay hidden.
1395
- // Exception: with no expanded flow at all (a sidebar-less embed), there is
1396
- // nothing to hide toward — everything shows, and focus/hover only dims.
1397
- const noOpenedFlow = openedEdgeIds == null;
1398
+ // While a step/flow is hovered, hovered participants stay bright; every
1399
+ // other node is dimmed (and, when a flow is expanded, hidden). Exception:
1400
+ // with no expanded flow at all (a sidebar-less embed) there is nothing to
1401
+ // hide toward — everything shows, and focus/hover only dims.
1398
1402
  return xyflowNodesBase.map((n) => {
1399
1403
  // Boundary frames follow their members: hidden when no member is
1400
1404
  // visible, dimmed when members are dimmed. Never selectable.
1401
1405
  if (n.type === 'subsystem-group') {
1402
1406
  const region = (n.data as { region?: { key?: string; memberAliases?: string[] } } | undefined)?.region;
1403
1407
  const memberAliases = region?.memberAliases ?? [];
1404
- // Hover/autoplay dim-only: never hide a frame (see the node branch).
1405
- const vis = flowElementVisibility({
1408
+ // `brightNodeIds` is the focused step's endpoints. It has to be a
1409
+ // spotlight member as well as a bright set: without an expanded flow
1410
+ // there is no opened set, so dimming has to be driven from the
1411
+ // participants or nothing outside the focused step would dim.
1412
+ const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
1413
+ const { hidden, dimmed } = flowNodeVisibility({
1406
1414
  inOpened: memberAliases.some((alias) => openedNodeIds?.has(alias) === true),
1407
1415
  inSelected: memberAliases.some((alias) => brightNodeIds?.has(alias) === true),
1416
+ inSpotlight: memberAliases.some((alias) => dimSource?.has(alias) === true),
1408
1417
  anyOpened: openedNodeIds != null,
1409
- anySelected: brightNodeIds != null || previewNodeIds != null,
1418
+ anySelected: brightNodeIds != null,
1419
+ anySpotlight: dimSource != null,
1410
1420
  });
1411
- // `brightNodeIds` is the focused step's endpoints. It has to be a dim
1412
- // source, not just a bright set: without an expanded flow,
1413
- // `flowElementVisibility` reports non-participants as *hidden*, and the
1414
- // noOpenedFlow escape below discards that — leaving them neither hidden
1415
- // nor dimmed. Naming the participants as the dim source dims everything
1416
- // outside the focused step however the flow got focused.
1417
- const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
1418
- const dimmed = dimSource
1419
- ? vis.hidden || !memberAliases.some((alias) => dimSource.has(alias))
1420
- : vis.dimmed;
1421
- const hidden = noOpenedFlow ? false : vis.hidden;
1422
1421
  // Host override wins over the library's derived frame color.
1423
1422
  const color = region?.key != null ? boundaryColors?.[region.key] : undefined;
1424
1423
  // Boundary findings badge the FRAME, not a member — a region's shape is
@@ -1438,22 +1437,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1438
1437
  }
1439
1438
  const comp = (n.data as { component?: SubsystemComponent } | undefined)?.component;
1440
1439
  const fileMatch = fileMatchForNode(comp?.file, openFile, focusNodeIds?.has(n.id) === true);
1441
- // Hover/autoplay is a dim-only signal: never hide non-participants (there
1442
- // may be no "opened" flow to reveal them, e.g. a sidebar-less autoplay
1443
- // embed), only dim them. Bases selection on the focused step, not the
1444
- // hover preview, so hovering doesn't strip the frame's `isSelected`.
1440
+ // Bases selection on the focused step, not the hover preview, so
1441
+ // hovering doesn't strip the frame's `isSelected`.
1445
1442
  const isSelected = selected?.alias !== undefined && comp?.alias === selected.alias;
1446
- const vis = flowElementVisibility({
1443
+ // `brightNodeIds` (the focused step's endpoints) is a spotlight member as
1444
+ // well as a bright set — see the group branch above.
1445
+ const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
1446
+ const { hidden, dimmed } = flowNodeVisibility({
1447
1447
  inOpened: openedNodeIds?.has(n.id) === true,
1448
1448
  inSelected: brightNodeIds?.has(n.id) === true,
1449
+ inSpotlight: dimSource?.has(n.id) === true,
1449
1450
  anyOpened: openedNodeIds != null,
1450
- anySelected: brightNodeIds != null || previewNodeIds != null,
1451
+ anySelected: brightNodeIds != null,
1452
+ anySpotlight: dimSource != null,
1451
1453
  });
1452
- // `brightNodeIds` (the focused step's endpoints) is a dim source as well
1453
- // as a bright set — see the group branch above.
1454
- const dimSource = previewNodeIds ?? brightNodeIds ?? issueFocusNodeIds;
1455
- const dimmed = dimSource ? (vis.hidden || !dimSource.has(n.id)) : vis.dimmed;
1456
- const hidden = noOpenedFlow ? false : vis.hidden;
1457
1454
  // Component findings key by alias, boundary findings by region node id.
1458
1455
  // The two id spaces are disjoint (`module:` / `process:` are prefixed), so
1459
1456
  // one lookup covers both node kinds.
@@ -1498,7 +1495,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1498
1495
  };
1499
1496
  // Edges outside the selected view are hidden entirely (labels included).
1500
1497
  // graphify-native edges belong to the graphify view (they are static
1501
- // topology, not runtime hops) regardless of their verb.
1498
+ // topology, not runtime steps) regardless of their verb.
1502
1499
  const edgeInView = (e: Edge): boolean => {
1503
1500
  const d = e.data as
1504
1501
  | { mechanism?: string; provenance?: SubsystemEdgeProvenance }
@@ -1507,7 +1504,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1507
1504
  if (resolvedEdgeView === 'graphify') {
1508
1505
  return d?.provenance === 'graphify';
1509
1506
  }
1510
- return isWalkthroughMechanism(mechanism);
1507
+ return isTrailMechanism(mechanism);
1511
1508
  };
1512
1509
  if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
1513
1510
  return baseEdges.map((e) => {
@@ -1574,7 +1571,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1574
1571
  (_e, node: Node) => {
1575
1572
  const comp = (node.data as { component?: SubsystemComponent } | undefined)?.component;
1576
1573
  setSelectedEdgeId(null);
1577
- setFocusedWalkthroughId(null);
1574
+ setFocusedTrailId(null);
1578
1575
  setFocusedStepIndex(null);
1579
1576
  if (node.type === 'subsystem-component' && comp) {
1580
1577
  // Clicking the already-selected node unselects it (toggle off).
@@ -1602,7 +1599,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1602
1599
  const onPaneClick = useCallback(() => {
1603
1600
  setSelected(null);
1604
1601
  setSelectedEdgeId(null);
1605
- setFocusedWalkthroughId(null);
1602
+ setFocusedTrailId(null);
1606
1603
  setFocusedStepIndex(null);
1607
1604
  issueModuleFocusRef.current = null;
1608
1605
  }, []);
@@ -1610,8 +1607,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1610
1607
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
1611
1608
  // own owner-avatar header. Clicking a header collapses that repo's tree.
1612
1609
  const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
1613
- const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
1614
- // Aliases of proposed components — the flows panel tints walkthrough titles
1610
+ const hasTrails = useMemo(() => (trails?.length ?? 0) > 0, [trails]);
1611
+ // Aliases of proposed components — the flows panel tints trail titles
1615
1612
  // (and steps) that touch one.
1616
1613
  const proposedAliases = useMemo(
1617
1614
  () => new Set(components.filter((c) => c.proposed).map((c) => c.alias)),
@@ -1688,7 +1685,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1688
1685
  const onNodeDoubleClick: NodeMouseHandler = useCallback(
1689
1686
  (_e, node: Node) => {
1690
1687
  // Boundary frame → frame its members on the canvas; double-click again to
1691
- // zoom back out. User-initiated, so it ignores the walkthrough zoom gate.
1688
+ // zoom back out. User-initiated, so it ignores the trail zoom gate.
1692
1689
  if (node.type === 'subsystem-group') {
1693
1690
  const key =
1694
1691
  (node.data as { region?: { key?: string } } | undefined)?.region?.key;
@@ -1724,7 +1721,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1724
1721
  [onOpenDeclarationFile, fitView, focusedBoundaryKey],
1725
1722
  );
1726
1723
 
1727
- const onOpenFileFromWalkthrough = useCallback(
1724
+ const onOpenFileFromTrail = useCallback(
1728
1725
  (file: string, opts?: SubsystemOpenFileOptions) => {
1729
1726
  setFileOverlay({ file, startLine: opts?.startLine });
1730
1727
  onFileSelect?.(file);
@@ -1746,15 +1743,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1746
1743
  const fileOverlayOpen = fileOverlay != null;
1747
1744
  const collapseCanvas = drawerFillHeight || fileOverlayOpen;
1748
1745
 
1749
- // Drop the full-file overlay when the underlying walkthrough drawer changes
1746
+ // Drop the full-file overlay when the underlying trail drawer changes
1750
1747
  // or closes — open-file keeps the same drawerTarget so the snippets stay mounted.
1751
- const walkthroughDrawerKey =
1752
- drawerTarget?.kind === 'walkthrough'
1753
- ? `${drawerTarget.walkthroughId}:${drawerTarget.stepIndex}`
1748
+ const trailDrawerKey =
1749
+ drawerTarget?.kind === 'trail'
1750
+ ? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
1754
1751
  : null;
1755
1752
  useEffect(() => {
1756
1753
  setFileOverlay(null);
1757
- }, [walkthroughDrawerKey]);
1754
+ }, [trailDrawerKey]);
1758
1755
 
1759
1756
  useEffect(() => {
1760
1757
  if (!fileOverlayOpen) return;
@@ -1765,16 +1762,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1765
1762
  return () => window.removeEventListener('keydown', onKey);
1766
1763
  }, [fileOverlayOpen, closeFileOverlay]);
1767
1764
 
1768
- // Camera helper shared by the walkthrough interactions. Fits the union of
1765
+ // Camera helper shared by the trail interactions. Fits the union of
1769
1766
  // the focused edges' endpoint rects AND their routed waypoints — the edge
1770
1767
  // carries its full polyline (`elkPathPoints`, absolute flow coords), so the
1771
- // frame is the line the hop actually traces. That keeps a hop whose route
1768
+ // frame is the line the step actually traces. That keeps a step whose route
1772
1769
  // bulges out around intervening nodes from being clipped at the viewport
1773
1770
  // edge, and it is the edge's own geometry doing the guiding, not fudge
1774
1771
  // padding.
1775
1772
  const fitFocusBounds = useCallback(
1776
1773
  (ids: ReadonlySet<string>) => {
1777
- if (!zoomOnWalkthroughFocus) return;
1774
+ if (!zoomOnTrailFocus) return;
1778
1775
  const nodeIds = new Set<string>();
1779
1776
  const points: { x: number; y: number }[] = [];
1780
1777
  for (const e of baseEdges) {
@@ -1819,15 +1816,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1819
1816
  };
1820
1817
  fitBounds(bounds, {
1821
1818
  padding: 0.15,
1822
- duration: walkthroughFocusDurationMs,
1819
+ duration: trailFocusDurationMs,
1823
1820
  });
1824
1821
  },
1825
1822
  [
1826
1823
  baseEdges,
1827
1824
  built.absoluteRects,
1828
1825
  fitBounds,
1829
- zoomOnWalkthroughFocus,
1830
- walkthroughFocusDurationMs,
1826
+ zoomOnTrailFocus,
1827
+ trailFocusDurationMs,
1831
1828
  ],
1832
1829
  );
1833
1830
 
@@ -1885,16 +1882,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1885
1882
  width: Math.max(1, maxX - minX),
1886
1883
  height: Math.max(1, maxY - minY),
1887
1884
  },
1888
- { padding, duration: walkthroughFocusDurationMs },
1885
+ { padding, duration: trailFocusDurationMs },
1889
1886
  );
1890
1887
  },
1891
- [rectForNode, fitBounds, walkthroughFocusDurationMs],
1888
+ [rectForNode, fitBounds, trailFocusDurationMs],
1892
1889
  );
1893
1890
 
1894
- // Zoom back out to the full diagram after the last expanded walkthrough
1891
+ // Zoom back out to the full diagram after the last expanded trail
1895
1892
  // closes (visibility restores non-flow nodes that were hidden).
1896
1893
  const fitOverview = useCallback(() => {
1897
- if (!zoomOnWalkthroughFocus) return;
1894
+ if (!zoomOnTrailFocus) return;
1898
1895
  fitView({
1899
1896
  padding: 0.1,
1900
1897
  includeHiddenNodes: false,
@@ -1902,115 +1899,115 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1902
1899
  maxZoom: 2,
1903
1900
  duration: 300,
1904
1901
  });
1905
- }, [fitView, zoomOnWalkthroughFocus]);
1902
+ }, [fitView, zoomOnTrailFocus]);
1906
1903
 
1907
1904
  // Focus an entire flow: hide everything but the flow's nodes and edges, and
1908
1905
  // frame the flow on the canvas. Selection state is cleared — the graph now
1909
1906
  // reads as the narrative. No drawer: the code view only opens on a step
1910
- // click. A stale walkthrough drawer (from a previously focused flow's step)
1907
+ // click. A stale trail drawer (from a previously focused flow's step)
1911
1908
  // closes; an explicitly opened file drawer stays.
1912
1909
  // In `dim` mode: clear step highlight and leave the full graph visible
1913
1910
  // (no zoom / hide) — matching "step away from a hovered step".
1914
- const focusWalkthroughEdges = useCallback(
1915
- (tl: SubsystemWalkthrough) => {
1911
+ const focusTrailEdges = useCallback(
1912
+ (tl: SubsystemTrail) => {
1916
1913
  setSelected(null);
1917
1914
  setSelectedEdgeId(null);
1918
- setHoveredWalkthroughStep(null);
1919
- if (walkthroughStepMode === 'dim') {
1915
+ setHoveredTrailStep(null);
1916
+ if (trailStepMode === 'dim') {
1920
1917
  setFocusedStepIndex(null);
1921
- setFocusedWalkthroughId(null);
1922
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1918
+ setFocusedTrailId(null);
1919
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1923
1920
  return;
1924
1921
  }
1925
1922
  setFocusedStepIndex(null);
1926
- setFocusedWalkthroughId(tl.id);
1927
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1928
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1923
+ setFocusedTrailId(tl.id);
1924
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1925
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1929
1926
  },
1930
- [fitFocusBounds, walkthroughStepMode],
1927
+ [fitFocusBounds, trailStepMode],
1931
1928
  );
1932
1929
 
1933
- const clearWalkthroughFocus = useCallback(() => {
1934
- setFocusedWalkthroughId(null);
1930
+ const clearTrailFocus = useCallback(() => {
1931
+ setFocusedTrailId(null);
1935
1932
  setFocusedStepIndex(null);
1936
- setHoveredWalkthroughStep(null);
1937
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1933
+ setHoveredTrailStep(null);
1934
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1938
1935
  }, []);
1939
1936
 
1940
- // Host deep-link: opening the graph from a walkthrough row selects that
1937
+ // Host deep-link: opening the graph from a trail row selects that
1941
1938
  // flow — expand its steps, focus its edges, and switch the sidebar to
1942
- // Walkthroughs. Guarded by a ref so a later in-tab selection isn't yanked
1939
+ // Trails. Guarded by a ref so a later in-tab selection isn't yanked
1943
1940
  // back; a remount (or a new id) re-applies it.
1944
- const appliedInitialWalkthroughRef = useRef<string | null>(null);
1941
+ const appliedInitialTrailRef = useRef<string | null>(null);
1945
1942
  useEffect(() => {
1946
- if (!initialWalkthroughId) return;
1947
- if (appliedInitialWalkthroughRef.current === initialWalkthroughId) return;
1948
- if (!layoutReady || !walkthroughs?.length) return;
1949
- const tl = walkthroughs.find((t) => t.id === initialWalkthroughId);
1943
+ if (!initialTrailId) return;
1944
+ if (appliedInitialTrailRef.current === initialTrailId) return;
1945
+ if (!layoutReady || !trails?.length) return;
1946
+ const tl = trails.find((t) => t.id === initialTrailId);
1950
1947
  if (!tl) return;
1951
- appliedInitialWalkthroughRef.current = initialWalkthroughId;
1952
- setSidebarView('walkthroughs');
1953
- setExpandedWalkthroughs((prev) => new Set(prev).add(tl.id));
1954
- focusWalkthroughEdges(tl);
1955
- }, [initialWalkthroughId, layoutReady, walkthroughs, focusWalkthroughEdges]);
1948
+ appliedInitialTrailRef.current = initialTrailId;
1949
+ setSidebarView('trails');
1950
+ setExpandedTrails((prev) => new Set(prev).add(tl.id));
1951
+ focusTrailEdges(tl);
1952
+ }, [initialTrailId, layoutReady, trails, focusTrailEdges]);
1956
1953
 
1957
1954
  // Switching sidebar panels also switches the edge vocabulary. Leaving the
1958
- // Walkthroughs panel drops its canvas state (focus, expanded flows, selected
1955
+ // Trails panel drops its canvas state (focus, expanded flows, selected
1959
1956
  // edge) so the Files view's topology edges aren't gated by a flow the user
1960
1957
  // can no longer see or un-dim.
1961
1958
  const changeSidebarView = useCallback(
1962
- (view: 'files' | 'walkthroughs') => {
1959
+ (view: 'files' | 'trails') => {
1963
1960
  setSidebarView(view);
1964
- if (view === 'walkthroughs') return;
1965
- clearWalkthroughFocus();
1966
- setExpandedWalkthroughs(new Set());
1961
+ if (view === 'trails') return;
1962
+ clearTrailFocus();
1963
+ setExpandedTrails(new Set());
1967
1964
  setSelectedEdgeId(null);
1968
1965
  },
1969
- [clearWalkthroughFocus],
1966
+ [clearTrailFocus],
1970
1967
  );
1971
1968
 
1972
- // Focus a single step's edge on the canvas and open/scroll the walkthrough
1969
+ // Focus a single step's edge on the canvas and open/scroll the trail
1973
1970
  // drawer to that step's snippet. Clicking the already-focused step clears
1974
1971
  // step focus and returns to whole-flow (high-level) framing.
1975
1972
  // In `dim` mode: only dim non-participants (same as hovering a step) —
1976
1973
  // no camera move, no drawer, no hide.
1977
- const focusWalkthroughStep = useCallback(
1978
- (tl: SubsystemWalkthrough, stepIndex: number) => {
1974
+ const focusTrailStep = useCallback(
1975
+ (tl: SubsystemTrail, stepIndex: number) => {
1979
1976
  const step = tl.steps[stepIndex];
1980
1977
  if (!step) return;
1981
1978
  setSelected(null);
1982
1979
  setSelectedEdgeId(null);
1983
- if (walkthroughStepMode === 'dim') {
1980
+ if (trailStepMode === 'dim') {
1984
1981
  setFocusedStepIndex(null);
1985
- setFocusedWalkthroughId(null);
1986
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1987
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1982
+ setFocusedTrailId(null);
1983
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1984
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1988
1985
  return;
1989
1986
  }
1990
1987
  // Toggle: clicking the selected step unselects it (back to whole flow).
1991
- if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
1988
+ if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
1992
1989
  setFocusedStepIndex(null);
1993
1990
  // Pointer is still over the row — keep hover preview so dimming doesn't
1994
1991
  // flash off until mouseleave.
1995
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1996
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1992
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1993
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1997
1994
  if (pendingFocusFitRef.current != null) {
1998
1995
  window.clearTimeout(pendingFocusFitRef.current);
1999
1996
  pendingFocusFitRef.current = null;
2000
1997
  }
2001
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1998
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
2002
1999
  return;
2003
2000
  }
2004
2001
  setFocusedStepIndex(stepIndex);
2005
- setFocusedWalkthroughId(tl.id);
2006
- setHoveredWalkthroughStep(null);
2002
+ setFocusedTrailId(tl.id);
2003
+ setHoveredTrailStep(null);
2007
2004
  // Open the drawer before fitting. If it was closed, wait for its height
2008
2005
  // transition so fitView uses the reduced canvas — not full height.
2009
2006
  const drawerWasOpen = drawerOpenRef.current;
2010
- if (renderWalkthroughViewer) {
2007
+ if (renderTrailViewer) {
2011
2008
  setDrawerTarget({
2012
- kind: 'walkthrough',
2013
- walkthroughId: tl.id,
2009
+ kind: 'trail',
2010
+ trailId: tl.id,
2014
2011
  stepIndex,
2015
2012
  });
2016
2013
  } else {
@@ -2020,7 +2017,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2020
2017
  startLine: step.line,
2021
2018
  });
2022
2019
  }
2023
- const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
2020
+ const edgeIds = new Set([trailStepGraphEdgeId(step)]);
2024
2021
  if (pendingFocusFitRef.current != null) {
2025
2022
  window.clearTimeout(pendingFocusFitRef.current);
2026
2023
  pendingFocusFitRef.current = null;
@@ -2037,9 +2034,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2037
2034
  [
2038
2035
  fitFocusBounds,
2039
2036
  focusedStepIndex,
2040
- focusedWalkthroughId,
2041
- renderWalkthroughViewer,
2042
- walkthroughStepMode,
2037
+ focusedTrailId,
2038
+ renderTrailViewer,
2039
+ trailStepMode,
2043
2040
  ],
2044
2041
  );
2045
2042
 
@@ -2061,10 +2058,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2061
2058
  const prev = prevPreviewEdgeIdsRef.current;
2062
2059
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
2063
2060
 
2064
- if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim') return;
2061
+ if (!zoomOnTrailFocus || trailStepMode === 'dim') return;
2065
2062
  // Camera follows hover only when a specific step is already focused (or
2066
2063
  // autoplay is driving an auto-focus).
2067
- if (!autoPlayFocus && (focusedWalkthroughId == null || focusedStepIndex == null)) {
2064
+ if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
2068
2065
  return;
2069
2066
  }
2070
2067
 
@@ -2078,26 +2075,26 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2078
2075
  }, [
2079
2076
  previewEdgeIds,
2080
2077
  focusEdgeIds,
2081
- focusedWalkthroughId,
2078
+ focusedTrailId,
2082
2079
  focusedStepIndex,
2083
2080
  autoPlayFocus,
2084
2081
  fitFocusBounds,
2085
- zoomOnWalkthroughFocus,
2086
- walkthroughStepMode,
2082
+ zoomOnTrailFocus,
2083
+ trailStepMode,
2087
2084
  ]);
2088
2085
 
2089
- // Graph-only embeds: cycle walkthrough steps. In `focus` mode each step is
2090
- // selected (camera frames it via fitFocusBounds) and, when a walkthrough
2086
+ // Graph-only embeds: cycle trail steps. In `focus` mode each step is
2087
+ // selected (camera frames it via fitFocusBounds) and, when a trail
2091
2088
  // viewer is supplied, its snippet drawer opens; in `dim` mode it only
2092
2089
  // dim-highlights the step (hover-style) with no camera move.
2093
2090
  useEffect(() => {
2094
- if (!autoPlayWalkthroughs || walkthroughs == null || walkthroughs.length === 0) return;
2091
+ if (!autoPlayTrails || trails == null || trails.length === 0) return;
2095
2092
  if (!layoutReady) return;
2096
- const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
2093
+ const playable = trails.filter((tl) => tl.steps.length > 0);
2097
2094
  if (playable.length === 0) return;
2098
2095
  // Only run the cycles that can actually be shown in the current edge view.
2099
2096
  const inView = playable.filter((tl) =>
2100
- tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)),
2097
+ tl.steps.some((s) => isTrailMechanism(s.mechanism)),
2101
2098
  );
2102
2099
  if (inView.length === 0) return;
2103
2100
  const cyc = inView;
@@ -2106,8 +2103,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2106
2103
  let tlIdx = 0;
2107
2104
  let stepIdx = 0;
2108
2105
  let timer: number | null = null;
2109
- const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
2110
- const focusMode = walkthroughStepMode !== 'dim';
2106
+ const interval = Math.max(400, trailAutoPlayIntervalMs);
2107
+ const focusMode = trailStepMode !== 'dim';
2111
2108
  let cleanup = () => {};
2112
2109
 
2113
2110
  const tick = () => {
@@ -2116,15 +2113,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2116
2113
  setSelected(null);
2117
2114
  setSelectedEdgeId(null);
2118
2115
  if (focusMode) {
2119
- setHoveredWalkthroughStep(null);
2120
- setFocusedWalkthroughId(null);
2116
+ setHoveredTrailStep(null);
2117
+ setFocusedTrailId(null);
2121
2118
  setFocusedStepIndex(null);
2122
- setAutoPlayFocus({ walkthroughId: tl.id, stepIndex: stepIdx });
2119
+ setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
2123
2120
  if (!hideDrawer) {
2124
2121
  const step = tl.steps[stepIdx];
2125
2122
  if (step) {
2126
- if (renderWalkthroughViewer) {
2127
- setDrawerTarget({ kind: 'walkthrough', walkthroughId: tl.id, stepIndex: stepIdx });
2123
+ if (renderTrailViewer) {
2124
+ setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
2128
2125
  } else {
2129
2126
  setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
2130
2127
  }
@@ -2132,9 +2129,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2132
2129
  }
2133
2130
  } else {
2134
2131
  setAutoPlayFocus(null);
2135
- setFocusedWalkthroughId(null);
2132
+ setFocusedTrailId(null);
2136
2133
  setFocusedStepIndex(null);
2137
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
2134
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
2138
2135
  }
2139
2136
  stepIdx += 1;
2140
2137
  if (stepIdx >= tl.steps.length) {
@@ -2148,29 +2145,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2148
2145
  cleanup = () => {
2149
2146
  cancelled = true;
2150
2147
  if (timer != null) window.clearTimeout(timer);
2151
- setHoveredWalkthroughStep(null);
2152
- setFocusedWalkthroughId(null);
2148
+ setHoveredTrailStep(null);
2149
+ setFocusedTrailId(null);
2153
2150
  setFocusedStepIndex(null);
2154
2151
  setAutoPlayFocus(null);
2155
2152
  };
2156
2153
  return cleanup;
2157
2154
  }, [
2158
- autoPlayWalkthroughs,
2159
- walkthroughs,
2160
- walkthroughAutoPlayIntervalMs,
2155
+ autoPlayTrails,
2156
+ trails,
2157
+ trailAutoPlayIntervalMs,
2161
2158
  layoutReady,
2162
- walkthroughStepMode,
2163
- renderWalkthroughViewer,
2159
+ trailStepMode,
2160
+ renderTrailViewer,
2164
2161
  hideDrawer,
2165
2162
  ]);
2166
2163
 
2167
- // Arrow keys step through the focused walkthrough once a step is active
2164
+ // Arrow keys step through the focused trail once a step is active
2168
2165
  // (sidebar click or drawer open). Ignores typing targets and chords.
2169
2166
  useEffect(() => {
2170
- if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
2167
+ if (focusedTrailId == null || focusedStepIndex == null || !trails) {
2171
2168
  return;
2172
2169
  }
2173
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
2170
+ const tl = trails.find((t) => t.id === focusedTrailId);
2174
2171
  if (!tl || tl.steps.length === 0) return;
2175
2172
 
2176
2173
  const onKeyDown = (e: KeyboardEvent) => {
@@ -2196,31 +2193,31 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2196
2193
  }
2197
2194
  if (next === focusedStepIndex) return;
2198
2195
  e.preventDefault();
2199
- focusWalkthroughStep(tl, next);
2196
+ focusTrailStep(tl, next);
2200
2197
  };
2201
2198
 
2202
2199
  window.addEventListener('keydown', onKeyDown);
2203
2200
  return () => window.removeEventListener('keydown', onKeyDown);
2204
2201
  }, [
2205
- focusedWalkthroughId,
2202
+ focusedTrailId,
2206
2203
  focusedStepIndex,
2207
- walkthroughs,
2208
- focusWalkthroughStep,
2204
+ trails,
2205
+ focusTrailStep,
2209
2206
  ]);
2210
2207
 
2211
- const toggleWalkthroughCollapsed = useCallback(
2208
+ const toggleTrailCollapsed = useCallback(
2212
2209
  (tlId: string) => {
2213
2210
  const collapsingLast =
2214
- expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
2211
+ expandedTrails.has(tlId) && expandedTrails.size === 1;
2215
2212
 
2216
- setExpandedWalkthroughs((prev) => {
2213
+ setExpandedTrails((prev) => {
2217
2214
  const next = new Set(prev);
2218
2215
  if (next.has(tlId)) next.delete(tlId);
2219
2216
  else next.add(tlId);
2220
2217
  return next;
2221
2218
  });
2222
2219
 
2223
- if (!collapsingLast || !zoomOnWalkthroughFocus) return;
2220
+ if (!collapsingLast || !zoomOnTrailFocus) return;
2224
2221
 
2225
2222
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
2226
2223
  if (pendingFocusFitRef.current != null) {
@@ -2240,7 +2237,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2240
2237
  drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20,
2241
2238
  );
2242
2239
  },
2243
- [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus],
2240
+ [expandedTrails, fitOverview, zoomOnTrailFocus],
2244
2241
  );
2245
2242
 
2246
2243
  // Detail-panel links: related-name clicks select the matching component —
@@ -2258,7 +2255,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2258
2255
  if (!comp || comp.alias === selectedRef.current?.alias) return;
2259
2256
  setSelected(comp);
2260
2257
  setSelectedEdgeId(null);
2261
- setFocusedWalkthroughId(null);
2258
+ setFocusedTrailId(null);
2262
2259
  setFocusedStepIndex(null);
2263
2260
  onSelect?.(comp.alias);
2264
2261
  },
@@ -2277,9 +2274,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2277
2274
  if (comp) {
2278
2275
  setSelected(comp);
2279
2276
  setSelectedEdgeId(null);
2280
- setFocusedWalkthroughId(null);
2277
+ setFocusedTrailId(null);
2281
2278
  setFocusedStepIndex(null);
2282
- setHoveredWalkthroughStep(null);
2279
+ setHoveredTrailStep(null);
2283
2280
  issueModuleFocusRef.current = null;
2284
2281
  onSelect?.(comp.alias);
2285
2282
  requestAnimationFrame(() => {
@@ -2293,11 +2290,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2293
2290
  });
2294
2291
  } else if (step) {
2295
2292
  // A step finding focuses the step itself, through the same entry point
2296
- // the walkthrough panel uses — so the drawer, the step numbering, the
2293
+ // the trail panel uses — so the drawer, the step numbering, the
2297
2294
  // dim-mode hover preview, and the camera all behave identically whether
2298
2295
  // the step was reached from the panel or from a diagnostics card.
2299
2296
  issueModuleFocusRef.current = null;
2300
- focusWalkthroughStep(step.walkthrough, step.stepIndex);
2297
+ focusTrailStep(step.trail, step.stepIndex);
2301
2298
  } else {
2302
2299
  // Nothing selectable — but a module target names a boundary frame, so
2303
2300
  // frame that region.
@@ -2305,9 +2302,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2305
2302
  if (moduleNodeId) {
2306
2303
  setSelected(null);
2307
2304
  setSelectedEdgeId(null);
2308
- setFocusedWalkthroughId(null);
2305
+ setFocusedTrailId(null);
2309
2306
  setFocusedStepIndex(null);
2310
- setHoveredWalkthroughStep(null);
2307
+ setHoveredTrailStep(null);
2311
2308
  issueModuleFocusRef.current = moduleNodeId;
2312
2309
  requestAnimationFrame(() => {
2313
2310
  fitNodeRects(new Set([moduleNodeId]), 0.4);
@@ -2320,7 +2317,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2320
2317
  issueComponent,
2321
2318
  issueStep,
2322
2319
  issueModuleNodeId,
2323
- focusWalkthroughStep,
2320
+ focusTrailStep,
2324
2321
  onSelect,
2325
2322
  onSelectIssue,
2326
2323
  fitView,
@@ -2350,14 +2347,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2350
2347
  // not the card), and correctly leaves that alone here.
2351
2348
  const owned = focusedStepRef.current;
2352
2349
  if (
2353
- owned?.walkthroughId !== step.walkthrough.id ||
2350
+ owned?.trailId !== step.trail.id ||
2354
2351
  owned?.stepIndex !== step.stepIndex
2355
2352
  ) {
2356
2353
  return;
2357
2354
  }
2358
- setFocusedWalkthroughId(null);
2355
+ setFocusedTrailId(null);
2359
2356
  setFocusedStepIndex(null);
2360
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
2357
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
2361
2358
  } else if (moduleNodeId) {
2362
2359
  // Only unwind framing this card still owns.
2363
2360
  if (issueModuleFocusRef.current !== moduleNodeId) return;
@@ -2373,14 +2370,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2373
2370
  [issueComponent, issueStep, issueModuleNodeId, fitView],
2374
2371
  );
2375
2372
 
2376
- // Construct tokens inside a walkthrough snippet. A step's line is an edge
2373
+ // Construct tokens inside a trail snippet. A step's line is an edge
2377
2374
  // between its `from`/`to` components, so a token naming either of those
2378
2375
  // constructs should navigate to that construct's declaration. Index the
2379
- // matchable identifiers per step (`walkthroughId:index`).
2380
- const walkthroughSymbolIndex = useMemo(() => {
2376
+ // matchable identifiers per step (`trailId:index`).
2377
+ const trailSymbolIndex = useMemo(() => {
2381
2378
  const byAlias = new Map(components.map((c) => [c.alias, c]));
2382
2379
  const index = new Map<string, Map<string, string>>();
2383
- for (const wt of walkthroughs ?? []) {
2380
+ for (const wt of trails ?? []) {
2384
2381
  wt.steps.forEach((step, i) => {
2385
2382
  const tokens = new Map<string, string>();
2386
2383
  for (const alias of [step.from, step.to]) {
@@ -2392,19 +2389,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2392
2389
  });
2393
2390
  }
2394
2391
  return index;
2395
- }, [components, walkthroughs]);
2396
- const walkthroughSymbolIndexRef = useRef(walkthroughSymbolIndex);
2397
- walkthroughSymbolIndexRef.current = walkthroughSymbolIndex;
2398
-
2399
- // Stable resolver: reads the live index + focused walkthrough from refs so
2400
- // the memoized walkthrough drawer isn't rebuilt on every graph render.
2401
- const resolveWalkthroughSymbol = useCallback(
2402
- (query: WalkthroughSymbolQuery): string | null => {
2403
- const walkthroughId = drawerWalkthroughIdRef.current;
2404
- if (walkthroughId == null) return null;
2392
+ }, [components, trails]);
2393
+ const trailSymbolIndexRef = useRef(trailSymbolIndex);
2394
+ trailSymbolIndexRef.current = trailSymbolIndex;
2395
+
2396
+ // Stable resolver: reads the live index + focused trail from refs so
2397
+ // the memoized trail drawer isn't rebuilt on every graph render.
2398
+ const resolveTrailSymbol = useCallback(
2399
+ (query: TrailSymbolQuery): string | null => {
2400
+ const trailId = drawerTrailIdRef.current;
2401
+ if (trailId == null) return null;
2405
2402
  return (
2406
- walkthroughSymbolIndexRef.current
2407
- .get(`${walkthroughId}:${query.stepIndex}`)
2403
+ trailSymbolIndexRef.current
2404
+ .get(`${trailId}:${query.stepIndex}`)
2408
2405
  ?.get(query.tokenText) ?? null
2409
2406
  );
2410
2407
  },
@@ -2492,11 +2489,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2492
2489
  [],
2493
2490
  );
2494
2491
 
2495
- const walkthroughViewerRef = useRef(renderWalkthroughViewer);
2496
- walkthroughViewerRef.current = renderWalkthroughViewer;
2497
- const renderWalkthroughDrawerContent = useCallback(
2498
- (ctx: WalkthroughViewerContext) =>
2499
- walkthroughViewerRef.current?.(ctx) ?? null,
2492
+ const trailViewerRef = useRef(renderTrailViewer);
2493
+ trailViewerRef.current = renderTrailViewer;
2494
+ const renderTrailDrawerContent = useCallback(
2495
+ (ctx: TrailViewerContext) =>
2496
+ trailViewerRef.current?.(ctx) ?? null,
2500
2497
  [],
2501
2498
  );
2502
2499
 
@@ -2505,7 +2502,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2505
2502
  {/* Sidebar: scrollable title/description on top, files or flows pinned below.
2506
2503
  The description hides by default so files/flows get the room; the
2507
2504
  title-row toggle reveals it, and the lower panel yields back to 50%. */}
2508
- {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
2505
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2509
2506
  <div
2510
2507
  style={{
2511
2508
  width: sidebarWidth,
@@ -2612,7 +2609,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2612
2609
  )}
2613
2610
  {sidebarAfterDescription}
2614
2611
  </div>
2615
- {(treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (
2612
+ {(treeFilePaths.length > 0 || hasTrails || issuesActive) && (
2616
2613
  <div
2617
2614
  style={{
2618
2615
  ...(showDesc ? { height: '50%' as const } : { flex: 1, minHeight: 0 }),
@@ -2691,7 +2688,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2691
2688
  )
2692
2689
  ) : (
2693
2690
  <>
2694
- {hasWalkthroughs && (
2691
+ {hasTrails && (
2695
2692
  <div
2696
2693
  role="tablist"
2697
2694
  aria-label="Sidebar view"
@@ -2703,7 +2700,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2703
2700
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2704
2701
  }}
2705
2702
  >
2706
- {(['walkthroughs', 'files'] as const).map((view) => (
2703
+ {(['trails', 'files'] as const).map((view) => (
2707
2704
  <button
2708
2705
  key={view}
2709
2706
  type="button"
@@ -2727,30 +2724,30 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2727
2724
  cursor: 'pointer',
2728
2725
  }}
2729
2726
  >
2730
- {view === 'walkthroughs' ? 'Walkthroughs' : 'Files'}
2727
+ {view === 'trails' ? 'Trails' : 'Files'}
2731
2728
  </button>
2732
2729
  ))}
2733
2730
  </div>
2734
2731
  )}
2735
- {sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (
2736
- <WalkthroughsPanel
2737
- walkthroughs={walkthroughs}
2738
- expandedWalkthroughs={expandedWalkthroughs}
2739
- focusedWalkthroughId={focusedWalkthroughId}
2732
+ {sidebarView === 'trails' && trails && trails.length > 0 ? (
2733
+ <TrailsPanel
2734
+ trails={trails}
2735
+ expandedTrails={expandedTrails}
2736
+ focusedTrailId={focusedTrailId}
2740
2737
  focusedStepIndex={focusedStepIndex}
2741
- hoveredWalkthroughStep={hoveredWalkthroughStep}
2742
- onToggleCollapsed={toggleWalkthroughCollapsed}
2743
- onFocusFlow={focusWalkthroughEdges}
2744
- onClearFocus={clearWalkthroughFocus}
2745
- onFocusStep={focusWalkthroughStep}
2738
+ hoveredTrailStep={hoveredTrailStep}
2739
+ onToggleCollapsed={toggleTrailCollapsed}
2740
+ onFocusFlow={focusTrailEdges}
2741
+ onClearFocus={clearTrailFocus}
2742
+ onFocusStep={focusTrailStep}
2746
2743
  onHoverStep={(tl, i) =>
2747
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i })
2744
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: i })
2748
2745
  }
2749
2746
  onHoverFlow={(tl) =>
2750
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null })
2747
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: null })
2751
2748
  }
2752
- onLeaveStep={() => setHoveredWalkthroughStep(null)}
2753
- onReorder={onReorderWalkthroughs}
2749
+ onLeaveStep={() => setHoveredTrailStep(null)}
2750
+ onReorder={onReorderTrails}
2754
2751
  proposedAliases={proposedAliases}
2755
2752
  />
2756
2753
  ) : treeFilePaths.length > 0 ? (
@@ -2799,7 +2796,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2799
2796
  )}
2800
2797
 
2801
2798
  {/* Drag handle between sidebar and canvas — resize the left panel. */}
2802
- {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
2799
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2803
2800
  <div
2804
2801
  onMouseDown={onSidebarResizeStart}
2805
2802
  aria-label="Resize sidebar"
@@ -2980,9 +2977,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2980
2977
  >
2981
2978
  <GraphChrome />
2982
2979
  </ReactFlow>
2983
- {/* Graph / walkthrough / step titles — non-interactive chips at the top
2980
+ {/* Graph / trail / step titles — non-interactive chips at the top
2984
2981
  of the canvas. Graph-only embeds use these without opening the sidebar. */}
2985
- {(graphTitle || overlayWalkthroughTitle || overlayWalkthroughStep) && (
2982
+ {(graphTitle || overlayTrailTitle || overlayTrailStep) && (
2986
2983
  <div
2987
2984
  style={{
2988
2985
  position: 'absolute',
@@ -3019,11 +3016,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3019
3016
  {graphTitle}
3020
3017
  </div>
3021
3018
  )}
3022
- {overlayWalkthroughTitle && (
3019
+ {overlayTrailTitle && (
3023
3020
  <div
3024
3021
  style={{
3025
3022
  maxWidth: '100%',
3026
- minWidth: overlayWalkthroughStep ? 160 : undefined,
3023
+ minWidth: overlayTrailStep ? 160 : undefined,
3027
3024
  display: 'flex',
3028
3025
  flexDirection: 'column',
3029
3026
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -3034,9 +3031,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3034
3031
  opacity: 0.95,
3035
3032
  }}
3036
3033
  aria-label={
3037
- overlayWalkthroughStep
3038
- ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
3039
- : overlayWalkthroughTitle
3034
+ overlayTrailStep
3035
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
3036
+ : overlayTrailTitle
3040
3037
  }
3041
3038
  >
3042
3039
  <div
@@ -3052,9 +3049,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3052
3049
  textAlign: 'center',
3053
3050
  }}
3054
3051
  >
3055
- {overlayWalkthroughTitle}
3052
+ {overlayTrailTitle}
3056
3053
  </div>
3057
- {overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (
3054
+ {overlayTrailStep && overlayTrailStep.total > 0 && (
3058
3055
  <div
3059
3056
  style={{
3060
3057
  display: 'flex',
@@ -3063,10 +3060,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3063
3060
  }}
3064
3061
  aria-hidden="true"
3065
3062
  >
3066
- {Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
3063
+ {Array.from({ length: overlayTrailStep.total }, (_, i) => {
3067
3064
  const n = i + 1;
3068
- const active = n === overlayWalkthroughStep.index;
3069
- const done = n < overlayWalkthroughStep.index;
3065
+ const active = n === overlayTrailStep.index;
3066
+ const done = n < overlayTrailStep.index;
3070
3067
  return (
3071
3068
  <span
3072
3069
  key={n}
@@ -3089,7 +3086,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3089
3086
  )}
3090
3087
  </div>
3091
3088
  )}
3092
- {overlayWalkthroughStep?.annotation && (
3089
+ {overlayTrailStep?.annotation && (
3093
3090
  <div
3094
3091
  style={{
3095
3092
  maxWidth: '100%',
@@ -3105,7 +3102,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3105
3102
  lineHeight: 1.35,
3106
3103
  }}
3107
3104
  >
3108
- {overlayWalkthroughStep.annotation}
3105
+ {overlayTrailStep.annotation}
3109
3106
  </div>
3110
3107
  )}
3111
3108
  </div>
@@ -3167,16 +3164,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3167
3164
  suppressEscape={fileOverlayOpen}
3168
3165
  hidden={hideDrawer}
3169
3166
  >
3170
- {drawerTarget?.kind === 'walkthrough' &&
3171
- focusedWalkthrough &&
3172
- renderWalkthroughViewer ? (
3173
- <WalkthroughDrawerContent
3174
- render={renderWalkthroughDrawerContent}
3175
- walkthrough={focusedWalkthrough}
3167
+ {drawerTarget?.kind === 'trail' &&
3168
+ focusedTrail &&
3169
+ renderTrailViewer ? (
3170
+ <TrailDrawerContent
3171
+ render={renderTrailDrawerContent}
3172
+ trail={focusedTrail}
3176
3173
  stepIndex={drawerTarget.stepIndex}
3177
- onOpenFile={onOpenFileFromWalkthrough}
3174
+ onOpenFile={onOpenFileFromTrail}
3178
3175
  proposedAliases={proposedAliases}
3179
- resolveSymbol={resolveWalkthroughSymbol}
3176
+ resolveSymbol={resolveTrailSymbol}
3180
3177
  onSymbolClick={openConstructDeclaration}
3181
3178
  />
3182
3179
  ) : drawerTarget?.kind === 'file' ? (
@@ -3345,7 +3342,7 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3345
3342
 
3346
3343
  const constructsOnly = isConstructsOnlyModel({
3347
3344
  components: props.components,
3348
- walkthroughs: props.walkthroughs,
3345
+ trails: props.trails,
3349
3346
  graphifyRelations: props.graphifyRelations,
3350
3347
  });
3351
3348