@principal-ai/subsystems-react 0.44.4 → 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 (126) 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 +284 -277
  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 +1 -1
  57. package/dist/subsystem/nodes.js +1 -1
  58. package/dist/subsystem/toC4.d.ts +2 -2
  59. package/dist/subsystem/toC4.d.ts.map +1 -1
  60. package/dist/subsystem/toC4.js +2 -2
  61. package/dist/subsystem/toC4.js.map +1 -1
  62. package/dist/subsystem/trailBrief.d.ts +18 -0
  63. package/dist/subsystem/trailBrief.d.ts.map +1 -0
  64. package/dist/subsystem/{walkthroughBrief.js → trailBrief.js} +8 -8
  65. package/dist/subsystem/trailBrief.js.map +1 -0
  66. package/package.json +3 -3
  67. package/src/index.ts +15 -15
  68. package/src/pierre/{PierreWalkthroughCodeView.tsx → PierreTrailCodeView.tsx} +56 -56
  69. package/src/pierre/index.ts +2 -2
  70. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  71. package/src/stories/Subsystem/AggregateGraph/AggregateFrameGraph.stories.tsx +1 -1
  72. package/src/stories/Subsystem/AggregateGraph/aggregateViewFixture.ts +1 -1
  73. package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
  74. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +8 -8
  75. package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +13 -13
  76. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -2
  77. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +5 -5
  78. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +8 -8
  79. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
  80. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +6 -6
  81. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +12 -12
  82. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +36 -36
  83. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +7 -7
  84. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  85. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +8 -8
  86. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +4 -4
  87. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +3 -3
  88. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +5 -5
  89. package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +6 -6
  90. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  91. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +4 -4
  92. package/src/stories/Subsystem/ComponentGraph/{ProposedWalkthroughs.stories.tsx → ProposedTrails.stories.tsx} +9 -9
  93. package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +19 -19
  94. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +21 -21
  95. package/src/stories/Subsystem/ComponentGraph/{WalkthroughAutoplay.stories.tsx → TrailAutoplay.stories.tsx} +22 -22
  96. package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +3 -3
  97. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +19 -19
  98. package/src/subsystem/C4Graph.tsx +2 -2
  99. package/src/subsystem/ConstructsCatalog.tsx +1 -1
  100. package/src/subsystem/DiagnosticToggle.tsx +1 -1
  101. package/src/subsystem/FileDrawer.tsx +2 -2
  102. package/src/subsystem/IssueList.focus.test.tsx +4 -4
  103. package/src/subsystem/IssueList.icon.test.tsx +3 -3
  104. package/src/subsystem/IssueList.tsx +14 -14
  105. package/src/subsystem/SubsystemAggregateGraph.tsx +3 -3
  106. package/src/subsystem/SubsystemComponentGraph.tsx +377 -371
  107. package/src/subsystem/SubsystemModelTransition.tsx +61 -61
  108. package/src/subsystem/{WalkthroughsPanel.test.tsx → TrailsPanel.test.tsx} +16 -16
  109. package/src/subsystem/{WalkthroughsPanel.tsx → TrailsPanel.tsx} +81 -81
  110. package/src/subsystem/declarationRef.ts +1 -1
  111. package/src/subsystem/formatDeclaration.test.ts +6 -6
  112. package/src/subsystem/model.test.ts +25 -25
  113. package/src/subsystem/model.ts +51 -51
  114. package/src/subsystem/nodes.tsx +1 -1
  115. package/src/subsystem/toC4.test.ts +1 -1
  116. package/src/subsystem/toC4.ts +3 -3
  117. package/src/subsystem/{walkthroughBrief.test.ts → trailBrief.test.ts} +10 -10
  118. package/src/subsystem/{walkthroughBrief.ts → trailBrief.ts} +8 -8
  119. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +0 -1
  120. package/dist/pierre/PierreWalkthroughCodeView.js.map +0 -1
  121. package/dist/subsystem/WalkthroughsPanel.d.ts +0 -55
  122. package/dist/subsystem/WalkthroughsPanel.d.ts.map +0 -1
  123. package/dist/subsystem/WalkthroughsPanel.js.map +0 -1
  124. package/dist/subsystem/walkthroughBrief.d.ts +0 -18
  125. package/dist/subsystem/walkthroughBrief.d.ts.map +0 -1
  126. 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,7 +63,7 @@ import {
63
63
  MaintainLivePanel,
64
64
  type MaintainLivePanelProps,
65
65
  } from '../components/maintain-events/MaintainLivePanel';
66
- import type { WalkthroughSymbolQuery } from '../pierre/PierreWalkthroughCodeView';
66
+ import type { TrailSymbolQuery } from '../pierre/PierreTrailCodeView';
67
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 {
@@ -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
@@ -1489,7 +1495,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1489
1495
  };
1490
1496
  // Edges outside the selected view are hidden entirely (labels included).
1491
1497
  // graphify-native edges belong to the graphify view (they are static
1492
- // topology, not runtime hops) regardless of their verb.
1498
+ // topology, not runtime steps) regardless of their verb.
1493
1499
  const edgeInView = (e: Edge): boolean => {
1494
1500
  const d = e.data as
1495
1501
  | { mechanism?: string; provenance?: SubsystemEdgeProvenance }
@@ -1498,7 +1504,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1498
1504
  if (resolvedEdgeView === 'graphify') {
1499
1505
  return d?.provenance === 'graphify';
1500
1506
  }
1501
- return isWalkthroughMechanism(mechanism);
1507
+ return isTrailMechanism(mechanism);
1502
1508
  };
1503
1509
  if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
1504
1510
  return baseEdges.map((e) => {
@@ -1565,7 +1571,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1565
1571
  (_e, node: Node) => {
1566
1572
  const comp = (node.data as { component?: SubsystemComponent } | undefined)?.component;
1567
1573
  setSelectedEdgeId(null);
1568
- setFocusedWalkthroughId(null);
1574
+ setFocusedTrailId(null);
1569
1575
  setFocusedStepIndex(null);
1570
1576
  if (node.type === 'subsystem-component' && comp) {
1571
1577
  // Clicking the already-selected node unselects it (toggle off).
@@ -1593,7 +1599,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1593
1599
  const onPaneClick = useCallback(() => {
1594
1600
  setSelected(null);
1595
1601
  setSelectedEdgeId(null);
1596
- setFocusedWalkthroughId(null);
1602
+ setFocusedTrailId(null);
1597
1603
  setFocusedStepIndex(null);
1598
1604
  issueModuleFocusRef.current = null;
1599
1605
  }, []);
@@ -1601,8 +1607,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1601
1607
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
1602
1608
  // own owner-avatar header. Clicking a header collapses that repo's tree.
1603
1609
  const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
1604
- const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
1605
- // 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
1606
1612
  // (and steps) that touch one.
1607
1613
  const proposedAliases = useMemo(
1608
1614
  () => new Set(components.filter((c) => c.proposed).map((c) => c.alias)),
@@ -1679,7 +1685,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1679
1685
  const onNodeDoubleClick: NodeMouseHandler = useCallback(
1680
1686
  (_e, node: Node) => {
1681
1687
  // Boundary frame → frame its members on the canvas; double-click again to
1682
- // 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.
1683
1689
  if (node.type === 'subsystem-group') {
1684
1690
  const key =
1685
1691
  (node.data as { region?: { key?: string } } | undefined)?.region?.key;
@@ -1715,7 +1721,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1715
1721
  [onOpenDeclarationFile, fitView, focusedBoundaryKey],
1716
1722
  );
1717
1723
 
1718
- const onOpenFileFromWalkthrough = useCallback(
1724
+ const onOpenFileFromTrail = useCallback(
1719
1725
  (file: string, opts?: SubsystemOpenFileOptions) => {
1720
1726
  setFileOverlay({ file, startLine: opts?.startLine });
1721
1727
  onFileSelect?.(file);
@@ -1737,15 +1743,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1737
1743
  const fileOverlayOpen = fileOverlay != null;
1738
1744
  const collapseCanvas = drawerFillHeight || fileOverlayOpen;
1739
1745
 
1740
- // Drop the full-file overlay when the underlying walkthrough drawer changes
1746
+ // Drop the full-file overlay when the underlying trail drawer changes
1741
1747
  // or closes — open-file keeps the same drawerTarget so the snippets stay mounted.
1742
- const walkthroughDrawerKey =
1743
- drawerTarget?.kind === 'walkthrough'
1744
- ? `${drawerTarget.walkthroughId}:${drawerTarget.stepIndex}`
1748
+ const trailDrawerKey =
1749
+ drawerTarget?.kind === 'trail'
1750
+ ? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
1745
1751
  : null;
1746
1752
  useEffect(() => {
1747
1753
  setFileOverlay(null);
1748
- }, [walkthroughDrawerKey]);
1754
+ }, [trailDrawerKey]);
1749
1755
 
1750
1756
  useEffect(() => {
1751
1757
  if (!fileOverlayOpen) return;
@@ -1756,16 +1762,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1756
1762
  return () => window.removeEventListener('keydown', onKey);
1757
1763
  }, [fileOverlayOpen, closeFileOverlay]);
1758
1764
 
1759
- // Camera helper shared by the walkthrough interactions. Fits the union of
1765
+ // Camera helper shared by the trail interactions. Fits the union of
1760
1766
  // the focused edges' endpoint rects AND their routed waypoints — the edge
1761
1767
  // carries its full polyline (`elkPathPoints`, absolute flow coords), so the
1762
- // 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
1763
1769
  // bulges out around intervening nodes from being clipped at the viewport
1764
1770
  // edge, and it is the edge's own geometry doing the guiding, not fudge
1765
1771
  // padding.
1766
1772
  const fitFocusBounds = useCallback(
1767
1773
  (ids: ReadonlySet<string>) => {
1768
- if (!zoomOnWalkthroughFocus) return;
1774
+ if (!zoomOnTrailFocus) return;
1769
1775
  const nodeIds = new Set<string>();
1770
1776
  const points: { x: number; y: number }[] = [];
1771
1777
  for (const e of baseEdges) {
@@ -1810,15 +1816,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1810
1816
  };
1811
1817
  fitBounds(bounds, {
1812
1818
  padding: 0.15,
1813
- duration: walkthroughFocusDurationMs,
1819
+ duration: trailFocusDurationMs,
1814
1820
  });
1815
1821
  },
1816
1822
  [
1817
1823
  baseEdges,
1818
1824
  built.absoluteRects,
1819
1825
  fitBounds,
1820
- zoomOnWalkthroughFocus,
1821
- walkthroughFocusDurationMs,
1826
+ zoomOnTrailFocus,
1827
+ trailFocusDurationMs,
1822
1828
  ],
1823
1829
  );
1824
1830
 
@@ -1876,16 +1882,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1876
1882
  width: Math.max(1, maxX - minX),
1877
1883
  height: Math.max(1, maxY - minY),
1878
1884
  },
1879
- { padding, duration: walkthroughFocusDurationMs },
1885
+ { padding, duration: trailFocusDurationMs },
1880
1886
  );
1881
1887
  },
1882
- [rectForNode, fitBounds, walkthroughFocusDurationMs],
1888
+ [rectForNode, fitBounds, trailFocusDurationMs],
1883
1889
  );
1884
1890
 
1885
- // 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
1886
1892
  // closes (visibility restores non-flow nodes that were hidden).
1887
1893
  const fitOverview = useCallback(() => {
1888
- if (!zoomOnWalkthroughFocus) return;
1894
+ if (!zoomOnTrailFocus) return;
1889
1895
  fitView({
1890
1896
  padding: 0.1,
1891
1897
  includeHiddenNodes: false,
@@ -1893,115 +1899,115 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1893
1899
  maxZoom: 2,
1894
1900
  duration: 300,
1895
1901
  });
1896
- }, [fitView, zoomOnWalkthroughFocus]);
1902
+ }, [fitView, zoomOnTrailFocus]);
1897
1903
 
1898
1904
  // Focus an entire flow: hide everything but the flow's nodes and edges, and
1899
1905
  // frame the flow on the canvas. Selection state is cleared — the graph now
1900
1906
  // reads as the narrative. No drawer: the code view only opens on a step
1901
- // 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)
1902
1908
  // closes; an explicitly opened file drawer stays.
1903
1909
  // In `dim` mode: clear step highlight and leave the full graph visible
1904
1910
  // (no zoom / hide) — matching "step away from a hovered step".
1905
- const focusWalkthroughEdges = useCallback(
1906
- (tl: SubsystemWalkthrough) => {
1911
+ const focusTrailEdges = useCallback(
1912
+ (tl: SubsystemTrail) => {
1907
1913
  setSelected(null);
1908
1914
  setSelectedEdgeId(null);
1909
- setHoveredWalkthroughStep(null);
1910
- if (walkthroughStepMode === 'dim') {
1915
+ setHoveredTrailStep(null);
1916
+ if (trailStepMode === 'dim') {
1911
1917
  setFocusedStepIndex(null);
1912
- setFocusedWalkthroughId(null);
1913
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1918
+ setFocusedTrailId(null);
1919
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1914
1920
  return;
1915
1921
  }
1916
1922
  setFocusedStepIndex(null);
1917
- setFocusedWalkthroughId(tl.id);
1918
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1919
- 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));
1920
1926
  },
1921
- [fitFocusBounds, walkthroughStepMode],
1927
+ [fitFocusBounds, trailStepMode],
1922
1928
  );
1923
1929
 
1924
- const clearWalkthroughFocus = useCallback(() => {
1925
- setFocusedWalkthroughId(null);
1930
+ const clearTrailFocus = useCallback(() => {
1931
+ setFocusedTrailId(null);
1926
1932
  setFocusedStepIndex(null);
1927
- setHoveredWalkthroughStep(null);
1928
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1933
+ setHoveredTrailStep(null);
1934
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1929
1935
  }, []);
1930
1936
 
1931
- // 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
1932
1938
  // flow — expand its steps, focus its edges, and switch the sidebar to
1933
- // 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
1934
1940
  // back; a remount (or a new id) re-applies it.
1935
- const appliedInitialWalkthroughRef = useRef<string | null>(null);
1941
+ const appliedInitialTrailRef = useRef<string | null>(null);
1936
1942
  useEffect(() => {
1937
- if (!initialWalkthroughId) return;
1938
- if (appliedInitialWalkthroughRef.current === initialWalkthroughId) return;
1939
- if (!layoutReady || !walkthroughs?.length) return;
1940
- 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);
1941
1947
  if (!tl) return;
1942
- appliedInitialWalkthroughRef.current = initialWalkthroughId;
1943
- setSidebarView('walkthroughs');
1944
- setExpandedWalkthroughs((prev) => new Set(prev).add(tl.id));
1945
- focusWalkthroughEdges(tl);
1946
- }, [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]);
1947
1953
 
1948
1954
  // Switching sidebar panels also switches the edge vocabulary. Leaving the
1949
- // Walkthroughs panel drops its canvas state (focus, expanded flows, selected
1955
+ // Trails panel drops its canvas state (focus, expanded flows, selected
1950
1956
  // edge) so the Files view's topology edges aren't gated by a flow the user
1951
1957
  // can no longer see or un-dim.
1952
1958
  const changeSidebarView = useCallback(
1953
- (view: 'files' | 'walkthroughs') => {
1959
+ (view: 'files' | 'trails') => {
1954
1960
  setSidebarView(view);
1955
- if (view === 'walkthroughs') return;
1956
- clearWalkthroughFocus();
1957
- setExpandedWalkthroughs(new Set());
1961
+ if (view === 'trails') return;
1962
+ clearTrailFocus();
1963
+ setExpandedTrails(new Set());
1958
1964
  setSelectedEdgeId(null);
1959
1965
  },
1960
- [clearWalkthroughFocus],
1966
+ [clearTrailFocus],
1961
1967
  );
1962
1968
 
1963
- // 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
1964
1970
  // drawer to that step's snippet. Clicking the already-focused step clears
1965
1971
  // step focus and returns to whole-flow (high-level) framing.
1966
1972
  // In `dim` mode: only dim non-participants (same as hovering a step) —
1967
1973
  // no camera move, no drawer, no hide.
1968
- const focusWalkthroughStep = useCallback(
1969
- (tl: SubsystemWalkthrough, stepIndex: number) => {
1974
+ const focusTrailStep = useCallback(
1975
+ (tl: SubsystemTrail, stepIndex: number) => {
1970
1976
  const step = tl.steps[stepIndex];
1971
1977
  if (!step) return;
1972
1978
  setSelected(null);
1973
1979
  setSelectedEdgeId(null);
1974
- if (walkthroughStepMode === 'dim') {
1980
+ if (trailStepMode === 'dim') {
1975
1981
  setFocusedStepIndex(null);
1976
- setFocusedWalkthroughId(null);
1977
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1978
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1982
+ setFocusedTrailId(null);
1983
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1984
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1979
1985
  return;
1980
1986
  }
1981
1987
  // Toggle: clicking the selected step unselects it (back to whole flow).
1982
- if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
1988
+ if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
1983
1989
  setFocusedStepIndex(null);
1984
1990
  // Pointer is still over the row — keep hover preview so dimming doesn't
1985
1991
  // flash off until mouseleave.
1986
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1987
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1992
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1993
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1988
1994
  if (pendingFocusFitRef.current != null) {
1989
1995
  window.clearTimeout(pendingFocusFitRef.current);
1990
1996
  pendingFocusFitRef.current = null;
1991
1997
  }
1992
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1998
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1993
1999
  return;
1994
2000
  }
1995
2001
  setFocusedStepIndex(stepIndex);
1996
- setFocusedWalkthroughId(tl.id);
1997
- setHoveredWalkthroughStep(null);
2002
+ setFocusedTrailId(tl.id);
2003
+ setHoveredTrailStep(null);
1998
2004
  // Open the drawer before fitting. If it was closed, wait for its height
1999
2005
  // transition so fitView uses the reduced canvas — not full height.
2000
2006
  const drawerWasOpen = drawerOpenRef.current;
2001
- if (renderWalkthroughViewer) {
2007
+ if (renderTrailViewer) {
2002
2008
  setDrawerTarget({
2003
- kind: 'walkthrough',
2004
- walkthroughId: tl.id,
2009
+ kind: 'trail',
2010
+ trailId: tl.id,
2005
2011
  stepIndex,
2006
2012
  });
2007
2013
  } else {
@@ -2011,7 +2017,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2011
2017
  startLine: step.line,
2012
2018
  });
2013
2019
  }
2014
- const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
2020
+ const edgeIds = new Set([trailStepGraphEdgeId(step)]);
2015
2021
  if (pendingFocusFitRef.current != null) {
2016
2022
  window.clearTimeout(pendingFocusFitRef.current);
2017
2023
  pendingFocusFitRef.current = null;
@@ -2028,9 +2034,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2028
2034
  [
2029
2035
  fitFocusBounds,
2030
2036
  focusedStepIndex,
2031
- focusedWalkthroughId,
2032
- renderWalkthroughViewer,
2033
- walkthroughStepMode,
2037
+ focusedTrailId,
2038
+ renderTrailViewer,
2039
+ trailStepMode,
2034
2040
  ],
2035
2041
  );
2036
2042
 
@@ -2052,10 +2058,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2052
2058
  const prev = prevPreviewEdgeIdsRef.current;
2053
2059
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
2054
2060
 
2055
- if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim') return;
2061
+ if (!zoomOnTrailFocus || trailStepMode === 'dim') return;
2056
2062
  // Camera follows hover only when a specific step is already focused (or
2057
2063
  // autoplay is driving an auto-focus).
2058
- if (!autoPlayFocus && (focusedWalkthroughId == null || focusedStepIndex == null)) {
2064
+ if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
2059
2065
  return;
2060
2066
  }
2061
2067
 
@@ -2069,26 +2075,26 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2069
2075
  }, [
2070
2076
  previewEdgeIds,
2071
2077
  focusEdgeIds,
2072
- focusedWalkthroughId,
2078
+ focusedTrailId,
2073
2079
  focusedStepIndex,
2074
2080
  autoPlayFocus,
2075
2081
  fitFocusBounds,
2076
- zoomOnWalkthroughFocus,
2077
- walkthroughStepMode,
2082
+ zoomOnTrailFocus,
2083
+ trailStepMode,
2078
2084
  ]);
2079
2085
 
2080
- // Graph-only embeds: cycle walkthrough steps. In `focus` mode each step is
2081
- // 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
2082
2088
  // viewer is supplied, its snippet drawer opens; in `dim` mode it only
2083
2089
  // dim-highlights the step (hover-style) with no camera move.
2084
2090
  useEffect(() => {
2085
- if (!autoPlayWalkthroughs || walkthroughs == null || walkthroughs.length === 0) return;
2091
+ if (!autoPlayTrails || trails == null || trails.length === 0) return;
2086
2092
  if (!layoutReady) return;
2087
- const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
2093
+ const playable = trails.filter((tl) => tl.steps.length > 0);
2088
2094
  if (playable.length === 0) return;
2089
2095
  // Only run the cycles that can actually be shown in the current edge view.
2090
2096
  const inView = playable.filter((tl) =>
2091
- tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)),
2097
+ tl.steps.some((s) => isTrailMechanism(s.mechanism)),
2092
2098
  );
2093
2099
  if (inView.length === 0) return;
2094
2100
  const cyc = inView;
@@ -2097,8 +2103,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2097
2103
  let tlIdx = 0;
2098
2104
  let stepIdx = 0;
2099
2105
  let timer: number | null = null;
2100
- const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
2101
- const focusMode = walkthroughStepMode !== 'dim';
2106
+ const interval = Math.max(400, trailAutoPlayIntervalMs);
2107
+ const focusMode = trailStepMode !== 'dim';
2102
2108
  let cleanup = () => {};
2103
2109
 
2104
2110
  const tick = () => {
@@ -2107,15 +2113,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2107
2113
  setSelected(null);
2108
2114
  setSelectedEdgeId(null);
2109
2115
  if (focusMode) {
2110
- setHoveredWalkthroughStep(null);
2111
- setFocusedWalkthroughId(null);
2116
+ setHoveredTrailStep(null);
2117
+ setFocusedTrailId(null);
2112
2118
  setFocusedStepIndex(null);
2113
- setAutoPlayFocus({ walkthroughId: tl.id, stepIndex: stepIdx });
2119
+ setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
2114
2120
  if (!hideDrawer) {
2115
2121
  const step = tl.steps[stepIdx];
2116
2122
  if (step) {
2117
- if (renderWalkthroughViewer) {
2118
- setDrawerTarget({ kind: 'walkthrough', walkthroughId: tl.id, stepIndex: stepIdx });
2123
+ if (renderTrailViewer) {
2124
+ setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
2119
2125
  } else {
2120
2126
  setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
2121
2127
  }
@@ -2123,9 +2129,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2123
2129
  }
2124
2130
  } else {
2125
2131
  setAutoPlayFocus(null);
2126
- setFocusedWalkthroughId(null);
2132
+ setFocusedTrailId(null);
2127
2133
  setFocusedStepIndex(null);
2128
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
2134
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
2129
2135
  }
2130
2136
  stepIdx += 1;
2131
2137
  if (stepIdx >= tl.steps.length) {
@@ -2139,29 +2145,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2139
2145
  cleanup = () => {
2140
2146
  cancelled = true;
2141
2147
  if (timer != null) window.clearTimeout(timer);
2142
- setHoveredWalkthroughStep(null);
2143
- setFocusedWalkthroughId(null);
2148
+ setHoveredTrailStep(null);
2149
+ setFocusedTrailId(null);
2144
2150
  setFocusedStepIndex(null);
2145
2151
  setAutoPlayFocus(null);
2146
2152
  };
2147
2153
  return cleanup;
2148
2154
  }, [
2149
- autoPlayWalkthroughs,
2150
- walkthroughs,
2151
- walkthroughAutoPlayIntervalMs,
2155
+ autoPlayTrails,
2156
+ trails,
2157
+ trailAutoPlayIntervalMs,
2152
2158
  layoutReady,
2153
- walkthroughStepMode,
2154
- renderWalkthroughViewer,
2159
+ trailStepMode,
2160
+ renderTrailViewer,
2155
2161
  hideDrawer,
2156
2162
  ]);
2157
2163
 
2158
- // 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
2159
2165
  // (sidebar click or drawer open). Ignores typing targets and chords.
2160
2166
  useEffect(() => {
2161
- if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
2167
+ if (focusedTrailId == null || focusedStepIndex == null || !trails) {
2162
2168
  return;
2163
2169
  }
2164
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
2170
+ const tl = trails.find((t) => t.id === focusedTrailId);
2165
2171
  if (!tl || tl.steps.length === 0) return;
2166
2172
 
2167
2173
  const onKeyDown = (e: KeyboardEvent) => {
@@ -2187,31 +2193,31 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2187
2193
  }
2188
2194
  if (next === focusedStepIndex) return;
2189
2195
  e.preventDefault();
2190
- focusWalkthroughStep(tl, next);
2196
+ focusTrailStep(tl, next);
2191
2197
  };
2192
2198
 
2193
2199
  window.addEventListener('keydown', onKeyDown);
2194
2200
  return () => window.removeEventListener('keydown', onKeyDown);
2195
2201
  }, [
2196
- focusedWalkthroughId,
2202
+ focusedTrailId,
2197
2203
  focusedStepIndex,
2198
- walkthroughs,
2199
- focusWalkthroughStep,
2204
+ trails,
2205
+ focusTrailStep,
2200
2206
  ]);
2201
2207
 
2202
- const toggleWalkthroughCollapsed = useCallback(
2208
+ const toggleTrailCollapsed = useCallback(
2203
2209
  (tlId: string) => {
2204
2210
  const collapsingLast =
2205
- expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
2211
+ expandedTrails.has(tlId) && expandedTrails.size === 1;
2206
2212
 
2207
- setExpandedWalkthroughs((prev) => {
2213
+ setExpandedTrails((prev) => {
2208
2214
  const next = new Set(prev);
2209
2215
  if (next.has(tlId)) next.delete(tlId);
2210
2216
  else next.add(tlId);
2211
2217
  return next;
2212
2218
  });
2213
2219
 
2214
- if (!collapsingLast || !zoomOnWalkthroughFocus) return;
2220
+ if (!collapsingLast || !zoomOnTrailFocus) return;
2215
2221
 
2216
2222
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
2217
2223
  if (pendingFocusFitRef.current != null) {
@@ -2231,7 +2237,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2231
2237
  drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20,
2232
2238
  );
2233
2239
  },
2234
- [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus],
2240
+ [expandedTrails, fitOverview, zoomOnTrailFocus],
2235
2241
  );
2236
2242
 
2237
2243
  // Detail-panel links: related-name clicks select the matching component —
@@ -2249,7 +2255,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2249
2255
  if (!comp || comp.alias === selectedRef.current?.alias) return;
2250
2256
  setSelected(comp);
2251
2257
  setSelectedEdgeId(null);
2252
- setFocusedWalkthroughId(null);
2258
+ setFocusedTrailId(null);
2253
2259
  setFocusedStepIndex(null);
2254
2260
  onSelect?.(comp.alias);
2255
2261
  },
@@ -2268,9 +2274,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2268
2274
  if (comp) {
2269
2275
  setSelected(comp);
2270
2276
  setSelectedEdgeId(null);
2271
- setFocusedWalkthroughId(null);
2277
+ setFocusedTrailId(null);
2272
2278
  setFocusedStepIndex(null);
2273
- setHoveredWalkthroughStep(null);
2279
+ setHoveredTrailStep(null);
2274
2280
  issueModuleFocusRef.current = null;
2275
2281
  onSelect?.(comp.alias);
2276
2282
  requestAnimationFrame(() => {
@@ -2284,11 +2290,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2284
2290
  });
2285
2291
  } else if (step) {
2286
2292
  // A step finding focuses the step itself, through the same entry point
2287
- // the walkthrough panel uses — so the drawer, the step numbering, the
2293
+ // the trail panel uses — so the drawer, the step numbering, the
2288
2294
  // dim-mode hover preview, and the camera all behave identically whether
2289
2295
  // the step was reached from the panel or from a diagnostics card.
2290
2296
  issueModuleFocusRef.current = null;
2291
- focusWalkthroughStep(step.walkthrough, step.stepIndex);
2297
+ focusTrailStep(step.trail, step.stepIndex);
2292
2298
  } else {
2293
2299
  // Nothing selectable — but a module target names a boundary frame, so
2294
2300
  // frame that region.
@@ -2296,9 +2302,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2296
2302
  if (moduleNodeId) {
2297
2303
  setSelected(null);
2298
2304
  setSelectedEdgeId(null);
2299
- setFocusedWalkthroughId(null);
2305
+ setFocusedTrailId(null);
2300
2306
  setFocusedStepIndex(null);
2301
- setHoveredWalkthroughStep(null);
2307
+ setHoveredTrailStep(null);
2302
2308
  issueModuleFocusRef.current = moduleNodeId;
2303
2309
  requestAnimationFrame(() => {
2304
2310
  fitNodeRects(new Set([moduleNodeId]), 0.4);
@@ -2311,7 +2317,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2311
2317
  issueComponent,
2312
2318
  issueStep,
2313
2319
  issueModuleNodeId,
2314
- focusWalkthroughStep,
2320
+ focusTrailStep,
2315
2321
  onSelect,
2316
2322
  onSelectIssue,
2317
2323
  fitView,
@@ -2341,14 +2347,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2341
2347
  // not the card), and correctly leaves that alone here.
2342
2348
  const owned = focusedStepRef.current;
2343
2349
  if (
2344
- owned?.walkthroughId !== step.walkthrough.id ||
2350
+ owned?.trailId !== step.trail.id ||
2345
2351
  owned?.stepIndex !== step.stepIndex
2346
2352
  ) {
2347
2353
  return;
2348
2354
  }
2349
- setFocusedWalkthroughId(null);
2355
+ setFocusedTrailId(null);
2350
2356
  setFocusedStepIndex(null);
2351
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
2357
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
2352
2358
  } else if (moduleNodeId) {
2353
2359
  // Only unwind framing this card still owns.
2354
2360
  if (issueModuleFocusRef.current !== moduleNodeId) return;
@@ -2364,14 +2370,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2364
2370
  [issueComponent, issueStep, issueModuleNodeId, fitView],
2365
2371
  );
2366
2372
 
2367
- // 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
2368
2374
  // between its `from`/`to` components, so a token naming either of those
2369
2375
  // constructs should navigate to that construct's declaration. Index the
2370
- // matchable identifiers per step (`walkthroughId:index`).
2371
- const walkthroughSymbolIndex = useMemo(() => {
2376
+ // matchable identifiers per step (`trailId:index`).
2377
+ const trailSymbolIndex = useMemo(() => {
2372
2378
  const byAlias = new Map(components.map((c) => [c.alias, c]));
2373
2379
  const index = new Map<string, Map<string, string>>();
2374
- for (const wt of walkthroughs ?? []) {
2380
+ for (const wt of trails ?? []) {
2375
2381
  wt.steps.forEach((step, i) => {
2376
2382
  const tokens = new Map<string, string>();
2377
2383
  for (const alias of [step.from, step.to]) {
@@ -2383,19 +2389,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2383
2389
  });
2384
2390
  }
2385
2391
  return index;
2386
- }, [components, walkthroughs]);
2387
- const walkthroughSymbolIndexRef = useRef(walkthroughSymbolIndex);
2388
- walkthroughSymbolIndexRef.current = walkthroughSymbolIndex;
2389
-
2390
- // Stable resolver: reads the live index + focused walkthrough from refs so
2391
- // the memoized walkthrough drawer isn't rebuilt on every graph render.
2392
- const resolveWalkthroughSymbol = useCallback(
2393
- (query: WalkthroughSymbolQuery): string | null => {
2394
- const walkthroughId = drawerWalkthroughIdRef.current;
2395
- 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;
2396
2402
  return (
2397
- walkthroughSymbolIndexRef.current
2398
- .get(`${walkthroughId}:${query.stepIndex}`)
2403
+ trailSymbolIndexRef.current
2404
+ .get(`${trailId}:${query.stepIndex}`)
2399
2405
  ?.get(query.tokenText) ?? null
2400
2406
  );
2401
2407
  },
@@ -2483,11 +2489,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2483
2489
  [],
2484
2490
  );
2485
2491
 
2486
- const walkthroughViewerRef = useRef(renderWalkthroughViewer);
2487
- walkthroughViewerRef.current = renderWalkthroughViewer;
2488
- const renderWalkthroughDrawerContent = useCallback(
2489
- (ctx: WalkthroughViewerContext) =>
2490
- 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,
2491
2497
  [],
2492
2498
  );
2493
2499
 
@@ -2496,7 +2502,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2496
2502
  {/* Sidebar: scrollable title/description on top, files or flows pinned below.
2497
2503
  The description hides by default so files/flows get the room; the
2498
2504
  title-row toggle reveals it, and the lower panel yields back to 50%. */}
2499
- {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
2505
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2500
2506
  <div
2501
2507
  style={{
2502
2508
  width: sidebarWidth,
@@ -2603,7 +2609,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2603
2609
  )}
2604
2610
  {sidebarAfterDescription}
2605
2611
  </div>
2606
- {(treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (
2612
+ {(treeFilePaths.length > 0 || hasTrails || issuesActive) && (
2607
2613
  <div
2608
2614
  style={{
2609
2615
  ...(showDesc ? { height: '50%' as const } : { flex: 1, minHeight: 0 }),
@@ -2682,7 +2688,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2682
2688
  )
2683
2689
  ) : (
2684
2690
  <>
2685
- {hasWalkthroughs && (
2691
+ {hasTrails && (
2686
2692
  <div
2687
2693
  role="tablist"
2688
2694
  aria-label="Sidebar view"
@@ -2694,7 +2700,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2694
2700
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2695
2701
  }}
2696
2702
  >
2697
- {(['walkthroughs', 'files'] as const).map((view) => (
2703
+ {(['trails', 'files'] as const).map((view) => (
2698
2704
  <button
2699
2705
  key={view}
2700
2706
  type="button"
@@ -2718,30 +2724,30 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2718
2724
  cursor: 'pointer',
2719
2725
  }}
2720
2726
  >
2721
- {view === 'walkthroughs' ? 'Walkthroughs' : 'Files'}
2727
+ {view === 'trails' ? 'Trails' : 'Files'}
2722
2728
  </button>
2723
2729
  ))}
2724
2730
  </div>
2725
2731
  )}
2726
- {sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (
2727
- <WalkthroughsPanel
2728
- walkthroughs={walkthroughs}
2729
- expandedWalkthroughs={expandedWalkthroughs}
2730
- focusedWalkthroughId={focusedWalkthroughId}
2732
+ {sidebarView === 'trails' && trails && trails.length > 0 ? (
2733
+ <TrailsPanel
2734
+ trails={trails}
2735
+ expandedTrails={expandedTrails}
2736
+ focusedTrailId={focusedTrailId}
2731
2737
  focusedStepIndex={focusedStepIndex}
2732
- hoveredWalkthroughStep={hoveredWalkthroughStep}
2733
- onToggleCollapsed={toggleWalkthroughCollapsed}
2734
- onFocusFlow={focusWalkthroughEdges}
2735
- onClearFocus={clearWalkthroughFocus}
2736
- onFocusStep={focusWalkthroughStep}
2738
+ hoveredTrailStep={hoveredTrailStep}
2739
+ onToggleCollapsed={toggleTrailCollapsed}
2740
+ onFocusFlow={focusTrailEdges}
2741
+ onClearFocus={clearTrailFocus}
2742
+ onFocusStep={focusTrailStep}
2737
2743
  onHoverStep={(tl, i) =>
2738
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i })
2744
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: i })
2739
2745
  }
2740
2746
  onHoverFlow={(tl) =>
2741
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null })
2747
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: null })
2742
2748
  }
2743
- onLeaveStep={() => setHoveredWalkthroughStep(null)}
2744
- onReorder={onReorderWalkthroughs}
2749
+ onLeaveStep={() => setHoveredTrailStep(null)}
2750
+ onReorder={onReorderTrails}
2745
2751
  proposedAliases={proposedAliases}
2746
2752
  />
2747
2753
  ) : treeFilePaths.length > 0 ? (
@@ -2790,7 +2796,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2790
2796
  )}
2791
2797
 
2792
2798
  {/* Drag handle between sidebar and canvas — resize the left panel. */}
2793
- {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
2799
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2794
2800
  <div
2795
2801
  onMouseDown={onSidebarResizeStart}
2796
2802
  aria-label="Resize sidebar"
@@ -2971,9 +2977,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2971
2977
  >
2972
2978
  <GraphChrome />
2973
2979
  </ReactFlow>
2974
- {/* Graph / walkthrough / step titles — non-interactive chips at the top
2980
+ {/* Graph / trail / step titles — non-interactive chips at the top
2975
2981
  of the canvas. Graph-only embeds use these without opening the sidebar. */}
2976
- {(graphTitle || overlayWalkthroughTitle || overlayWalkthroughStep) && (
2982
+ {(graphTitle || overlayTrailTitle || overlayTrailStep) && (
2977
2983
  <div
2978
2984
  style={{
2979
2985
  position: 'absolute',
@@ -3010,11 +3016,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3010
3016
  {graphTitle}
3011
3017
  </div>
3012
3018
  )}
3013
- {overlayWalkthroughTitle && (
3019
+ {overlayTrailTitle && (
3014
3020
  <div
3015
3021
  style={{
3016
3022
  maxWidth: '100%',
3017
- minWidth: overlayWalkthroughStep ? 160 : undefined,
3023
+ minWidth: overlayTrailStep ? 160 : undefined,
3018
3024
  display: 'flex',
3019
3025
  flexDirection: 'column',
3020
3026
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -3025,9 +3031,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3025
3031
  opacity: 0.95,
3026
3032
  }}
3027
3033
  aria-label={
3028
- overlayWalkthroughStep
3029
- ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
3030
- : overlayWalkthroughTitle
3034
+ overlayTrailStep
3035
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
3036
+ : overlayTrailTitle
3031
3037
  }
3032
3038
  >
3033
3039
  <div
@@ -3043,9 +3049,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3043
3049
  textAlign: 'center',
3044
3050
  }}
3045
3051
  >
3046
- {overlayWalkthroughTitle}
3052
+ {overlayTrailTitle}
3047
3053
  </div>
3048
- {overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (
3054
+ {overlayTrailStep && overlayTrailStep.total > 0 && (
3049
3055
  <div
3050
3056
  style={{
3051
3057
  display: 'flex',
@@ -3054,10 +3060,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3054
3060
  }}
3055
3061
  aria-hidden="true"
3056
3062
  >
3057
- {Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
3063
+ {Array.from({ length: overlayTrailStep.total }, (_, i) => {
3058
3064
  const n = i + 1;
3059
- const active = n === overlayWalkthroughStep.index;
3060
- const done = n < overlayWalkthroughStep.index;
3065
+ const active = n === overlayTrailStep.index;
3066
+ const done = n < overlayTrailStep.index;
3061
3067
  return (
3062
3068
  <span
3063
3069
  key={n}
@@ -3080,7 +3086,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3080
3086
  )}
3081
3087
  </div>
3082
3088
  )}
3083
- {overlayWalkthroughStep?.annotation && (
3089
+ {overlayTrailStep?.annotation && (
3084
3090
  <div
3085
3091
  style={{
3086
3092
  maxWidth: '100%',
@@ -3096,7 +3102,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3096
3102
  lineHeight: 1.35,
3097
3103
  }}
3098
3104
  >
3099
- {overlayWalkthroughStep.annotation}
3105
+ {overlayTrailStep.annotation}
3100
3106
  </div>
3101
3107
  )}
3102
3108
  </div>
@@ -3158,16 +3164,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3158
3164
  suppressEscape={fileOverlayOpen}
3159
3165
  hidden={hideDrawer}
3160
3166
  >
3161
- {drawerTarget?.kind === 'walkthrough' &&
3162
- focusedWalkthrough &&
3163
- renderWalkthroughViewer ? (
3164
- <WalkthroughDrawerContent
3165
- render={renderWalkthroughDrawerContent}
3166
- walkthrough={focusedWalkthrough}
3167
+ {drawerTarget?.kind === 'trail' &&
3168
+ focusedTrail &&
3169
+ renderTrailViewer ? (
3170
+ <TrailDrawerContent
3171
+ render={renderTrailDrawerContent}
3172
+ trail={focusedTrail}
3167
3173
  stepIndex={drawerTarget.stepIndex}
3168
- onOpenFile={onOpenFileFromWalkthrough}
3174
+ onOpenFile={onOpenFileFromTrail}
3169
3175
  proposedAliases={proposedAliases}
3170
- resolveSymbol={resolveWalkthroughSymbol}
3176
+ resolveSymbol={resolveTrailSymbol}
3171
3177
  onSymbolClick={openConstructDeclaration}
3172
3178
  />
3173
3179
  ) : drawerTarget?.kind === 'file' ? (
@@ -3336,7 +3342,7 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3336
3342
 
3337
3343
  const constructsOnly = isConstructsOnlyModel({
3338
3344
  components: props.components,
3339
- walkthroughs: props.walkthroughs,
3345
+ trails: props.trails,
3340
3346
  graphifyRelations: props.graphifyRelations,
3341
3347
  });
3342
3348