@principal-ai/subsystems-react 0.44.4 → 0.46.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 (128) 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 +7 -11
  20. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  21. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +10 -26
  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 +2 -3
  26. package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
  27. package/dist/subsystem/ConstructsCatalog.js +3 -14
  28. package/dist/subsystem/ConstructsCatalog.js.map +1 -1
  29. package/dist/subsystem/DiagnosticToggle.d.ts +1 -1
  30. package/dist/subsystem/DiagnosticToggle.js +1 -1
  31. package/dist/subsystem/FileDrawer.d.ts +2 -2
  32. package/dist/subsystem/FileDrawer.js +2 -2
  33. package/dist/subsystem/IssueList.d.ts +6 -6
  34. package/dist/subsystem/IssueList.d.ts.map +1 -1
  35. package/dist/subsystem/IssueList.js +9 -9
  36. package/dist/subsystem/IssueList.js.map +1 -1
  37. package/dist/subsystem/SubsystemAggregateGraph.d.ts +3 -3
  38. package/dist/subsystem/SubsystemAggregateGraph.d.ts.map +1 -1
  39. package/dist/subsystem/SubsystemAggregateGraph.js +2 -2
  40. package/dist/subsystem/SubsystemComponentGraph.d.ts +45 -51
  41. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  42. package/dist/subsystem/SubsystemComponentGraph.js +288 -292
  43. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  44. package/dist/subsystem/SubsystemModelTransition.d.ts +10 -10
  45. package/dist/subsystem/SubsystemModelTransition.d.ts.map +1 -1
  46. package/dist/subsystem/SubsystemModelTransition.js +42 -42
  47. package/dist/subsystem/SubsystemModelTransition.js.map +1 -1
  48. package/dist/subsystem/TrailsPanel.d.ts +55 -0
  49. package/dist/subsystem/TrailsPanel.d.ts.map +1 -0
  50. package/dist/subsystem/{WalkthroughsPanel.js → TrailsPanel.js} +51 -51
  51. package/dist/subsystem/TrailsPanel.js.map +1 -0
  52. package/dist/subsystem/declarationRef.d.ts +1 -1
  53. package/dist/subsystem/declarationRef.d.ts.map +1 -1
  54. package/dist/subsystem/model.d.ts +40 -40
  55. package/dist/subsystem/model.d.ts.map +1 -1
  56. package/dist/subsystem/model.js +17 -17
  57. package/dist/subsystem/model.js.map +1 -1
  58. package/dist/subsystem/nodes.d.ts +1 -1
  59. package/dist/subsystem/nodes.js +1 -1
  60. package/dist/subsystem/toC4.d.ts +2 -2
  61. package/dist/subsystem/toC4.d.ts.map +1 -1
  62. package/dist/subsystem/toC4.js +2 -2
  63. package/dist/subsystem/toC4.js.map +1 -1
  64. package/dist/subsystem/trailBrief.d.ts +18 -0
  65. package/dist/subsystem/trailBrief.d.ts.map +1 -0
  66. package/dist/subsystem/{walkthroughBrief.js → trailBrief.js} +8 -8
  67. package/dist/subsystem/trailBrief.js.map +1 -0
  68. package/package.json +3 -3
  69. package/src/index.ts +15 -15
  70. package/src/pierre/{PierreWalkthroughCodeView.tsx → PierreTrailCodeView.tsx} +56 -56
  71. package/src/pierre/index.ts +2 -2
  72. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  73. package/src/stories/Subsystem/AggregateGraph/AggregateFrameGraph.stories.tsx +1 -1
  74. package/src/stories/Subsystem/AggregateGraph/aggregateViewFixture.ts +1 -1
  75. package/src/stories/Subsystem/C4Graph/c4Fixture.ts +1 -1
  76. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +8 -8
  77. package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +13 -13
  78. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +2 -2
  79. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +5 -5
  80. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +8 -8
  81. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +3 -3
  82. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +6 -6
  83. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +12 -12
  84. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +36 -36
  85. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +7 -7
  86. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  87. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +8 -8
  88. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +4 -4
  89. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +3 -3
  90. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +5 -5
  91. package/src/stories/Subsystem/ComponentGraph/Packages.stories.tsx +6 -6
  92. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  93. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +4 -4
  94. package/src/stories/Subsystem/ComponentGraph/{ProposedWalkthroughs.stories.tsx → ProposedTrails.stories.tsx} +9 -9
  95. package/src/stories/Subsystem/ComponentGraph/Reorder.stories.tsx +19 -19
  96. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +21 -21
  97. package/src/stories/Subsystem/ComponentGraph/{WalkthroughAutoplay.stories.tsx → TrailAutoplay.stories.tsx} +22 -22
  98. package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +3 -3
  99. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +17 -37
  100. package/src/subsystem/C4Graph.tsx +2 -2
  101. package/src/subsystem/ConstructsCatalog.tsx +2 -16
  102. package/src/subsystem/DiagnosticToggle.tsx +1 -1
  103. package/src/subsystem/FileDrawer.tsx +2 -2
  104. package/src/subsystem/IssueList.focus.test.tsx +4 -4
  105. package/src/subsystem/IssueList.icon.test.tsx +3 -3
  106. package/src/subsystem/IssueList.tsx +14 -14
  107. package/src/subsystem/SubsystemAggregateGraph.tsx +3 -3
  108. package/src/subsystem/SubsystemComponentGraph.tsx +380 -393
  109. package/src/subsystem/SubsystemModelTransition.tsx +61 -61
  110. package/src/subsystem/{WalkthroughsPanel.test.tsx → TrailsPanel.test.tsx} +16 -16
  111. package/src/subsystem/{WalkthroughsPanel.tsx → TrailsPanel.tsx} +81 -81
  112. package/src/subsystem/declarationRef.ts +1 -1
  113. package/src/subsystem/formatDeclaration.test.ts +6 -6
  114. package/src/subsystem/model.test.ts +25 -25
  115. package/src/subsystem/model.ts +51 -51
  116. package/src/subsystem/nodes.tsx +1 -1
  117. package/src/subsystem/toC4.test.ts +1 -1
  118. package/src/subsystem/toC4.ts +3 -3
  119. package/src/subsystem/{walkthroughBrief.test.ts → trailBrief.test.ts} +10 -10
  120. package/src/subsystem/{walkthroughBrief.ts → trailBrief.ts} +8 -8
  121. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +0 -1
  122. package/dist/pierre/PierreWalkthroughCodeView.js.map +0 -1
  123. package/dist/subsystem/WalkthroughsPanel.d.ts +0 -55
  124. package/dist/subsystem/WalkthroughsPanel.d.ts.map +0 -1
  125. package/dist/subsystem/WalkthroughsPanel.js.map +0 -1
  126. package/dist/subsystem/walkthroughBrief.d.ts +0 -18
  127. package/dist/subsystem/walkthroughBrief.d.ts.map +0 -1
  128. 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,24 +368,18 @@ 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.
381
381
  */
382
382
  hideDrawer?: boolean;
383
- /**
384
- * Legacy component-keyed variant, kept for backward compatibility. When
385
- * `renderFileViewer` is absent, drawer content resolves via the first
386
- * component whose `file` matches the opened path.
387
- */
388
- renderFileView?: (component: SubsystemComponent) => ReactNode;
389
383
  /**
390
384
  * Called when a file in the sidebar file tree is clicked (repo-root-relative
391
385
  * path). The tree is derived from the components' `file` values.
@@ -406,7 +400,7 @@ export interface SubsystemComponentGraphProps {
406
400
  ref: DeclarationSymbolRef;
407
401
  }) => Promise<SymbolInspection | null> | SymbolInspection | null;
408
402
  /**
409
- * When set, the graph's walkthrough working set — expanded flows and the
403
+ * When set, the graph's trail working set — expanded flows and the
410
404
  * selected flow/step — is persisted to `localStorage` under this key and
411
405
  * restored on mount. Hosts key it by the model id so each model remembers
412
406
  * where the user left off. Omit to keep the state purely in-memory.
@@ -489,27 +483,27 @@ function FileOverlayCloseButton({ onClose }: { onClose: () => void }) {
489
483
  );
490
484
  }
491
485
 
492
- const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({
486
+ const TrailDrawerContent = memo(function TrailDrawerContent({
493
487
  render,
494
- walkthrough,
488
+ trail,
495
489
  stepIndex,
496
490
  onOpenFile,
497
491
  proposedAliases,
498
492
  resolveSymbol,
499
493
  onSymbolClick,
500
494
  }: {
501
- render: (ctx: WalkthroughViewerContext) => ReactNode;
502
- walkthrough: SubsystemWalkthrough;
495
+ render: (ctx: TrailViewerContext) => ReactNode;
496
+ trail: SubsystemTrail;
503
497
  stepIndex: number | null;
504
498
  onOpenFile: (path: string, opts?: SubsystemOpenFileOptions) => void;
505
499
  proposedAliases: ReadonlySet<string>;
506
- resolveSymbol?: (query: WalkthroughSymbolQuery) => string | null;
507
- onSymbolClick?: (symbol: string, query: WalkthroughSymbolQuery) => void;
500
+ resolveSymbol?: (query: TrailSymbolQuery) => string | null;
501
+ onSymbolClick?: (symbol: string, query: TrailSymbolQuery) => void;
508
502
  }) {
509
503
  return (
510
504
  <>
511
505
  {render({
512
- walkthrough,
506
+ trail,
513
507
  stepIndex,
514
508
  onOpenFile,
515
509
  proposedAliases,
@@ -524,7 +518,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
524
518
  measured: { w: number; h: number } | null;
525
519
  }
526
520
 
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) {
521
+ 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, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }: InnerProps) {
528
522
  const { theme } = useTheme();
529
523
  const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
530
524
  const viewport = useViewport();
@@ -532,8 +526,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
532
526
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
533
527
  const persisted = useMemo(() => readViewState(persistKey), [persistKey]);
534
528
  const graphEdges = useMemo(
535
- () => deriveGraphEdges({ walkthroughs }),
536
- [walkthroughs],
529
+ () => deriveGraphEdges({ trails }),
530
+ [trails],
537
531
  );
538
532
  const [built, setBuilt] = useState<{
539
533
  nodes: Node[];
@@ -546,9 +540,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
546
540
  });
547
541
  const [layoutReady, setLayoutReady] = useState(false);
548
542
  const [selected, setSelected] = useState<SubsystemComponent | null>(null);
549
- /** Bottom drawer: single file or walkthrough multi-snippet mode. */
543
+ /** Bottom drawer: single file or trail multi-snippet mode. */
550
544
  const [drawerTarget, setDrawerTarget] = useState<DrawerTarget | null>(null);
551
- /** Full-file layer over an open walkthrough drawer — walkthrough stays mounted. */
545
+ /** Full-file layer over an open trail drawer — trail stays mounted. */
552
546
  const [fileOverlay, setFileOverlay] = useState<{
553
547
  file: string;
554
548
  startLine?: number;
@@ -628,52 +622,58 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
628
622
  if (raf) cancelAnimationFrame(raf);
629
623
  };
630
624
  }, [pathFrameRects, screenToFlowPosition]);
631
- // Walkthrough focus — selected flow (or step) is full strength; other
625
+ // Trail focus — selected flow (or step) is full strength; other
632
626
  // opened-flow members stay visible but dimmed; everything else is hidden.
633
627
  // Restored from the persisted view state when a `persistKey` is set.
634
- const [focusedWalkthroughId, setFocusedWalkthroughId] = useState<string | null>(
635
- persisted.focusedWalkthroughId ?? null,
628
+ const [focusedTrailId, setFocusedTrailId] = useState<string | null>(
629
+ persisted.focusedTrailId ?? null,
636
630
  );
637
631
  // `null` = whole flow focused; a number = that single step's edge focused.
638
632
  const [focusedStepIndex, setFocusedStepIndex] = useState<number | null>(
639
633
  persisted.focusedStepIndex ?? null,
640
634
  );
641
- // Hovered walkthrough in the flows panel: dims every canvas node/edge not
635
+ // Hovered trail in the flows panel: dims every canvas node/edge not
642
636
  // involved in the hover preview (or selected ∪ hovered when a step is
643
637
  // focused). `stepIndex: null` = whole flow (collapsed title hover);
644
638
  // a number = that step. Transient — no drawer. Camera only reframes when
645
639
  // a step is already selected.
646
- const [hoveredWalkthroughStep, setHoveredWalkthroughStep] = useState<{
647
- walkthroughId: string;
640
+ const [hoveredTrailStep, setHoveredTrailStep] = useState<{
641
+ trailId: string;
648
642
  stepIndex: number | null;
649
643
  } | 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
- );
644
+ // Sidebar bottom half: which panel is shown when trails exist.
645
+ // The persisted value is membership-checked rather than trusted: a stale
646
+ // localStorage entry from before the walkthrough->trail rename would match
647
+ // neither panel and render a blank sidebar with no selected tab.
648
+ const [sidebarView, setSidebarView] = useState<'files' | 'trails'>(() => {
649
+ const stored = persisted.sidebarView;
650
+ const restored: 'files' | 'trails' =
651
+ stored === 'files' || stored === 'trails' ? stored : 'trails';
652
+ return trails?.length ? restored : 'files';
653
+ });
654
654
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
655
655
  const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>(
656
656
  () => persisted.diagnosticsTab ?? 'issues',
657
657
  );
658
658
  // 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;
659
+ // Files / Trails tab picks: Files draws graphify static edges,
660
+ // Trails draws runtime step edges. Without visible tabs (no trails,
661
+ // or a sidebar-less embed) fall back to trail edges.
662
+ const sidebarTabsVisible = !hideSidebar && (trails?.length ?? 0) > 0;
663
663
  const resolvedEdgeView: SubsystemEdgeView =
664
664
  edgeView ??
665
665
  (sidebarTabsVisible
666
- ? (sidebarView === 'walkthroughs' ? 'walkthroughs' : 'graphify')
667
- : (walkthroughs?.length ?? 0) > 0
668
- ? 'walkthroughs'
666
+ ? (sidebarView === 'trails' ? 'trails' : 'graphify')
667
+ : (trails?.length ?? 0) > 0
668
+ ? 'trails'
669
669
  : (graphifyRelations?.length ?? 0) > 0
670
670
  ? 'graphify'
671
- : 'walkthroughs');
672
- // Walkthrough flows the user has expanded (via the title row). Closed by
671
+ : 'trails');
672
+ // Trail flows the user has expanded (via the title row). Closed by
673
673
  // default so a graph with several flows doesn't dump every step list at once.
674
674
  // Restored from the persisted view state when a `persistKey` is set.
675
- const [expandedWalkthroughs, setExpandedWalkthroughs] = useState<Set<string>>(
676
- () => new Set(persisted.expandedWalkthroughs ?? []),
675
+ const [expandedTrails, setExpandedTrails] = useState<Set<string>>(
676
+ () => new Set(persisted.expandedTrails ?? []),
677
677
  );
678
678
  // Sidebar description visibility. Hidden by default so the files/flows
679
679
  // panel gets the vertical room; the title-row toggle reveals it.
@@ -732,18 +732,18 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
732
732
  document.removeEventListener('mouseup', onUp);
733
733
  };
734
734
  }, [sidebarDrag, sidebarMaxWidth, fitView, persistKey]);
735
- // Persist the walkthrough working set per model. Each write merges into the
735
+ // Persist the trail working set per model. Each write merges into the
736
736
  // stored blob, so the three fields never clobber one another.
737
737
  useEffect(() => {
738
738
  if (!persistKey) return;
739
739
  writeViewState(persistKey, {
740
- expandedWalkthroughs: Array.from(expandedWalkthroughs),
740
+ expandedTrails: Array.from(expandedTrails),
741
741
  });
742
- }, [persistKey, expandedWalkthroughs]);
742
+ }, [persistKey, expandedTrails]);
743
743
  useEffect(() => {
744
744
  if (!persistKey) return;
745
- writeViewState(persistKey, { focusedWalkthroughId });
746
- }, [persistKey, focusedWalkthroughId]);
745
+ writeViewState(persistKey, { focusedTrailId });
746
+ }, [persistKey, focusedTrailId]);
747
747
  useEffect(() => {
748
748
  if (!persistKey) return;
749
749
  writeViewState(persistKey, { focusedStepIndex });
@@ -776,59 +776,59 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
776
776
  ? { file: drawerTarget.file, startLine: drawerTarget.startLine }
777
777
  : null;
778
778
 
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]);
779
+ const focusedTrail = useMemo(() => {
780
+ if (drawerTarget?.kind !== 'trail' || !trails) return null;
781
+ return trails.find((t) => t.id === drawerTarget.trailId) ?? null;
782
+ }, [drawerTarget, trails]);
783
783
  // Autoplay focus: a sidebar-less embed has no expanded flow to "open", so
784
784
  // 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 —
785
+ // autoplay sets this (flow id + step) to frame the step and dim the rest —
786
786
  // same treatment as hover — without hiding anything. Declared before the
787
787
  // overlay memos, which read it to title the canvas chip + step bar.
788
788
  const [autoPlayFocus, setAutoPlayFocus] = useState<
789
- { walkthroughId: string; stepIndex: number } | null
789
+ { trailId: string; stepIndex: number } | null
790
790
  >(null);
791
- // Ref mirror of the drawer's walkthrough id so the symbol resolver stays
791
+ // Ref mirror of the drawer's trail id so the symbol resolver stays
792
792
  // 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;
793
+ const drawerTrailIdRef = useRef<string | null>(null);
794
+ drawerTrailIdRef.current = focusedTrail?.id ?? null;
795
795
 
796
- // Walkthrough shown on the canvas title chip (focus, hover highlight, or
796
+ // Trail shown on the canvas title chip (focus, hover highlight, or
797
797
  // autoplay focus).
798
- const overlayWalkthroughTitle = useMemo(() => {
799
- if (!showWalkthroughTitle || !walkthroughs?.length) return null;
798
+ const overlayTrailTitle = useMemo(() => {
799
+ if (!showTrailTitle || !trails?.length) return null;
800
800
  const id =
801
- focusedWalkthroughId ??
802
- autoPlayFocus?.walkthroughId ??
803
- hoveredWalkthroughStep?.walkthroughId;
801
+ focusedTrailId ??
802
+ autoPlayFocus?.trailId ??
803
+ hoveredTrailStep?.trailId;
804
804
  if (!id) return null;
805
- return walkthroughs.find((t) => t.id === id)?.title ?? null;
805
+ return trails.find((t) => t.id === id)?.title ?? null;
806
806
  }, [
807
- showWalkthroughTitle,
808
- walkthroughs,
809
- focusedWalkthroughId,
807
+ showTrailTitle,
808
+ trails,
809
+ focusedTrailId,
810
810
  autoPlayFocus,
811
- hoveredWalkthroughStep,
811
+ hoveredTrailStep,
812
812
  ]);
813
813
 
814
814
  // Active step for the bottom-of-title progress + annotation chip.
815
- const overlayWalkthroughStep = useMemo(() => {
816
- if (!showWalkthroughTitle || !walkthroughs?.length) return null;
815
+ const overlayTrailStep = useMemo(() => {
816
+ if (!showTrailTitle || !trails?.length) return null;
817
817
  const tlId =
818
- focusedWalkthroughId ??
819
- autoPlayFocus?.walkthroughId ??
820
- hoveredWalkthroughStep?.walkthroughId ??
818
+ focusedTrailId ??
819
+ autoPlayFocus?.trailId ??
820
+ hoveredTrailStep?.trailId ??
821
821
  null;
822
822
  let stepIndex: number | null = null;
823
- if (focusedWalkthroughId != null) {
823
+ if (focusedTrailId != null) {
824
824
  stepIndex = focusedStepIndex;
825
825
  } else if (autoPlayFocus != null) {
826
826
  stepIndex = autoPlayFocus.stepIndex;
827
- } else if (hoveredWalkthroughStep != null) {
828
- stepIndex = hoveredWalkthroughStep.stepIndex;
827
+ } else if (hoveredTrailStep != null) {
828
+ stepIndex = hoveredTrailStep.stepIndex;
829
829
  }
830
830
  if (tlId == null || stepIndex == null) return null;
831
- const tl = walkthroughs.find((t) => t.id === tlId);
831
+ const tl = trails.find((t) => t.id === tlId);
832
832
  const step = tl?.steps[stepIndex];
833
833
  if (!step || !tl) return null;
834
834
  return {
@@ -837,25 +837,25 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
837
837
  annotation: step.annotation,
838
838
  };
839
839
  }, [
840
- showWalkthroughTitle,
841
- walkthroughs,
842
- focusedWalkthroughId,
840
+ showTrailTitle,
841
+ trails,
842
+ focusedTrailId,
843
843
  focusedStepIndex,
844
844
  autoPlayFocus,
845
- hoveredWalkthroughStep,
845
+ hoveredTrailStep,
846
846
  ]);
847
847
 
848
848
  const drawerTitle = useMemo(() => {
849
849
  if (!drawerTarget) return null;
850
850
  if (drawerTarget.kind === 'file') return drawerTarget.file;
851
- const tl = focusedWalkthrough;
851
+ const tl = focusedTrail;
852
852
  if (!tl) return null;
853
853
  if (drawerTarget.stepIndex == null) return tl.title;
854
854
  const step = tl.steps[drawerTarget.stepIndex];
855
855
  if (!step) return tl.title;
856
856
  const site = `${step.file.split('/').pop() ?? step.file}:${step.line}`;
857
857
  return `${tl.title} · ${site}`;
858
- }, [drawerTarget, focusedWalkthrough]);
858
+ }, [drawerTarget, focusedTrail]);
859
859
 
860
860
  // Refresh selected component when the components list updates (e.g. verify
861
861
  // writes back declarationRef).
@@ -869,14 +869,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
869
869
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
870
870
  // fields only — declarationRef updates after verify must not re-run ELK.
871
871
  const layoutKey = useMemo(
872
- () => subsystemGraphLayoutKey({ components, walkthroughs, graphifyRelations }),
873
- [components, walkthroughs, graphifyRelations],
872
+ () => subsystemGraphLayoutKey({ components, trails, graphifyRelations }),
873
+ [components, trails, graphifyRelations],
874
874
  );
875
875
  const componentsRef = useRef(components);
876
- const walkthroughsRef = useRef(walkthroughs);
876
+ const trailsRef = useRef(trails);
877
877
  const graphifyRelationsRef = useRef(graphifyRelations);
878
878
  componentsRef.current = components;
879
- walkthroughsRef.current = walkthroughs;
879
+ trailsRef.current = trails;
880
880
  graphifyRelationsRef.current = graphifyRelations;
881
881
 
882
882
  // Track measured dimensions from React Flow's dimension changes.
@@ -896,7 +896,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
896
896
  pass2GenRef.current += 1;
897
897
  const doc = {
898
898
  components: componentsRef.current,
899
- walkthroughs: walkthroughsRef.current,
899
+ trails: trailsRef.current,
900
900
  };
901
901
  void buildSubsystemGraph(doc, {
902
902
  maxNodeWidth,
@@ -953,7 +953,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
953
953
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id)!.height]));
954
954
  const gen = ++pass2GenRef.current;
955
955
  void buildSubsystemGraph(
956
- { components, walkthroughs },
956
+ { components, trails },
957
957
  { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine },
958
958
  )
959
959
  .then(({ nodes, edges: e, absoluteRects }) => {
@@ -967,7 +967,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
967
967
  // Reveal Pass 1 layout rather than leaving the cover up forever.
968
968
  setLayoutReady(true);
969
969
  });
970
- }, [built.nodes, components, walkthroughs, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
970
+ }, [built.nodes, components, trails, graphifyRelations, orderByLine, maxNodeWidth, showEdgeLabels, moduleNesting]);
971
971
 
972
972
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
973
973
  // Same-id live updates often get no new React Flow `dimensions` events, so
@@ -1008,8 +1008,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1008
1008
  }
1009
1009
  const src = edgeById.get(edgeId);
1010
1010
  setSelectedEdgeId(edgeId);
1011
- // A direct edge selection on the canvas supersedes any walkthrough focus.
1012
- setFocusedWalkthroughId(null);
1011
+ // A direct edge selection on the canvas supersedes any trail focus.
1012
+ setFocusedTrailId(null);
1013
1013
  setFocusedStepIndex(null);
1014
1014
  if (src) onEdgeSelect?.(src);
1015
1015
  },
@@ -1029,7 +1029,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1029
1029
  }
1030
1030
  setSelected(comp);
1031
1031
  setSelectedEdgeId(null);
1032
- setFocusedWalkthroughId(null);
1032
+ setFocusedTrailId(null);
1033
1033
  setFocusedStepIndex(null);
1034
1034
  onSelect?.(alias);
1035
1035
  }
@@ -1083,22 +1083,22 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1083
1083
  [components],
1084
1084
  );
1085
1085
 
1086
- // A `step` issue target names ONE step of a walkthrough — the walkthrough id in
1086
+ // A `step` issue target names ONE step of a trail — the trail id in
1087
1087
  // `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
1088
+ // trails so a finding left over from an edited flow resolves to null
1089
1089
  // rather than framing whatever now happens to sit at that index.
1090
1090
  const issueStep = useCallback(
1091
1091
  (
1092
1092
  issue: SubsystemIssue,
1093
- ): { walkthrough: SubsystemWalkthrough; stepIndex: number } | null => {
1093
+ ): { trail: SubsystemTrail; stepIndex: number } | null => {
1094
1094
  const target = issue.target;
1095
1095
  if (target?.kind !== 'step') return null;
1096
1096
  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 };
1097
+ const trail = trails?.find((t) => t.id === target.id);
1098
+ if (!trail?.steps[target.stepIndex]) return null;
1099
+ return { trail, stepIndex: target.stepIndex };
1100
1100
  },
1101
- [walkthroughs],
1101
+ [trails],
1102
1102
  );
1103
1103
 
1104
1104
  // Per-node diagnostics badge: fold each component-targeted finding that maps
@@ -1194,7 +1194,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1194
1194
  // target:
1195
1195
  // component → that node
1196
1196
  // module → every component in that module
1197
- // walkthrough → the flow's nodes + its hop edges
1197
+ // trail → the flow's nodes + its step edges
1198
1198
  // repo → every component of that repo
1199
1199
  // graph → whole-graph finding; implicates nothing specific
1200
1200
  // Returns null when nothing is expanded, no expanded layer has findings, or
@@ -1225,15 +1225,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1225
1225
  nodeIds.add(c.alias);
1226
1226
  }
1227
1227
  }
1228
- } else if (t.kind === 'walkthrough') {
1229
- const wt = (walkthroughs ?? []).find(
1228
+ } else if (t.kind === 'trail') {
1229
+ const wt = (trails ?? []).find(
1230
1230
  (w) => w.id === t.id || w.title === t.label,
1231
1231
  );
1232
1232
  if (wt) {
1233
1233
  for (const s of wt.steps) {
1234
1234
  nodeIds.add(s.from);
1235
1235
  nodeIds.add(s.to);
1236
- edgeIds.add(walkthroughStepGraphEdgeId(s));
1236
+ edgeIds.add(trailStepGraphEdgeId(s));
1237
1237
  }
1238
1238
  }
1239
1239
  } else if (t.kind === 'repo') {
@@ -1250,7 +1250,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1250
1250
  if (nodeIds.has(e.source) && nodeIds.has(e.target)) edgeIds.add(e.id);
1251
1251
  }
1252
1252
  return { nodeIds, edgeIds };
1253
- }, [issues, expandedIssueCategories, components, walkthroughs, resolveAlias]);
1253
+ }, [issues, expandedIssueCategories, components, trails, resolveAlias]);
1254
1254
  const issueFocusNodeIds = issueFocus?.nodeIds ?? null;
1255
1255
  const issueFocusEdgeIds = issueFocus?.edgeIds ?? null;
1256
1256
 
@@ -1262,58 +1262,58 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1262
1262
  selectedEdgeIdRef.current = selectedEdgeId;
1263
1263
  // Ref mirrors of the focused step, so `unfocusIssueTarget` (a useCallback
1264
1264
  // 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>(
1265
+ // since moved elsewhere in the trail panel.
1266
+ const focusedStepRef = useRef<{ trailId: string; stepIndex: number } | null>(
1267
1267
  null,
1268
1268
  );
1269
1269
  focusedStepRef.current =
1270
- focusedWalkthroughId != null && focusedStepIndex != null
1271
- ? { walkthroughId: focusedWalkthroughId, stepIndex: focusedStepIndex }
1270
+ focusedTrailId != null && focusedStepIndex != null
1271
+ ? { trailId: focusedTrailId, stepIndex: focusedStepIndex }
1272
1272
  : null;
1273
1273
 
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.
1274
+ // Edge ids in trail focus (an active flow's edge set, or a single
1275
+ // step's edge). Used to frame the camera. `null` = no trail focus.
1276
1276
  const focusEdgeIds = useMemo(() => { if (autoPlayFocus) {
1277
- const tl = walkthroughs?.find((t) => t.id === autoPlayFocus.walkthroughId);
1277
+ const tl = trails?.find((t) => t.id === autoPlayFocus.trailId);
1278
1278
  const step = tl?.steps[autoPlayFocus.stepIndex];
1279
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1279
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1280
1280
  }
1281
- if (focusedWalkthroughId == null || !walkthroughs) return null;
1282
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
1281
+ if (focusedTrailId == null || !trails) return null;
1282
+ const tl = trails.find((t) => t.id === focusedTrailId);
1283
1283
  if (!tl) return null;
1284
1284
  if (focusedStepIndex != null) {
1285
1285
  const step = tl.steps[focusedStepIndex];
1286
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1286
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1287
1287
  }
1288
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
1289
- }, [walkthroughs, focusedWalkthroughId, focusedStepIndex, autoPlayFocus]);
1288
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
1289
+ }, [trails, focusedTrailId, focusedStepIndex, autoPlayFocus]);
1290
1290
 
1291
1291
  // 1-based step numbers per edge of the active flow (focused or
1292
1292
  // hover/autoplay-highlighted). An edge can appear in more than one step.
1293
1293
  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);
1294
+ const activeId = focusedTrailId ?? hoveredTrailStep?.trailId;
1295
+ if (activeId == null || !trails) return null;
1296
+ const tl = trails.find((t) => t.id === activeId);
1297
1297
  if (!tl) return null;
1298
1298
  const map = new Map<string, number[]>();
1299
1299
  tl.steps.forEach((s, i) => {
1300
- const list = map.get(walkthroughStepGraphEdgeId(s)) ?? [];
1300
+ const list = map.get(trailStepGraphEdgeId(s)) ?? [];
1301
1301
  list.push(i + 1);
1302
- map.set(walkthroughStepGraphEdgeId(s), list);
1302
+ map.set(trailStepGraphEdgeId(s), list);
1303
1303
  });
1304
1304
  return map;
1305
- }, [walkthroughs, focusedWalkthroughId, hoveredWalkthroughStep]);
1305
+ }, [trails, focusedTrailId, hoveredTrailStep]);
1306
1306
 
1307
- // Union of every expanded (opened) walkthrough's edges — the visible set.
1307
+ // Union of every expanded (opened) trail's edges — the visible set.
1308
1308
  const openedEdgeIds = useMemo(() => {
1309
- if (!walkthroughs || expandedWalkthroughs.size === 0) return null;
1309
+ if (!trails || expandedTrails.size === 0) return null;
1310
1310
  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));
1311
+ for (const tl of trails) {
1312
+ if (!expandedTrails.has(tl.id)) continue;
1313
+ for (const s of tl.steps) ids.add(trailStepGraphEdgeId(s));
1314
1314
  }
1315
1315
  return ids.size > 0 ? ids : null;
1316
- }, [walkthroughs, expandedWalkthroughs]);
1316
+ }, [trails, expandedTrails]);
1317
1317
 
1318
1318
  const endpointsOf = (edgeIds: ReadonlySet<string> | null): Set<string> | null => {
1319
1319
  if (!edgeIds) return null;
@@ -1327,20 +1327,20 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1327
1327
  };
1328
1328
  // Edge/nodes involved in the hovered step or whole flow.
1329
1329
  const hoverEdgeIds = useMemo(() => {
1330
- if (!hoveredWalkthroughStep || !walkthroughs) return null;
1331
- const tl = walkthroughs.find((t) => t.id === hoveredWalkthroughStep.walkthroughId);
1330
+ if (!hoveredTrailStep || !trails) return null;
1331
+ const tl = trails.find((t) => t.id === hoveredTrailStep.trailId);
1332
1332
  if (!tl) return null;
1333
- if (hoveredWalkthroughStep.stepIndex == null) {
1334
- return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
1333
+ if (hoveredTrailStep.stepIndex == null) {
1334
+ return new Set(tl.steps.map((s) => trailStepGraphEdgeId(s)));
1335
1335
  }
1336
- const step = tl.steps[hoveredWalkthroughStep.stepIndex];
1337
- return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
1338
- }, [walkthroughs, hoveredWalkthroughStep]);
1336
+ const step = tl.steps[hoveredTrailStep.stepIndex];
1337
+ return step ? new Set([trailStepGraphEdgeId(step)]) : null;
1338
+ }, [trails, hoveredTrailStep]);
1339
1339
  // While hovering with a *step* already selected, brighten the union of
1340
1340
  // selected + hovered participants. Whole-flow focus (or no focus) keeps
1341
1341
  // the old hover-replace preview so a step hover still dims the rest.
1342
1342
  const previewEdgeIds = useMemo(() => {
1343
- // Autoplay drives focus without a hover, so its hop is the preview set.
1343
+ // Autoplay drives focus without a hover, so its step is the preview set.
1344
1344
  if (autoPlayFocus) return focusEdgeIds;
1345
1345
  if (!hoverEdgeIds) return null;
1346
1346
  // While hovering with a *step* already selected, brighten the union of
@@ -1489,7 +1489,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1489
1489
  };
1490
1490
  // Edges outside the selected view are hidden entirely (labels included).
1491
1491
  // graphify-native edges belong to the graphify view (they are static
1492
- // topology, not runtime hops) regardless of their verb.
1492
+ // topology, not runtime steps) regardless of their verb.
1493
1493
  const edgeInView = (e: Edge): boolean => {
1494
1494
  const d = e.data as
1495
1495
  | { mechanism?: string; provenance?: SubsystemEdgeProvenance }
@@ -1498,7 +1498,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1498
1498
  if (resolvedEdgeView === 'graphify') {
1499
1499
  return d?.provenance === 'graphify';
1500
1500
  }
1501
- return isWalkthroughMechanism(mechanism);
1501
+ return isTrailMechanism(mechanism);
1502
1502
  };
1503
1503
  if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
1504
1504
  return baseEdges.map((e) => {
@@ -1565,7 +1565,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1565
1565
  (_e, node: Node) => {
1566
1566
  const comp = (node.data as { component?: SubsystemComponent } | undefined)?.component;
1567
1567
  setSelectedEdgeId(null);
1568
- setFocusedWalkthroughId(null);
1568
+ setFocusedTrailId(null);
1569
1569
  setFocusedStepIndex(null);
1570
1570
  if (node.type === 'subsystem-component' && comp) {
1571
1571
  // Clicking the already-selected node unselects it (toggle off).
@@ -1593,7 +1593,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1593
1593
  const onPaneClick = useCallback(() => {
1594
1594
  setSelected(null);
1595
1595
  setSelectedEdgeId(null);
1596
- setFocusedWalkthroughId(null);
1596
+ setFocusedTrailId(null);
1597
1597
  setFocusedStepIndex(null);
1598
1598
  issueModuleFocusRef.current = null;
1599
1599
  }, []);
@@ -1601,8 +1601,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1601
1601
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
1602
1602
  // own owner-avatar header. Clicking a header collapses that repo's tree.
1603
1603
  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
1604
+ const hasTrails = useMemo(() => (trails?.length ?? 0) > 0, [trails]);
1605
+ // Aliases of proposed components — the flows panel tints trail titles
1606
1606
  // (and steps) that touch one.
1607
1607
  const proposedAliases = useMemo(
1608
1608
  () => new Set(components.filter((c) => c.proposed).map((c) => c.alias)),
@@ -1679,7 +1679,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1679
1679
  const onNodeDoubleClick: NodeMouseHandler = useCallback(
1680
1680
  (_e, node: Node) => {
1681
1681
  // 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.
1682
+ // zoom back out. User-initiated, so it ignores the trail zoom gate.
1683
1683
  if (node.type === 'subsystem-group') {
1684
1684
  const key =
1685
1685
  (node.data as { region?: { key?: string } } | undefined)?.region?.key;
@@ -1715,7 +1715,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1715
1715
  [onOpenDeclarationFile, fitView, focusedBoundaryKey],
1716
1716
  );
1717
1717
 
1718
- const onOpenFileFromWalkthrough = useCallback(
1718
+ const onOpenFileFromTrail = useCallback(
1719
1719
  (file: string, opts?: SubsystemOpenFileOptions) => {
1720
1720
  setFileOverlay({ file, startLine: opts?.startLine });
1721
1721
  onFileSelect?.(file);
@@ -1737,15 +1737,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1737
1737
  const fileOverlayOpen = fileOverlay != null;
1738
1738
  const collapseCanvas = drawerFillHeight || fileOverlayOpen;
1739
1739
 
1740
- // Drop the full-file overlay when the underlying walkthrough drawer changes
1740
+ // Drop the full-file overlay when the underlying trail drawer changes
1741
1741
  // 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}`
1742
+ const trailDrawerKey =
1743
+ drawerTarget?.kind === 'trail'
1744
+ ? `${drawerTarget.trailId}:${drawerTarget.stepIndex}`
1745
1745
  : null;
1746
1746
  useEffect(() => {
1747
1747
  setFileOverlay(null);
1748
- }, [walkthroughDrawerKey]);
1748
+ }, [trailDrawerKey]);
1749
1749
 
1750
1750
  useEffect(() => {
1751
1751
  if (!fileOverlayOpen) return;
@@ -1756,16 +1756,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1756
1756
  return () => window.removeEventListener('keydown', onKey);
1757
1757
  }, [fileOverlayOpen, closeFileOverlay]);
1758
1758
 
1759
- // Camera helper shared by the walkthrough interactions. Fits the union of
1759
+ // Camera helper shared by the trail interactions. Fits the union of
1760
1760
  // the focused edges' endpoint rects AND their routed waypoints — the edge
1761
1761
  // 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
1762
+ // frame is the line the step actually traces. That keeps a step whose route
1763
1763
  // bulges out around intervening nodes from being clipped at the viewport
1764
1764
  // edge, and it is the edge's own geometry doing the guiding, not fudge
1765
1765
  // padding.
1766
1766
  const fitFocusBounds = useCallback(
1767
1767
  (ids: ReadonlySet<string>) => {
1768
- if (!zoomOnWalkthroughFocus) return;
1768
+ if (!zoomOnTrailFocus) return;
1769
1769
  const nodeIds = new Set<string>();
1770
1770
  const points: { x: number; y: number }[] = [];
1771
1771
  for (const e of baseEdges) {
@@ -1810,15 +1810,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1810
1810
  };
1811
1811
  fitBounds(bounds, {
1812
1812
  padding: 0.15,
1813
- duration: walkthroughFocusDurationMs,
1813
+ duration: trailFocusDurationMs,
1814
1814
  });
1815
1815
  },
1816
1816
  [
1817
1817
  baseEdges,
1818
1818
  built.absoluteRects,
1819
1819
  fitBounds,
1820
- zoomOnWalkthroughFocus,
1821
- walkthroughFocusDurationMs,
1820
+ zoomOnTrailFocus,
1821
+ trailFocusDurationMs,
1822
1822
  ],
1823
1823
  );
1824
1824
 
@@ -1876,16 +1876,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1876
1876
  width: Math.max(1, maxX - minX),
1877
1877
  height: Math.max(1, maxY - minY),
1878
1878
  },
1879
- { padding, duration: walkthroughFocusDurationMs },
1879
+ { padding, duration: trailFocusDurationMs },
1880
1880
  );
1881
1881
  },
1882
- [rectForNode, fitBounds, walkthroughFocusDurationMs],
1882
+ [rectForNode, fitBounds, trailFocusDurationMs],
1883
1883
  );
1884
1884
 
1885
- // Zoom back out to the full diagram after the last expanded walkthrough
1885
+ // Zoom back out to the full diagram after the last expanded trail
1886
1886
  // closes (visibility restores non-flow nodes that were hidden).
1887
1887
  const fitOverview = useCallback(() => {
1888
- if (!zoomOnWalkthroughFocus) return;
1888
+ if (!zoomOnTrailFocus) return;
1889
1889
  fitView({
1890
1890
  padding: 0.1,
1891
1891
  includeHiddenNodes: false,
@@ -1893,115 +1893,115 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
1893
1893
  maxZoom: 2,
1894
1894
  duration: 300,
1895
1895
  });
1896
- }, [fitView, zoomOnWalkthroughFocus]);
1896
+ }, [fitView, zoomOnTrailFocus]);
1897
1897
 
1898
1898
  // Focus an entire flow: hide everything but the flow's nodes and edges, and
1899
1899
  // frame the flow on the canvas. Selection state is cleared — the graph now
1900
1900
  // 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)
1901
+ // click. A stale trail drawer (from a previously focused flow's step)
1902
1902
  // closes; an explicitly opened file drawer stays.
1903
1903
  // In `dim` mode: clear step highlight and leave the full graph visible
1904
1904
  // (no zoom / hide) — matching "step away from a hovered step".
1905
- const focusWalkthroughEdges = useCallback(
1906
- (tl: SubsystemWalkthrough) => {
1905
+ const focusTrailEdges = useCallback(
1906
+ (tl: SubsystemTrail) => {
1907
1907
  setSelected(null);
1908
1908
  setSelectedEdgeId(null);
1909
- setHoveredWalkthroughStep(null);
1910
- if (walkthroughStepMode === 'dim') {
1909
+ setHoveredTrailStep(null);
1910
+ if (trailStepMode === 'dim') {
1911
1911
  setFocusedStepIndex(null);
1912
- setFocusedWalkthroughId(null);
1913
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1912
+ setFocusedTrailId(null);
1913
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1914
1914
  return;
1915
1915
  }
1916
1916
  setFocusedStepIndex(null);
1917
- setFocusedWalkthroughId(tl.id);
1918
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1919
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1917
+ setFocusedTrailId(tl.id);
1918
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1919
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1920
1920
  },
1921
- [fitFocusBounds, walkthroughStepMode],
1921
+ [fitFocusBounds, trailStepMode],
1922
1922
  );
1923
1923
 
1924
- const clearWalkthroughFocus = useCallback(() => {
1925
- setFocusedWalkthroughId(null);
1924
+ const clearTrailFocus = useCallback(() => {
1925
+ setFocusedTrailId(null);
1926
1926
  setFocusedStepIndex(null);
1927
- setHoveredWalkthroughStep(null);
1928
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1927
+ setHoveredTrailStep(null);
1928
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1929
1929
  }, []);
1930
1930
 
1931
- // Host deep-link: opening the graph from a walkthrough row selects that
1931
+ // Host deep-link: opening the graph from a trail row selects that
1932
1932
  // 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
1933
+ // Trails. Guarded by a ref so a later in-tab selection isn't yanked
1934
1934
  // back; a remount (or a new id) re-applies it.
1935
- const appliedInitialWalkthroughRef = useRef<string | null>(null);
1935
+ const appliedInitialTrailRef = useRef<string | null>(null);
1936
1936
  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);
1937
+ if (!initialTrailId) return;
1938
+ if (appliedInitialTrailRef.current === initialTrailId) return;
1939
+ if (!layoutReady || !trails?.length) return;
1940
+ const tl = trails.find((t) => t.id === initialTrailId);
1941
1941
  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]);
1942
+ appliedInitialTrailRef.current = initialTrailId;
1943
+ setSidebarView('trails');
1944
+ setExpandedTrails((prev) => new Set(prev).add(tl.id));
1945
+ focusTrailEdges(tl);
1946
+ }, [initialTrailId, layoutReady, trails, focusTrailEdges]);
1947
1947
 
1948
1948
  // Switching sidebar panels also switches the edge vocabulary. Leaving the
1949
- // Walkthroughs panel drops its canvas state (focus, expanded flows, selected
1949
+ // Trails panel drops its canvas state (focus, expanded flows, selected
1950
1950
  // edge) so the Files view's topology edges aren't gated by a flow the user
1951
1951
  // can no longer see or un-dim.
1952
1952
  const changeSidebarView = useCallback(
1953
- (view: 'files' | 'walkthroughs') => {
1953
+ (view: 'files' | 'trails') => {
1954
1954
  setSidebarView(view);
1955
- if (view === 'walkthroughs') return;
1956
- clearWalkthroughFocus();
1957
- setExpandedWalkthroughs(new Set());
1955
+ if (view === 'trails') return;
1956
+ clearTrailFocus();
1957
+ setExpandedTrails(new Set());
1958
1958
  setSelectedEdgeId(null);
1959
1959
  },
1960
- [clearWalkthroughFocus],
1960
+ [clearTrailFocus],
1961
1961
  );
1962
1962
 
1963
- // Focus a single step's edge on the canvas and open/scroll the walkthrough
1963
+ // Focus a single step's edge on the canvas and open/scroll the trail
1964
1964
  // drawer to that step's snippet. Clicking the already-focused step clears
1965
1965
  // step focus and returns to whole-flow (high-level) framing.
1966
1966
  // In `dim` mode: only dim non-participants (same as hovering a step) —
1967
1967
  // no camera move, no drawer, no hide.
1968
- const focusWalkthroughStep = useCallback(
1969
- (tl: SubsystemWalkthrough, stepIndex: number) => {
1968
+ const focusTrailStep = useCallback(
1969
+ (tl: SubsystemTrail, stepIndex: number) => {
1970
1970
  const step = tl.steps[stepIndex];
1971
1971
  if (!step) return;
1972
1972
  setSelected(null);
1973
1973
  setSelectedEdgeId(null);
1974
- if (walkthroughStepMode === 'dim') {
1974
+ if (trailStepMode === 'dim') {
1975
1975
  setFocusedStepIndex(null);
1976
- setFocusedWalkthroughId(null);
1977
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1978
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1976
+ setFocusedTrailId(null);
1977
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1978
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1979
1979
  return;
1980
1980
  }
1981
1981
  // Toggle: clicking the selected step unselects it (back to whole flow).
1982
- if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
1982
+ if (focusedTrailId === tl.id && focusedStepIndex === stepIndex) {
1983
1983
  setFocusedStepIndex(null);
1984
1984
  // Pointer is still over the row — keep hover preview so dimming doesn't
1985
1985
  // flash off until mouseleave.
1986
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
1987
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
1986
+ setHoveredTrailStep({ trailId: tl.id, stepIndex });
1987
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1988
1988
  if (pendingFocusFitRef.current != null) {
1989
1989
  window.clearTimeout(pendingFocusFitRef.current);
1990
1990
  pendingFocusFitRef.current = null;
1991
1991
  }
1992
- fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
1992
+ fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1993
1993
  return;
1994
1994
  }
1995
1995
  setFocusedStepIndex(stepIndex);
1996
- setFocusedWalkthroughId(tl.id);
1997
- setHoveredWalkthroughStep(null);
1996
+ setFocusedTrailId(tl.id);
1997
+ setHoveredTrailStep(null);
1998
1998
  // Open the drawer before fitting. If it was closed, wait for its height
1999
1999
  // transition so fitView uses the reduced canvas — not full height.
2000
2000
  const drawerWasOpen = drawerOpenRef.current;
2001
- if (renderWalkthroughViewer) {
2001
+ if (renderTrailViewer) {
2002
2002
  setDrawerTarget({
2003
- kind: 'walkthrough',
2004
- walkthroughId: tl.id,
2003
+ kind: 'trail',
2004
+ trailId: tl.id,
2005
2005
  stepIndex,
2006
2006
  });
2007
2007
  } else {
@@ -2011,7 +2011,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2011
2011
  startLine: step.line,
2012
2012
  });
2013
2013
  }
2014
- const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
2014
+ const edgeIds = new Set([trailStepGraphEdgeId(step)]);
2015
2015
  if (pendingFocusFitRef.current != null) {
2016
2016
  window.clearTimeout(pendingFocusFitRef.current);
2017
2017
  pendingFocusFitRef.current = null;
@@ -2028,9 +2028,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2028
2028
  [
2029
2029
  fitFocusBounds,
2030
2030
  focusedStepIndex,
2031
- focusedWalkthroughId,
2032
- renderWalkthroughViewer,
2033
- walkthroughStepMode,
2031
+ focusedTrailId,
2032
+ renderTrailViewer,
2033
+ trailStepMode,
2034
2034
  ],
2035
2035
  );
2036
2036
 
@@ -2052,10 +2052,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2052
2052
  const prev = prevPreviewEdgeIdsRef.current;
2053
2053
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
2054
2054
 
2055
- if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim') return;
2055
+ if (!zoomOnTrailFocus || trailStepMode === 'dim') return;
2056
2056
  // Camera follows hover only when a specific step is already focused (or
2057
2057
  // autoplay is driving an auto-focus).
2058
- if (!autoPlayFocus && (focusedWalkthroughId == null || focusedStepIndex == null)) {
2058
+ if (!autoPlayFocus && (focusedTrailId == null || focusedStepIndex == null)) {
2059
2059
  return;
2060
2060
  }
2061
2061
 
@@ -2069,26 +2069,26 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2069
2069
  }, [
2070
2070
  previewEdgeIds,
2071
2071
  focusEdgeIds,
2072
- focusedWalkthroughId,
2072
+ focusedTrailId,
2073
2073
  focusedStepIndex,
2074
2074
  autoPlayFocus,
2075
2075
  fitFocusBounds,
2076
- zoomOnWalkthroughFocus,
2077
- walkthroughStepMode,
2076
+ zoomOnTrailFocus,
2077
+ trailStepMode,
2078
2078
  ]);
2079
2079
 
2080
- // Graph-only embeds: cycle walkthrough steps. In `focus` mode each step is
2081
- // selected (camera frames it via fitFocusBounds) and, when a walkthrough
2080
+ // Graph-only embeds: cycle trail steps. In `focus` mode each step is
2081
+ // selected (camera frames it via fitFocusBounds) and, when a trail
2082
2082
  // viewer is supplied, its snippet drawer opens; in `dim` mode it only
2083
2083
  // dim-highlights the step (hover-style) with no camera move.
2084
2084
  useEffect(() => {
2085
- if (!autoPlayWalkthroughs || walkthroughs == null || walkthroughs.length === 0) return;
2085
+ if (!autoPlayTrails || trails == null || trails.length === 0) return;
2086
2086
  if (!layoutReady) return;
2087
- const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
2087
+ const playable = trails.filter((tl) => tl.steps.length > 0);
2088
2088
  if (playable.length === 0) return;
2089
2089
  // Only run the cycles that can actually be shown in the current edge view.
2090
2090
  const inView = playable.filter((tl) =>
2091
- tl.steps.some((s) => isWalkthroughMechanism(s.mechanism)),
2091
+ tl.steps.some((s) => isTrailMechanism(s.mechanism)),
2092
2092
  );
2093
2093
  if (inView.length === 0) return;
2094
2094
  const cyc = inView;
@@ -2097,8 +2097,8 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2097
2097
  let tlIdx = 0;
2098
2098
  let stepIdx = 0;
2099
2099
  let timer: number | null = null;
2100
- const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
2101
- const focusMode = walkthroughStepMode !== 'dim';
2100
+ const interval = Math.max(400, trailAutoPlayIntervalMs);
2101
+ const focusMode = trailStepMode !== 'dim';
2102
2102
  let cleanup = () => {};
2103
2103
 
2104
2104
  const tick = () => {
@@ -2107,15 +2107,15 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2107
2107
  setSelected(null);
2108
2108
  setSelectedEdgeId(null);
2109
2109
  if (focusMode) {
2110
- setHoveredWalkthroughStep(null);
2111
- setFocusedWalkthroughId(null);
2110
+ setHoveredTrailStep(null);
2111
+ setFocusedTrailId(null);
2112
2112
  setFocusedStepIndex(null);
2113
- setAutoPlayFocus({ walkthroughId: tl.id, stepIndex: stepIdx });
2113
+ setAutoPlayFocus({ trailId: tl.id, stepIndex: stepIdx });
2114
2114
  if (!hideDrawer) {
2115
2115
  const step = tl.steps[stepIdx];
2116
2116
  if (step) {
2117
- if (renderWalkthroughViewer) {
2118
- setDrawerTarget({ kind: 'walkthrough', walkthroughId: tl.id, stepIndex: stepIdx });
2117
+ if (renderTrailViewer) {
2118
+ setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
2119
2119
  } else {
2120
2120
  setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
2121
2121
  }
@@ -2123,9 +2123,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2123
2123
  }
2124
2124
  } else {
2125
2125
  setAutoPlayFocus(null);
2126
- setFocusedWalkthroughId(null);
2126
+ setFocusedTrailId(null);
2127
2127
  setFocusedStepIndex(null);
2128
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
2128
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: stepIdx });
2129
2129
  }
2130
2130
  stepIdx += 1;
2131
2131
  if (stepIdx >= tl.steps.length) {
@@ -2139,29 +2139,29 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2139
2139
  cleanup = () => {
2140
2140
  cancelled = true;
2141
2141
  if (timer != null) window.clearTimeout(timer);
2142
- setHoveredWalkthroughStep(null);
2143
- setFocusedWalkthroughId(null);
2142
+ setHoveredTrailStep(null);
2143
+ setFocusedTrailId(null);
2144
2144
  setFocusedStepIndex(null);
2145
2145
  setAutoPlayFocus(null);
2146
2146
  };
2147
2147
  return cleanup;
2148
2148
  }, [
2149
- autoPlayWalkthroughs,
2150
- walkthroughs,
2151
- walkthroughAutoPlayIntervalMs,
2149
+ autoPlayTrails,
2150
+ trails,
2151
+ trailAutoPlayIntervalMs,
2152
2152
  layoutReady,
2153
- walkthroughStepMode,
2154
- renderWalkthroughViewer,
2153
+ trailStepMode,
2154
+ renderTrailViewer,
2155
2155
  hideDrawer,
2156
2156
  ]);
2157
2157
 
2158
- // Arrow keys step through the focused walkthrough once a step is active
2158
+ // Arrow keys step through the focused trail once a step is active
2159
2159
  // (sidebar click or drawer open). Ignores typing targets and chords.
2160
2160
  useEffect(() => {
2161
- if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
2161
+ if (focusedTrailId == null || focusedStepIndex == null || !trails) {
2162
2162
  return;
2163
2163
  }
2164
- const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
2164
+ const tl = trails.find((t) => t.id === focusedTrailId);
2165
2165
  if (!tl || tl.steps.length === 0) return;
2166
2166
 
2167
2167
  const onKeyDown = (e: KeyboardEvent) => {
@@ -2187,31 +2187,31 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2187
2187
  }
2188
2188
  if (next === focusedStepIndex) return;
2189
2189
  e.preventDefault();
2190
- focusWalkthroughStep(tl, next);
2190
+ focusTrailStep(tl, next);
2191
2191
  };
2192
2192
 
2193
2193
  window.addEventListener('keydown', onKeyDown);
2194
2194
  return () => window.removeEventListener('keydown', onKeyDown);
2195
2195
  }, [
2196
- focusedWalkthroughId,
2196
+ focusedTrailId,
2197
2197
  focusedStepIndex,
2198
- walkthroughs,
2199
- focusWalkthroughStep,
2198
+ trails,
2199
+ focusTrailStep,
2200
2200
  ]);
2201
2201
 
2202
- const toggleWalkthroughCollapsed = useCallback(
2202
+ const toggleTrailCollapsed = useCallback(
2203
2203
  (tlId: string) => {
2204
2204
  const collapsingLast =
2205
- expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
2205
+ expandedTrails.has(tlId) && expandedTrails.size === 1;
2206
2206
 
2207
- setExpandedWalkthroughs((prev) => {
2207
+ setExpandedTrails((prev) => {
2208
2208
  const next = new Set(prev);
2209
2209
  if (next.has(tlId)) next.delete(tlId);
2210
2210
  else next.add(tlId);
2211
2211
  return next;
2212
2212
  });
2213
2213
 
2214
- if (!collapsingLast || !zoomOnWalkthroughFocus) return;
2214
+ if (!collapsingLast || !zoomOnTrailFocus) return;
2215
2215
 
2216
2216
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
2217
2217
  if (pendingFocusFitRef.current != null) {
@@ -2231,7 +2231,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2231
2231
  drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20,
2232
2232
  );
2233
2233
  },
2234
- [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus],
2234
+ [expandedTrails, fitOverview, zoomOnTrailFocus],
2235
2235
  );
2236
2236
 
2237
2237
  // Detail-panel links: related-name clicks select the matching component —
@@ -2249,7 +2249,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2249
2249
  if (!comp || comp.alias === selectedRef.current?.alias) return;
2250
2250
  setSelected(comp);
2251
2251
  setSelectedEdgeId(null);
2252
- setFocusedWalkthroughId(null);
2252
+ setFocusedTrailId(null);
2253
2253
  setFocusedStepIndex(null);
2254
2254
  onSelect?.(comp.alias);
2255
2255
  },
@@ -2268,9 +2268,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2268
2268
  if (comp) {
2269
2269
  setSelected(comp);
2270
2270
  setSelectedEdgeId(null);
2271
- setFocusedWalkthroughId(null);
2271
+ setFocusedTrailId(null);
2272
2272
  setFocusedStepIndex(null);
2273
- setHoveredWalkthroughStep(null);
2273
+ setHoveredTrailStep(null);
2274
2274
  issueModuleFocusRef.current = null;
2275
2275
  onSelect?.(comp.alias);
2276
2276
  requestAnimationFrame(() => {
@@ -2284,11 +2284,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2284
2284
  });
2285
2285
  } else if (step) {
2286
2286
  // A step finding focuses the step itself, through the same entry point
2287
- // the walkthrough panel uses — so the drawer, the step numbering, the
2287
+ // the trail panel uses — so the drawer, the step numbering, the
2288
2288
  // dim-mode hover preview, and the camera all behave identically whether
2289
2289
  // the step was reached from the panel or from a diagnostics card.
2290
2290
  issueModuleFocusRef.current = null;
2291
- focusWalkthroughStep(step.walkthrough, step.stepIndex);
2291
+ focusTrailStep(step.trail, step.stepIndex);
2292
2292
  } else {
2293
2293
  // Nothing selectable — but a module target names a boundary frame, so
2294
2294
  // frame that region.
@@ -2296,9 +2296,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2296
2296
  if (moduleNodeId) {
2297
2297
  setSelected(null);
2298
2298
  setSelectedEdgeId(null);
2299
- setFocusedWalkthroughId(null);
2299
+ setFocusedTrailId(null);
2300
2300
  setFocusedStepIndex(null);
2301
- setHoveredWalkthroughStep(null);
2301
+ setHoveredTrailStep(null);
2302
2302
  issueModuleFocusRef.current = moduleNodeId;
2303
2303
  requestAnimationFrame(() => {
2304
2304
  fitNodeRects(new Set([moduleNodeId]), 0.4);
@@ -2311,7 +2311,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2311
2311
  issueComponent,
2312
2312
  issueStep,
2313
2313
  issueModuleNodeId,
2314
- focusWalkthroughStep,
2314
+ focusTrailStep,
2315
2315
  onSelect,
2316
2316
  onSelectIssue,
2317
2317
  fitView,
@@ -2341,14 +2341,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2341
2341
  // not the card), and correctly leaves that alone here.
2342
2342
  const owned = focusedStepRef.current;
2343
2343
  if (
2344
- owned?.walkthroughId !== step.walkthrough.id ||
2344
+ owned?.trailId !== step.trail.id ||
2345
2345
  owned?.stepIndex !== step.stepIndex
2346
2346
  ) {
2347
2347
  return;
2348
2348
  }
2349
- setFocusedWalkthroughId(null);
2349
+ setFocusedTrailId(null);
2350
2350
  setFocusedStepIndex(null);
2351
- setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
2351
+ setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
2352
2352
  } else if (moduleNodeId) {
2353
2353
  // Only unwind framing this card still owns.
2354
2354
  if (issueModuleFocusRef.current !== moduleNodeId) return;
@@ -2364,14 +2364,14 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2364
2364
  [issueComponent, issueStep, issueModuleNodeId, fitView],
2365
2365
  );
2366
2366
 
2367
- // Construct tokens inside a walkthrough snippet. A step's line is an edge
2367
+ // Construct tokens inside a trail snippet. A step's line is an edge
2368
2368
  // between its `from`/`to` components, so a token naming either of those
2369
2369
  // constructs should navigate to that construct's declaration. Index the
2370
- // matchable identifiers per step (`walkthroughId:index`).
2371
- const walkthroughSymbolIndex = useMemo(() => {
2370
+ // matchable identifiers per step (`trailId:index`).
2371
+ const trailSymbolIndex = useMemo(() => {
2372
2372
  const byAlias = new Map(components.map((c) => [c.alias, c]));
2373
2373
  const index = new Map<string, Map<string, string>>();
2374
- for (const wt of walkthroughs ?? []) {
2374
+ for (const wt of trails ?? []) {
2375
2375
  wt.steps.forEach((step, i) => {
2376
2376
  const tokens = new Map<string, string>();
2377
2377
  for (const alias of [step.from, step.to]) {
@@ -2383,19 +2383,19 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2383
2383
  });
2384
2384
  }
2385
2385
  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;
2386
+ }, [components, trails]);
2387
+ const trailSymbolIndexRef = useRef(trailSymbolIndex);
2388
+ trailSymbolIndexRef.current = trailSymbolIndex;
2389
+
2390
+ // Stable resolver: reads the live index + focused trail from refs so
2391
+ // the memoized trail drawer isn't rebuilt on every graph render.
2392
+ const resolveTrailSymbol = useCallback(
2393
+ (query: TrailSymbolQuery): string | null => {
2394
+ const trailId = drawerTrailIdRef.current;
2395
+ if (trailId == null) return null;
2396
2396
  return (
2397
- walkthroughSymbolIndexRef.current
2398
- .get(`${walkthroughId}:${query.stepIndex}`)
2397
+ trailSymbolIndexRef.current
2398
+ .get(`${trailId}:${query.stepIndex}`)
2399
2399
  ?.get(query.tokenText) ?? null
2400
2400
  );
2401
2401
  },
@@ -2459,21 +2459,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2459
2459
  return components.find((c) => c.alias === hoveredComponentAlias)?.file ?? null;
2460
2460
  }, [components, hoveredComponentAlias]);
2461
2461
 
2462
- // Drawer content renderer: prefer the path-keyed viewer; fall back to the
2463
- // legacy component-keyed one via a file → first-component lookup.
2464
- const fileViewer = useMemo(() => {
2465
- if (renderFileViewer) return renderFileViewer;
2466
- if (renderFileView) {
2467
- const byFile = new Map(
2468
- components.filter((c) => c.file).map((c) => [c.file, c] as const),
2469
- );
2470
- return (file: string, _opts?: SubsystemOpenFileOptions) => {
2471
- const comp = byFile.get(file);
2472
- return comp ? renderFileView(comp) : null;
2473
- };
2474
- }
2475
- return renderFileViewer;
2476
- }, [renderFileViewer, renderFileView, components]);
2462
+ // Drawer content renderer. Path-keyed only — the component-keyed
2463
+ // `renderFileView` variant was removed; nothing in-repo passed it.
2464
+ const fileViewer = renderFileViewer;
2477
2465
 
2478
2466
  const fileViewerRef = useRef(fileViewer);
2479
2467
  fileViewerRef.current = fileViewer;
@@ -2483,11 +2471,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2483
2471
  [],
2484
2472
  );
2485
2473
 
2486
- const walkthroughViewerRef = useRef(renderWalkthroughViewer);
2487
- walkthroughViewerRef.current = renderWalkthroughViewer;
2488
- const renderWalkthroughDrawerContent = useCallback(
2489
- (ctx: WalkthroughViewerContext) =>
2490
- walkthroughViewerRef.current?.(ctx) ?? null,
2474
+ const trailViewerRef = useRef(renderTrailViewer);
2475
+ trailViewerRef.current = renderTrailViewer;
2476
+ const renderTrailDrawerContent = useCallback(
2477
+ (ctx: TrailViewerContext) =>
2478
+ trailViewerRef.current?.(ctx) ?? null,
2491
2479
  [],
2492
2480
  );
2493
2481
 
@@ -2496,7 +2484,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2496
2484
  {/* Sidebar: scrollable title/description on top, files or flows pinned below.
2497
2485
  The description hides by default so files/flows get the room; the
2498
2486
  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) && (
2487
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2500
2488
  <div
2501
2489
  style={{
2502
2490
  width: sidebarWidth,
@@ -2603,7 +2591,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2603
2591
  )}
2604
2592
  {sidebarAfterDescription}
2605
2593
  </div>
2606
- {(treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (
2594
+ {(treeFilePaths.length > 0 || hasTrails || issuesActive) && (
2607
2595
  <div
2608
2596
  style={{
2609
2597
  ...(showDesc ? { height: '50%' as const } : { flex: 1, minHeight: 0 }),
@@ -2682,7 +2670,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2682
2670
  )
2683
2671
  ) : (
2684
2672
  <>
2685
- {hasWalkthroughs && (
2673
+ {hasTrails && (
2686
2674
  <div
2687
2675
  role="tablist"
2688
2676
  aria-label="Sidebar view"
@@ -2694,7 +2682,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2694
2682
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2695
2683
  }}
2696
2684
  >
2697
- {(['walkthroughs', 'files'] as const).map((view) => (
2685
+ {(['trails', 'files'] as const).map((view) => (
2698
2686
  <button
2699
2687
  key={view}
2700
2688
  type="button"
@@ -2718,30 +2706,30 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2718
2706
  cursor: 'pointer',
2719
2707
  }}
2720
2708
  >
2721
- {view === 'walkthroughs' ? 'Walkthroughs' : 'Files'}
2709
+ {view === 'trails' ? 'Trails' : 'Files'}
2722
2710
  </button>
2723
2711
  ))}
2724
2712
  </div>
2725
2713
  )}
2726
- {sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (
2727
- <WalkthroughsPanel
2728
- walkthroughs={walkthroughs}
2729
- expandedWalkthroughs={expandedWalkthroughs}
2730
- focusedWalkthroughId={focusedWalkthroughId}
2714
+ {sidebarView === 'trails' && trails && trails.length > 0 ? (
2715
+ <TrailsPanel
2716
+ trails={trails}
2717
+ expandedTrails={expandedTrails}
2718
+ focusedTrailId={focusedTrailId}
2731
2719
  focusedStepIndex={focusedStepIndex}
2732
- hoveredWalkthroughStep={hoveredWalkthroughStep}
2733
- onToggleCollapsed={toggleWalkthroughCollapsed}
2734
- onFocusFlow={focusWalkthroughEdges}
2735
- onClearFocus={clearWalkthroughFocus}
2736
- onFocusStep={focusWalkthroughStep}
2720
+ hoveredTrailStep={hoveredTrailStep}
2721
+ onToggleCollapsed={toggleTrailCollapsed}
2722
+ onFocusFlow={focusTrailEdges}
2723
+ onClearFocus={clearTrailFocus}
2724
+ onFocusStep={focusTrailStep}
2737
2725
  onHoverStep={(tl, i) =>
2738
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i })
2726
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: i })
2739
2727
  }
2740
2728
  onHoverFlow={(tl) =>
2741
- setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null })
2729
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: null })
2742
2730
  }
2743
- onLeaveStep={() => setHoveredWalkthroughStep(null)}
2744
- onReorder={onReorderWalkthroughs}
2731
+ onLeaveStep={() => setHoveredTrailStep(null)}
2732
+ onReorder={onReorderTrails}
2745
2733
  proposedAliases={proposedAliases}
2746
2734
  />
2747
2735
  ) : treeFilePaths.length > 0 ? (
@@ -2790,7 +2778,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2790
2778
  )}
2791
2779
 
2792
2780
  {/* Drag handle between sidebar and canvas — resize the left panel. */}
2793
- {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
2781
+ {!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (
2794
2782
  <div
2795
2783
  onMouseDown={onSidebarResizeStart}
2796
2784
  aria-label="Resize sidebar"
@@ -2971,9 +2959,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
2971
2959
  >
2972
2960
  <GraphChrome />
2973
2961
  </ReactFlow>
2974
- {/* Graph / walkthrough / step titles — non-interactive chips at the top
2962
+ {/* Graph / trail / step titles — non-interactive chips at the top
2975
2963
  of the canvas. Graph-only embeds use these without opening the sidebar. */}
2976
- {(graphTitle || overlayWalkthroughTitle || overlayWalkthroughStep) && (
2964
+ {(graphTitle || overlayTrailTitle || overlayTrailStep) && (
2977
2965
  <div
2978
2966
  style={{
2979
2967
  position: 'absolute',
@@ -3010,11 +2998,11 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3010
2998
  {graphTitle}
3011
2999
  </div>
3012
3000
  )}
3013
- {overlayWalkthroughTitle && (
3001
+ {overlayTrailTitle && (
3014
3002
  <div
3015
3003
  style={{
3016
3004
  maxWidth: '100%',
3017
- minWidth: overlayWalkthroughStep ? 160 : undefined,
3005
+ minWidth: overlayTrailStep ? 160 : undefined,
3018
3006
  display: 'flex',
3019
3007
  flexDirection: 'column',
3020
3008
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -3025,9 +3013,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3025
3013
  opacity: 0.95,
3026
3014
  }}
3027
3015
  aria-label={
3028
- overlayWalkthroughStep
3029
- ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
3030
- : overlayWalkthroughTitle
3016
+ overlayTrailStep
3017
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
3018
+ : overlayTrailTitle
3031
3019
  }
3032
3020
  >
3033
3021
  <div
@@ -3043,9 +3031,9 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3043
3031
  textAlign: 'center',
3044
3032
  }}
3045
3033
  >
3046
- {overlayWalkthroughTitle}
3034
+ {overlayTrailTitle}
3047
3035
  </div>
3048
- {overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (
3036
+ {overlayTrailStep && overlayTrailStep.total > 0 && (
3049
3037
  <div
3050
3038
  style={{
3051
3039
  display: 'flex',
@@ -3054,10 +3042,10 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3054
3042
  }}
3055
3043
  aria-hidden="true"
3056
3044
  >
3057
- {Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
3045
+ {Array.from({ length: overlayTrailStep.total }, (_, i) => {
3058
3046
  const n = i + 1;
3059
- const active = n === overlayWalkthroughStep.index;
3060
- const done = n < overlayWalkthroughStep.index;
3047
+ const active = n === overlayTrailStep.index;
3048
+ const done = n < overlayTrailStep.index;
3061
3049
  return (
3062
3050
  <span
3063
3051
  key={n}
@@ -3080,7 +3068,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3080
3068
  )}
3081
3069
  </div>
3082
3070
  )}
3083
- {overlayWalkthroughStep?.annotation && (
3071
+ {overlayTrailStep?.annotation && (
3084
3072
  <div
3085
3073
  style={{
3086
3074
  maxWidth: '100%',
@@ -3096,7 +3084,7 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3096
3084
  lineHeight: 1.35,
3097
3085
  }}
3098
3086
  >
3099
- {overlayWalkthroughStep.annotation}
3087
+ {overlayTrailStep.annotation}
3100
3088
  </div>
3101
3089
  )}
3102
3090
  </div>
@@ -3158,16 +3146,16 @@ function Inner({ components, walkthroughs, graphifyRelations, orderByLine, initi
3158
3146
  suppressEscape={fileOverlayOpen}
3159
3147
  hidden={hideDrawer}
3160
3148
  >
3161
- {drawerTarget?.kind === 'walkthrough' &&
3162
- focusedWalkthrough &&
3163
- renderWalkthroughViewer ? (
3164
- <WalkthroughDrawerContent
3165
- render={renderWalkthroughDrawerContent}
3166
- walkthrough={focusedWalkthrough}
3149
+ {drawerTarget?.kind === 'trail' &&
3150
+ focusedTrail &&
3151
+ renderTrailViewer ? (
3152
+ <TrailDrawerContent
3153
+ render={renderTrailDrawerContent}
3154
+ trail={focusedTrail}
3167
3155
  stepIndex={drawerTarget.stepIndex}
3168
- onOpenFile={onOpenFileFromWalkthrough}
3156
+ onOpenFile={onOpenFileFromTrail}
3169
3157
  proposedAliases={proposedAliases}
3170
- resolveSymbol={resolveWalkthroughSymbol}
3158
+ resolveSymbol={resolveTrailSymbol}
3171
3159
  onSymbolClick={openConstructDeclaration}
3172
3160
  />
3173
3161
  ) : drawerTarget?.kind === 'file' ? (
@@ -3336,7 +3324,7 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3336
3324
 
3337
3325
  const constructsOnly = isConstructsOnlyModel({
3338
3326
  components: props.components,
3339
- walkthroughs: props.walkthroughs,
3327
+ trails: props.trails,
3340
3328
  graphifyRelations: props.graphifyRelations,
3341
3329
  });
3342
3330
 
@@ -3366,7 +3354,6 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3366
3354
  sidebarExtra={props.sidebarExtra}
3367
3355
  sidebarAfterDescription={props.sidebarAfterDescription}
3368
3356
  renderFileViewer={props.renderFileViewer}
3369
- renderFileView={props.renderFileView}
3370
3357
  onFileSelect={props.onFileSelect}
3371
3358
  componentVerification={props.componentVerification}
3372
3359
  onInspectSymbol={props.onInspectSymbol}