@principal-ai/subsystems-react 0.20.5 → 0.21.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 (87) hide show
  1. package/README.md +1 -1
  2. package/dist/graphify/anchor.d.ts +17 -0
  3. package/dist/graphify/anchor.d.ts.map +1 -1
  4. package/dist/graphify/anchor.js +41 -0
  5. package/dist/graphify/anchor.js.map +1 -1
  6. package/dist/graphify/consolidated.d.ts +40 -1
  7. package/dist/graphify/consolidated.d.ts.map +1 -1
  8. package/dist/graphify/construct.d.ts +23 -0
  9. package/dist/graphify/construct.d.ts.map +1 -0
  10. package/dist/graphify/construct.js +77 -0
  11. package/dist/graphify/construct.js.map +1 -0
  12. package/dist/graphify/index.d.ts +2 -2
  13. package/dist/graphify/index.d.ts.map +1 -1
  14. package/dist/graphify/index.js +1 -1
  15. package/dist/graphify/index.js.map +1 -1
  16. package/dist/index.d.ts +7 -7
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/pierre/PierreWalkthroughCodeView.d.ts +22 -0
  21. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -0
  22. package/dist/pierre/PierreWalkthroughCodeView.js +198 -0
  23. package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -0
  24. package/dist/pierre/index.d.ts +2 -2
  25. package/dist/pierre/index.js +1 -1
  26. package/dist/pierre/pierreFileLang.d.ts +1 -1
  27. package/dist/pierre/pierreFileLang.js +1 -1
  28. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +14 -2
  29. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  30. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +86 -20
  31. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  32. package/dist/subsystem/ComponentDeclaration.js +1 -1
  33. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  34. package/dist/subsystem/FileDrawer.d.ts +2 -2
  35. package/dist/subsystem/FileDrawer.js +2 -2
  36. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -24
  37. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  38. package/dist/subsystem/SubsystemComponentGraph.js +220 -194
  39. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  40. package/dist/subsystem/formatDeclaration.d.ts +2 -1
  41. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  42. package/dist/subsystem/formatDeclaration.js +57 -33
  43. package/dist/subsystem/formatDeclaration.js.map +1 -1
  44. package/dist/subsystem/model.d.ts +109 -42
  45. package/dist/subsystem/model.d.ts.map +1 -1
  46. package/dist/subsystem/model.js +107 -5
  47. package/dist/subsystem/model.js.map +1 -1
  48. package/dist/subsystem/nodes.d.ts.map +1 -1
  49. package/dist/subsystem/nodes.js +43 -18
  50. package/dist/subsystem/nodes.js.map +1 -1
  51. package/dist/subsystem/tokenizeComponent.js +2 -2
  52. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  53. package/package.json +3 -3
  54. package/src/graphify/anchor.test.ts +31 -2
  55. package/src/graphify/anchor.ts +46 -0
  56. package/src/graphify/consolidated.ts +43 -1
  57. package/src/graphify/{kind.test.ts → construct.test.ts} +20 -20
  58. package/src/graphify/{kind.ts → construct.ts} +17 -17
  59. package/src/graphify/index.ts +5 -2
  60. package/src/index.ts +20 -9
  61. package/src/pierre/{PierreThroughlineCodeView.tsx → PierreWalkthroughCodeView.tsx} +39 -47
  62. package/src/pierre/index.ts +2 -2
  63. package/src/pierre/pierreFileLang.ts +1 -1
  64. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  65. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +14 -14
  66. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +13 -15
  67. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
  68. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +16 -20
  69. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +11 -10
  70. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +13 -14
  71. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +31 -66
  72. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +2 -2
  73. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  74. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  75. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +254 -0
  76. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +518 -33
  77. package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +13 -13
  78. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +105 -21
  79. package/src/subsystem/ComponentDeclaration.tsx +1 -1
  80. package/src/subsystem/FileDrawer.tsx +2 -2
  81. package/src/subsystem/SubsystemComponentGraph.tsx +284 -254
  82. package/src/subsystem/formatDeclaration.test.ts +76 -1
  83. package/src/subsystem/formatDeclaration.ts +57 -33
  84. package/src/subsystem/model.test.ts +49 -15
  85. package/src/subsystem/model.ts +210 -51
  86. package/src/subsystem/nodes.tsx +63 -22
  87. package/src/subsystem/tokenizeComponent.ts +2 -2
@@ -17,7 +17,7 @@ import { ReactFlow, ReactFlowProvider, Background, BackgroundVariant, Controls,
17
17
  import { useTheme } from '@principal-ade/industry-theme';
18
18
  import { ChevronDown, ChevronUp, Pause, Play, X } from 'lucide-react';
19
19
  import { IndustryMarkdownSlide } from 'themed-markdown';
20
- import { buildSubsystemGraph, MECHANISM_COLOR, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, } from './model';
20
+ import { buildSubsystemGraph, deriveGraphEdges, MECHANISM_COLOR, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
21
21
  import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
22
22
  import { SubsystemFileTree } from './SubsystemFileTree';
23
23
  import { GraphLayoutCover } from './GraphLayoutCover';
@@ -29,8 +29,8 @@ const EDGE_LABEL_MAX_EDGE_FRACTION = 0.55;
29
29
  /** Rough monospace width at fontSize 10 + horizontal padding/border. */
30
30
  const EDGE_LABEL_CHAR_PX = 6.2;
31
31
  const EDGE_LABEL_PAD_PX = 18;
32
- /** Pause (ms) between steps when a throughline autoplays. */
33
- const THROUGHLINE_PLAY_PAUSE_MS = 2500;
32
+ /** Pause (ms) between steps when a walkthrough autoplays. */
33
+ const WALKTHROUGH_PLAY_PAUSE_MS = 2500;
34
34
  const nodeTypes = {
35
35
  'subsystem-component': SubsystemComponentNode,
36
36
  'subsystem-group': SubsystemGroupNode,
@@ -45,20 +45,21 @@ const edgeTypes = {
45
45
  const FileDrawerContent = memo(function FileDrawerContent({ render, file, startLine, }) {
46
46
  return _jsx(_Fragment, { children: render(file, startLine != null ? { startLine } : undefined) });
47
47
  });
48
- const ThroughlineDrawerContent = memo(function ThroughlineDrawerContent({ render, throughline, stepIndex, }) {
49
- return _jsx(_Fragment, { children: render({ throughline, stepIndex }) });
48
+ const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render, walkthrough, stepIndex, }) {
49
+ return _jsx(_Fragment, { children: render({ walkthrough, stepIndex }) });
50
50
  });
51
- function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, title, hideSidebar, throughlineStepMode = 'focus', autoPlayThroughlines = false, throughlineAutoPlayIntervalMs = THROUGHLINE_PLAY_PAUSE_MS, zoomOnThroughlineFocus = true, graphTitle, showThroughlineTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, renderThroughlineViewer, onFileSelect, onVerifyComponent, componentVerification }) {
51
+ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification }) {
52
52
  const { theme } = useTheme();
53
53
  const { fitView } = useReactFlow();
54
54
  const viewport = useViewport();
55
+ const graphEdges = useMemo(() => deriveGraphEdges({ relations, walkthroughs }), [relations, walkthroughs]);
55
56
  const [built, setBuilt] = useState({
56
57
  nodes: [],
57
58
  edges: [],
58
59
  });
59
60
  const [layoutReady, setLayoutReady] = useState(false);
60
61
  const [selected, setSelected] = useState(null);
61
- /** Bottom drawer: single file or throughline multi-snippet mode. */
62
+ /** Bottom drawer: single file or walkthrough multi-snippet mode. */
62
63
  const [drawerTarget, setDrawerTarget] = useState(null);
63
64
  // Mirror so step-focus can decide whether to wait for the drawer open
64
65
  // transition before fitView (avoids framing against full-height canvas).
@@ -68,22 +69,22 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
68
69
  const pendingFocusFitRef = useRef(null);
69
70
  // Component the pointer is over (null on leave) → transient tree highlight.
70
71
  const [hoveredComponentId, setHoveredComponentId] = useState(null);
71
- // Throughline focus — selected flow (or step) is full strength; other
72
+ // Walkthrough focus — selected flow (or step) is full strength; other
72
73
  // opened-flow members stay visible but dimmed; everything else is hidden.
73
- const [focusedThroughlineId, setFocusedThroughlineId] = useState(null);
74
+ const [focusedWalkthroughId, setFocusedWalkthroughId] = useState(null);
74
75
  // `null` = whole flow focused; a number = that single step's edge focused.
75
76
  const [focusedStepIndex, setFocusedStepIndex] = useState(null);
76
- // Hovered throughline in the flows panel: dims every canvas node/edge not
77
+ // Hovered walkthrough in the flows panel: dims every canvas node/edge not
77
78
  // involved in the hover preview (or selected ∪ hovered when a step is
78
79
  // focused). `stepIndex: null` = whole flow (collapsed title hover);
79
80
  // a number = that step. Transient — no drawer. Camera only reframes when
80
81
  // a step is already selected.
81
- const [hoveredThroughlineStep, setHoveredThroughlineStep] = useState(null);
82
- // Sidebar bottom half: which panel is shown when throughlines exist.
83
- const [sidebarView, setSidebarView] = useState(() => throughlines?.length ? 'flows' : 'files');
84
- // Throughline flows the user has expanded (via the title row). Closed by
82
+ const [hoveredWalkthroughStep, setHoveredWalkthroughStep] = useState(null);
83
+ // Sidebar bottom half: which panel is shown when walkthroughs exist.
84
+ const [sidebarView, setSidebarView] = useState(() => walkthroughs?.length ? 'walkthroughs' : 'files');
85
+ // Walkthrough flows the user has expanded (via the title row). Closed by
85
86
  // default so a graph with several flows doesn't dump every step list at once.
86
- const [expandedThroughlines, setExpandedThroughlines] = useState(new Set());
87
+ const [expandedWalkthroughs, setExpandedWalkthroughs] = useState(new Set());
87
88
  // Sidebar description visibility. Hidden by default so the files/flows
88
89
  // panel gets the vertical room; the title-row toggle reveals it.
89
90
  const [descriptionVisible, setDescriptionVisible] = useState(false);
@@ -137,40 +138,40 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
137
138
  drawerTarget?.kind === 'file'
138
139
  ? { file: drawerTarget.file, startLine: drawerTarget.startLine }
139
140
  : null;
140
- const focusedThroughline = useMemo(() => {
141
- if (drawerTarget?.kind !== 'throughline' || !throughlines)
141
+ const focusedWalkthrough = useMemo(() => {
142
+ if (drawerTarget?.kind !== 'walkthrough' || !walkthroughs)
142
143
  return null;
143
- return throughlines.find((t) => t.id === drawerTarget.throughlineId) ?? null;
144
- }, [drawerTarget, throughlines]);
145
- // Throughline shown on the canvas title chip (focus or hover/autoplay highlight).
146
- const overlayThroughlineTitle = useMemo(() => {
147
- if (!showThroughlineTitle || !throughlines?.length)
144
+ return walkthroughs.find((t) => t.id === drawerTarget.walkthroughId) ?? null;
145
+ }, [drawerTarget, walkthroughs]);
146
+ // Walkthrough shown on the canvas title chip (focus or hover/autoplay highlight).
147
+ const overlayWalkthroughTitle = useMemo(() => {
148
+ if (!showWalkthroughTitle || !walkthroughs?.length)
148
149
  return null;
149
- const id = focusedThroughlineId ?? hoveredThroughlineStep?.throughlineId;
150
+ const id = focusedWalkthroughId ?? hoveredWalkthroughStep?.walkthroughId;
150
151
  if (!id)
151
152
  return null;
152
- return throughlines.find((t) => t.id === id)?.title ?? null;
153
+ return walkthroughs.find((t) => t.id === id)?.title ?? null;
153
154
  }, [
154
- showThroughlineTitle,
155
- throughlines,
156
- focusedThroughlineId,
157
- hoveredThroughlineStep,
155
+ showWalkthroughTitle,
156
+ walkthroughs,
157
+ focusedWalkthroughId,
158
+ hoveredWalkthroughStep,
158
159
  ]);
159
160
  // Active step for the bottom-of-title progress + annotation chip.
160
- const overlayThroughlineStep = useMemo(() => {
161
- if (!showThroughlineTitle || !throughlines?.length)
161
+ const overlayWalkthroughStep = useMemo(() => {
162
+ if (!showWalkthroughTitle || !walkthroughs?.length)
162
163
  return null;
163
- const tlId = focusedThroughlineId ?? hoveredThroughlineStep?.throughlineId ?? null;
164
+ const tlId = focusedWalkthroughId ?? hoveredWalkthroughStep?.walkthroughId ?? null;
164
165
  let stepIndex = null;
165
- if (focusedThroughlineId != null) {
166
+ if (focusedWalkthroughId != null) {
166
167
  stepIndex = focusedStepIndex;
167
168
  }
168
- else if (hoveredThroughlineStep != null) {
169
- stepIndex = hoveredThroughlineStep.stepIndex;
169
+ else if (hoveredWalkthroughStep != null) {
170
+ stepIndex = hoveredWalkthroughStep.stepIndex;
170
171
  }
171
172
  if (tlId == null || stepIndex == null)
172
173
  return null;
173
- const tl = throughlines.find((t) => t.id === tlId);
174
+ const tl = walkthroughs.find((t) => t.id === tlId);
174
175
  const step = tl?.steps[stepIndex];
175
176
  if (!step || !tl)
176
177
  return null;
@@ -180,18 +181,18 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
180
181
  annotation: step.annotation,
181
182
  };
182
183
  }, [
183
- showThroughlineTitle,
184
- throughlines,
185
- focusedThroughlineId,
184
+ showWalkthroughTitle,
185
+ walkthroughs,
186
+ focusedWalkthroughId,
186
187
  focusedStepIndex,
187
- hoveredThroughlineStep,
188
+ hoveredWalkthroughStep,
188
189
  ]);
189
190
  const drawerTitle = useMemo(() => {
190
191
  if (!drawerTarget)
191
192
  return null;
192
193
  if (drawerTarget.kind === 'file')
193
194
  return drawerTarget.file;
194
- const tl = focusedThroughline;
195
+ const tl = focusedWalkthrough;
195
196
  if (!tl)
196
197
  return null;
197
198
  if (drawerTarget.stepIndex == null)
@@ -201,7 +202,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
201
202
  return tl.title;
202
203
  const site = `${step.file.split('/').pop() ?? step.file}:${step.line}`;
203
204
  return `${tl.title} · ${site}`;
204
- }, [drawerTarget, focusedThroughline]);
205
+ }, [drawerTarget, focusedWalkthrough]);
205
206
  // Refresh selected component when the components list updates (e.g. verify
206
207
  // writes back declarationRef).
207
208
  useEffect(() => {
@@ -214,11 +215,13 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
214
215
  // Pass 1: build with estimated widths so React Flow can measure the DOM.
215
216
  // The pane stays hidden until Pass 2 completes. Key off layout-affecting
216
217
  // fields only — declarationRef updates after verify must not re-run ELK.
217
- const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, edges }), [components, edges]);
218
+ const layoutKey = useMemo(() => subsystemGraphLayoutKey({ components, relations, walkthroughs }), [components, relations, walkthroughs]);
218
219
  const componentsRef = useRef(components);
219
- const edgesRef = useRef(edges);
220
+ const relationsRef = useRef(relations);
221
+ const walkthroughsRef = useRef(walkthroughs);
220
222
  componentsRef.current = components;
221
- edgesRef.current = edges;
223
+ relationsRef.current = relations;
224
+ walkthroughsRef.current = walkthroughs;
222
225
  // Track measured dimensions from React Flow's dimension changes.
223
226
  // These arrive as { type: 'dimensions', id, dimensions } in onNodesChange.
224
227
  // Retained across same-id layoutKey updates so Pass 2 can run without a remount.
@@ -233,7 +236,11 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
233
236
  prevMeasuredSigRef.current = '';
234
237
  // Bump generation so any in-flight Pass 2 from the prior layoutKey is ignored.
235
238
  pass2GenRef.current += 1;
236
- const doc = { components: componentsRef.current, edges: edgesRef.current };
239
+ const doc = {
240
+ components: componentsRef.current,
241
+ relations: relationsRef.current,
242
+ walkthroughs: walkthroughsRef.current,
243
+ };
237
244
  void buildSubsystemGraph(doc, { maxNodeWidth, showEdgeLabels })
238
245
  .then(({ nodes, edges: e }) => {
239
246
  if (!alive)
@@ -284,7 +291,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
284
291
  const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
285
292
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
286
293
  const gen = ++pass2GenRef.current;
287
- void buildSubsystemGraph({ components, edges }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights })
294
+ void buildSubsystemGraph({ components, relations, walkthroughs }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights })
288
295
  .then(({ nodes, edges: e }) => {
289
296
  if (gen !== pass2GenRef.current)
290
297
  return;
@@ -298,7 +305,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
298
305
  // Reveal Pass 1 layout rather than leaving the cover up forever.
299
306
  setLayoutReady(true);
300
307
  });
301
- }, [built.nodes, components, edges, maxNodeWidth, showEdgeLabels]);
308
+ }, [built.nodes, components, relations, walkthroughs, maxNodeWidth, showEdgeLabels]);
302
309
  // After Pass 1 commits, try Pass 2 immediately with retained measurements.
303
310
  // Same-id live updates often get no new React Flow `dimensions` events, so
304
311
  // waiting only on onNodesChange leaves the cover stuck.
@@ -329,7 +336,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
329
336
  // Node components call SUBSYSTEM_CALLBACKS.onSelect on click (their inner
330
337
  // onClick stops React Flow propagation), so wire selection + width through it.
331
338
  // Lookup from edge id → the authored SubsystemComponentEdge (for onEdgeSelect).
332
- const edgeById = useMemo(() => new Map(edges.map((e) => [e.id, e])), [edges]);
339
+ const edgeById = useMemo(() => new Map(graphEdges.map((e) => [e.id, e])), [graphEdges]);
333
340
  // Shared edge-selection logic used by both React Flow's onEdgeClick and the
334
341
  // clickable edge label (SUBSYSTEM_CALLBACKS.onEdgeSelect).
335
342
  const selectEdge = useCallback((edgeId) => {
@@ -339,8 +346,8 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
339
346
  }
340
347
  const src = edgeById.get(edgeId);
341
348
  setSelectedEdgeId(edgeId);
342
- // A direct edge selection on the canvas supersedes any throughline focus.
343
- setFocusedThroughlineId(null);
349
+ // A direct edge selection on the canvas supersedes any walkthrough focus.
350
+ setFocusedWalkthroughId(null);
344
351
  setFocusedStepIndex(null);
345
352
  if (src)
346
353
  onEdgeSelect?.(src);
@@ -358,7 +365,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
358
365
  }
359
366
  setSelected(comp);
360
367
  setSelectedEdgeId(null);
361
- setFocusedThroughlineId(null);
368
+ setFocusedWalkthroughId(null);
362
369
  setFocusedStepIndex(null);
363
370
  onSelect?.(id);
364
371
  }
@@ -382,50 +389,50 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
382
389
  // without a stale closure value.
383
390
  const selectedEdgeIdRef = useRef(null);
384
391
  selectedEdgeIdRef.current = selectedEdgeId;
385
- // Edge ids in throughline focus (an active flow's edge set, or a single
386
- // step's edge). Used to frame the camera. `null` = no throughline focus.
392
+ // Edge ids in walkthrough focus (an active flow's edge set, or a single
393
+ // step's edge). Used to frame the camera. `null` = no walkthrough focus.
387
394
  const focusEdgeIds = useMemo(() => {
388
- if (focusedThroughlineId == null || !throughlines)
395
+ if (focusedWalkthroughId == null || !walkthroughs)
389
396
  return null;
390
- const tl = throughlines.find((t) => t.id === focusedThroughlineId);
397
+ const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
391
398
  if (!tl)
392
399
  return null;
393
400
  if (focusedStepIndex != null) {
394
401
  const step = tl.steps[focusedStepIndex];
395
- return step ? new Set([step.edgeId]) : null;
402
+ return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
396
403
  }
397
- return new Set(tl.steps.map((s) => s.edgeId));
398
- }, [throughlines, focusedThroughlineId, focusedStepIndex]);
404
+ return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
405
+ }, [walkthroughs, focusedWalkthroughId, focusedStepIndex]);
399
406
  // 1-based step numbers per edge of the active flow (focused or
400
407
  // hover/autoplay-highlighted). An edge can appear in more than one step.
401
408
  const selectedFlowStepNos = useMemo(() => {
402
- const activeId = focusedThroughlineId ?? hoveredThroughlineStep?.throughlineId;
403
- if (activeId == null || !throughlines)
409
+ const activeId = focusedWalkthroughId ?? hoveredWalkthroughStep?.walkthroughId;
410
+ if (activeId == null || !walkthroughs)
404
411
  return null;
405
- const tl = throughlines.find((t) => t.id === activeId);
412
+ const tl = walkthroughs.find((t) => t.id === activeId);
406
413
  if (!tl)
407
414
  return null;
408
415
  const map = new Map();
409
416
  tl.steps.forEach((s, i) => {
410
- const list = map.get(s.edgeId) ?? [];
417
+ const list = map.get(walkthroughStepGraphEdgeId(s)) ?? [];
411
418
  list.push(i + 1);
412
- map.set(s.edgeId, list);
419
+ map.set(walkthroughStepGraphEdgeId(s), list);
413
420
  });
414
421
  return map;
415
- }, [throughlines, focusedThroughlineId, hoveredThroughlineStep]);
416
- // Union of every expanded (opened) throughline's edges — the visible set.
422
+ }, [walkthroughs, focusedWalkthroughId, hoveredWalkthroughStep]);
423
+ // Union of every expanded (opened) walkthrough's edges — the visible set.
417
424
  const openedEdgeIds = useMemo(() => {
418
- if (!throughlines || expandedThroughlines.size === 0)
425
+ if (!walkthroughs || expandedWalkthroughs.size === 0)
419
426
  return null;
420
427
  const ids = new Set();
421
- for (const tl of throughlines) {
422
- if (!expandedThroughlines.has(tl.id))
428
+ for (const tl of walkthroughs) {
429
+ if (!expandedWalkthroughs.has(tl.id))
423
430
  continue;
424
431
  for (const s of tl.steps)
425
- ids.add(s.edgeId);
432
+ ids.add(walkthroughStepGraphEdgeId(s));
426
433
  }
427
434
  return ids.size > 0 ? ids : null;
428
- }, [throughlines, expandedThroughlines]);
435
+ }, [walkthroughs, expandedWalkthroughs]);
429
436
  const endpointsOf = (edgeIds) => {
430
437
  if (!edgeIds)
431
438
  return null;
@@ -440,17 +447,17 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
440
447
  };
441
448
  // Edge/nodes involved in the hovered step or whole flow.
442
449
  const hoverEdgeIds = useMemo(() => {
443
- if (!hoveredThroughlineStep || !throughlines)
450
+ if (!hoveredWalkthroughStep || !walkthroughs)
444
451
  return null;
445
- const tl = throughlines.find((t) => t.id === hoveredThroughlineStep.throughlineId);
452
+ const tl = walkthroughs.find((t) => t.id === hoveredWalkthroughStep.walkthroughId);
446
453
  if (!tl)
447
454
  return null;
448
- if (hoveredThroughlineStep.stepIndex == null) {
449
- return new Set(tl.steps.map((s) => s.edgeId));
455
+ if (hoveredWalkthroughStep.stepIndex == null) {
456
+ return new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s)));
450
457
  }
451
- const step = tl.steps[hoveredThroughlineStep.stepIndex];
452
- return step ? new Set([step.edgeId]) : null;
453
- }, [throughlines, hoveredThroughlineStep]);
458
+ const step = tl.steps[hoveredWalkthroughStep.stepIndex];
459
+ return step ? new Set([walkthroughStepGraphEdgeId(step)]) : null;
460
+ }, [walkthroughs, hoveredWalkthroughStep]);
454
461
  // While hovering with a *step* already selected, brighten the union of
455
462
  // selected + hovered participants. Whole-flow focus (or no focus) keeps
456
463
  // the old hover-replace preview so a step hover still dims the rest.
@@ -497,8 +504,8 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
497
504
  // React Flow's own selection state from updating.
498
505
  const dispNodes = useMemo(() => {
499
506
  // While a step/flow is hovered, selected + hovered participants stay
500
- // bright; everything else among the currently-visible (opened-throughline)
501
- // nodes is dimmed. Nodes outside the opened-throughline set stay hidden.
507
+ // bright; everything else among the currently-visible (opened-walkthrough)
508
+ // nodes is dimmed. Nodes outside the opened-walkthrough set stay hidden.
502
509
  return xyflowNodesBase.map((n) => {
503
510
  // Boundary frames follow their members: hidden when no member is
504
511
  // visible, dimmed when members are dimmed. Never selectable.
@@ -613,7 +620,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
613
620
  const onNodeClick = useCallback((_e, node) => {
614
621
  const comp = node.data?.component;
615
622
  setSelectedEdgeId(null);
616
- setFocusedThroughlineId(null);
623
+ setFocusedWalkthroughId(null);
617
624
  setFocusedStepIndex(null);
618
625
  if (node.type === 'subsystem-component' && comp) {
619
626
  // Clicking the already-selected node unselects it (toggle off).
@@ -634,13 +641,13 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
634
641
  const onPaneClick = useCallback(() => {
635
642
  setSelected(null);
636
643
  setSelectedEdgeId(null);
637
- setFocusedThroughlineId(null);
644
+ setFocusedWalkthroughId(null);
638
645
  setFocusedStepIndex(null);
639
646
  }, []);
640
647
  // Sidebar file trees — one per repo on multi-repo graphs, each under its
641
648
  // own owner-avatar header. Clicking a header collapses that repo's tree.
642
649
  const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
643
- const hasThroughlines = useMemo(() => (throughlines?.length ?? 0) > 0, [throughlines]);
650
+ const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
644
651
  const [collapsedRepos, setCollapsedRepos] = useState(new Set());
645
652
  const toggleRepoCollapsed = useCallback((key) => {
646
653
  setCollapsedRepos((prev) => {
@@ -671,12 +678,12 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
671
678
  setDrawerTarget({ kind: 'file', file, startLine });
672
679
  onFileSelect?.(file);
673
680
  }, [onFileSelect]);
674
- // Camera helper shared by the throughline interactions: frames the focused
681
+ // Camera helper shared by the walkthrough interactions: frames the focused
675
682
  // edges' endpoint nodes via `fitView({ nodes })`, which uses the store's
676
683
  // live positions + measured dims — so the frame always includes BOTH
677
684
  // components the edge attaches to (and therefore the edge line between them).
678
685
  const fitFocusBounds = useCallback((ids) => {
679
- if (!zoomOnThroughlineFocus)
686
+ if (!zoomOnWalkthroughFocus)
680
687
  return;
681
688
  const nodeIds = new Set();
682
689
  for (const e of baseEdges) {
@@ -692,11 +699,11 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
692
699
  padding: 0.25,
693
700
  duration: 300,
694
701
  });
695
- }, [baseEdges, fitView, zoomOnThroughlineFocus]);
696
- // Zoom back out to the full diagram after the last expanded throughline
702
+ }, [baseEdges, fitView, zoomOnWalkthroughFocus]);
703
+ // Zoom back out to the full diagram after the last expanded walkthrough
697
704
  // closes (visibility restores non-flow nodes that were hidden).
698
705
  const fitOverview = useCallback(() => {
699
- if (!zoomOnThroughlineFocus)
706
+ if (!zoomOnWalkthroughFocus)
700
707
  return;
701
708
  fitView({
702
709
  padding: 0.1,
@@ -705,62 +712,77 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
705
712
  maxZoom: 2,
706
713
  duration: 300,
707
714
  });
708
- }, [fitView, zoomOnThroughlineFocus]);
715
+ }, [fitView, zoomOnWalkthroughFocus]);
709
716
  // Focus an entire flow: hide everything but the flow's nodes and edges, and
710
717
  // frame the flow on the canvas. Selection state is cleared — the graph now
711
718
  // reads as the narrative. No drawer: the code view only opens on a step
712
- // click. A stale throughline drawer (from a previously focused flow's step)
719
+ // click. A stale walkthrough drawer (from a previously focused flow's step)
713
720
  // closes; an explicitly opened file drawer stays.
714
721
  // In `dim` mode: clear step highlight and leave the full graph visible
715
722
  // (no zoom / hide) — matching "step away from a hovered step".
716
- const focusThroughlineEdges = useCallback((tl) => {
723
+ const focusWalkthroughEdges = useCallback((tl) => {
717
724
  setSelected(null);
718
725
  setSelectedEdgeId(null);
719
- setHoveredThroughlineStep(null);
720
- if (throughlineStepMode === 'dim') {
726
+ setHoveredWalkthroughStep(null);
727
+ if (walkthroughStepMode === 'dim') {
721
728
  setFocusedStepIndex(null);
722
- setFocusedThroughlineId(null);
723
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
729
+ setFocusedWalkthroughId(null);
730
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
724
731
  return;
725
732
  }
726
733
  setFocusedStepIndex(null);
727
- setFocusedThroughlineId(tl.id);
728
- fitFocusBounds(new Set(tl.steps.map((s) => s.edgeId)));
729
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
730
- }, [fitFocusBounds, throughlineStepMode]);
731
- const clearThroughlineFocus = useCallback(() => {
732
- setFocusedThroughlineId(null);
734
+ setFocusedWalkthroughId(tl.id);
735
+ fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
736
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
737
+ }, [fitFocusBounds, walkthroughStepMode]);
738
+ const clearWalkthroughFocus = useCallback(() => {
739
+ setFocusedWalkthroughId(null);
733
740
  setFocusedStepIndex(null);
734
- setHoveredThroughlineStep(null);
735
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
741
+ setHoveredWalkthroughStep(null);
742
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
736
743
  }, []);
737
- // Focus a single step's edge on the canvas and open/scroll the throughline
738
- // drawer to that step's snippet.
744
+ // Focus a single step's edge on the canvas and open/scroll the walkthrough
745
+ // drawer to that step's snippet. Clicking the already-focused step clears
746
+ // step focus and returns to whole-flow (high-level) framing.
739
747
  // In `dim` mode: only dim non-participants (same as hovering a step) —
740
748
  // no camera move, no drawer, no hide.
741
- const focusThroughlineStep = useCallback((tl, stepIndex) => {
749
+ const focusWalkthroughStep = useCallback((tl, stepIndex) => {
742
750
  const step = tl.steps[stepIndex];
743
751
  if (!step)
744
752
  return;
745
753
  setSelected(null);
746
754
  setSelectedEdgeId(null);
747
- if (throughlineStepMode === 'dim') {
755
+ if (walkthroughStepMode === 'dim') {
756
+ setFocusedStepIndex(null);
757
+ setFocusedWalkthroughId(null);
758
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
759
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
760
+ return;
761
+ }
762
+ // Toggle: clicking the selected step unselects it (back to whole flow).
763
+ if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
748
764
  setFocusedStepIndex(null);
749
- setFocusedThroughlineId(null);
750
- setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex });
751
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
765
+ // Pointer is still over the row — keep hover preview so dimming doesn't
766
+ // flash off until mouseleave.
767
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
768
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
769
+ if (pendingFocusFitRef.current != null) {
770
+ window.clearTimeout(pendingFocusFitRef.current);
771
+ pendingFocusFitRef.current = null;
772
+ }
773
+ fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
752
774
  return;
753
775
  }
754
776
  setFocusedStepIndex(stepIndex);
755
- setFocusedThroughlineId(tl.id);
756
- setHoveredThroughlineStep(null);
777
+ setFocusedWalkthroughId(tl.id);
778
+ setHoveredWalkthroughStep(null);
757
779
  // Open the drawer before fitting. If it was closed, wait for its height
758
780
  // transition so fitView uses the reduced canvas — not full height.
759
781
  const drawerWasOpen = drawerOpenRef.current;
760
- if (renderThroughlineViewer) {
782
+ if (renderWalkthroughViewer) {
761
783
  setDrawerTarget({
762
- kind: 'throughline',
763
- throughlineId: tl.id,
784
+ kind: 'walkthrough',
785
+ walkthroughId: tl.id,
764
786
  stepIndex,
765
787
  });
766
788
  }
@@ -771,7 +793,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
771
793
  startLine: step.line,
772
794
  });
773
795
  }
774
- const edgeIds = new Set([step.edgeId]);
796
+ const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
775
797
  if (pendingFocusFitRef.current != null) {
776
798
  window.clearTimeout(pendingFocusFitRef.current);
777
799
  pendingFocusFitRef.current = null;
@@ -785,7 +807,13 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
785
807
  requestAnimationFrame(() => fitFocusBounds(edgeIds));
786
808
  }, FILE_DRAWER_HEIGHT_MS + 20);
787
809
  }
788
- }, [fitFocusBounds, renderThroughlineViewer, throughlineStepMode]);
810
+ }, [
811
+ fitFocusBounds,
812
+ focusedStepIndex,
813
+ focusedWalkthroughId,
814
+ renderWalkthroughViewer,
815
+ walkthroughStepMode,
816
+ ]);
789
817
  useEffect(() => {
790
818
  return () => {
791
819
  if (pendingFocusFitRef.current != null) {
@@ -802,10 +830,10 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
802
830
  useEffect(() => {
803
831
  const prev = prevPreviewEdgeIdsRef.current;
804
832
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
805
- if (!zoomOnThroughlineFocus || throughlineStepMode === 'dim')
833
+ if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim')
806
834
  return;
807
835
  // Camera follows hover only when a specific step is already focused.
808
- if (focusedThroughlineId == null || focusedStepIndex == null)
836
+ if (focusedWalkthroughId == null || focusedStepIndex == null)
809
837
  return;
810
838
  if (previewEdgeIds) {
811
839
  fitFocusBounds(previewEdgeIds);
@@ -817,33 +845,33 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
817
845
  }, [
818
846
  previewEdgeIds,
819
847
  focusEdgeIds,
820
- focusedThroughlineId,
848
+ focusedWalkthroughId,
821
849
  focusedStepIndex,
822
850
  fitFocusBounds,
823
- zoomOnThroughlineFocus,
824
- throughlineStepMode,
851
+ zoomOnWalkthroughFocus,
852
+ walkthroughStepMode,
825
853
  ]);
826
- // Graph-only embeds: cycle throughline steps with hover-style dimming.
854
+ // Graph-only embeds: cycle walkthrough steps with hover-style dimming.
827
855
  useEffect(() => {
828
- if (!autoPlayThroughlines || !throughlines?.length || !layoutReady)
856
+ if (!autoPlayWalkthroughs || !walkthroughs?.length || !layoutReady)
829
857
  return;
830
- const playable = throughlines.filter((tl) => tl.steps.length > 0);
858
+ const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
831
859
  if (playable.length === 0)
832
860
  return;
833
861
  let cancelled = false;
834
862
  let tlIdx = 0;
835
863
  let stepIdx = 0;
836
864
  let timer = null;
837
- const interval = Math.max(400, throughlineAutoPlayIntervalMs);
865
+ const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
838
866
  const tick = () => {
839
867
  if (cancelled)
840
868
  return;
841
869
  const tl = playable[tlIdx];
842
870
  setSelected(null);
843
871
  setSelectedEdgeId(null);
844
- setFocusedThroughlineId(null);
872
+ setFocusedWalkthroughId(null);
845
873
  setFocusedStepIndex(null);
846
- setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex: stepIdx });
874
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
847
875
  stepIdx += 1;
848
876
  if (stepIdx >= tl.steps.length) {
849
877
  stepIdx = 0;
@@ -856,16 +884,16 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
856
884
  cancelled = true;
857
885
  if (timer != null)
858
886
  window.clearTimeout(timer);
859
- setHoveredThroughlineStep(null);
887
+ setHoveredWalkthroughStep(null);
860
888
  };
861
- }, [autoPlayThroughlines, throughlines, throughlineAutoPlayIntervalMs, layoutReady]);
862
- // Arrow keys step through the focused throughline once a step is active
889
+ }, [autoPlayWalkthroughs, walkthroughs, walkthroughAutoPlayIntervalMs, layoutReady]);
890
+ // Arrow keys step through the focused walkthrough once a step is active
863
891
  // (sidebar click or drawer open). Ignores typing targets and chords.
864
892
  useEffect(() => {
865
- if (focusedThroughlineId == null || focusedStepIndex == null || !throughlines) {
893
+ if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
866
894
  return;
867
895
  }
868
- const tl = throughlines.find((t) => t.id === focusedThroughlineId);
896
+ const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
869
897
  if (!tl || tl.steps.length === 0)
870
898
  return;
871
899
  const onKeyDown = (e) => {
@@ -892,19 +920,19 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
892
920
  if (next === focusedStepIndex)
893
921
  return;
894
922
  e.preventDefault();
895
- focusThroughlineStep(tl, next);
923
+ focusWalkthroughStep(tl, next);
896
924
  };
897
925
  window.addEventListener('keydown', onKeyDown);
898
926
  return () => window.removeEventListener('keydown', onKeyDown);
899
927
  }, [
900
- focusedThroughlineId,
928
+ focusedWalkthroughId,
901
929
  focusedStepIndex,
902
- throughlines,
903
- focusThroughlineStep,
930
+ walkthroughs,
931
+ focusWalkthroughStep,
904
932
  ]);
905
- const toggleThroughlineCollapsed = useCallback((tlId) => {
906
- const collapsingLast = expandedThroughlines.has(tlId) && expandedThroughlines.size === 1;
907
- setExpandedThroughlines((prev) => {
933
+ const toggleWalkthroughCollapsed = useCallback((tlId) => {
934
+ const collapsingLast = expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
935
+ setExpandedWalkthroughs((prev) => {
908
936
  const next = new Set(prev);
909
937
  if (next.has(tlId))
910
938
  next.delete(tlId);
@@ -912,7 +940,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
912
940
  next.add(tlId);
913
941
  return next;
914
942
  });
915
- if (!collapsingLast || !zoomOnThroughlineFocus)
943
+ if (!collapsingLast || !zoomOnWalkthroughFocus)
916
944
  return;
917
945
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
918
946
  if (pendingFocusFitRef.current != null) {
@@ -927,7 +955,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
927
955
  pendingFocusFitRef.current = null;
928
956
  requestAnimationFrame(() => fitOverview());
929
957
  }, drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20);
930
- }, [expandedThroughlines, fitOverview, zoomOnThroughlineFocus]);
958
+ }, [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus]);
931
959
  // Filename-badge clicks on nodes open the drawer through the same path as
932
960
  // the declaration panel's file link (toggle + tree sync, no start line).
933
961
  useEffect(() => {
@@ -952,7 +980,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
952
980
  return;
953
981
  setSelected(comp);
954
982
  setSelectedEdgeId(null);
955
- setFocusedThroughlineId(null);
983
+ setFocusedWalkthroughId(null);
956
984
  setFocusedStepIndex(null);
957
985
  onSelect?.(comp.id);
958
986
  }, [components, onSelect]);
@@ -1000,10 +1028,10 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1000
1028
  const fileViewerRef = useRef(fileViewer);
1001
1029
  fileViewerRef.current = fileViewer;
1002
1030
  const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
1003
- const throughlineViewerRef = useRef(renderThroughlineViewer);
1004
- throughlineViewerRef.current = renderThroughlineViewer;
1005
- const renderThroughlineDrawerContent = useCallback((ctx) => throughlineViewerRef.current?.(ctx) ?? null, []);
1006
- return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasThroughlines) && (_jsxs("div", { style: {
1031
+ const walkthroughViewerRef = useRef(renderWalkthroughViewer);
1032
+ walkthroughViewerRef.current = renderWalkthroughViewer;
1033
+ const renderWalkthroughDrawerContent = useCallback((ctx) => walkthroughViewerRef.current?.(ctx) ?? null, []);
1034
+ return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1007
1035
  width: sidebarWidth,
1008
1036
  minWidth: sidebarWidth,
1009
1037
  borderRight: `1px solid ${theme.colors.border}`,
@@ -1041,7 +1069,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1041
1069
  color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
1042
1070
  cursor: 'pointer',
1043
1071
  transition: 'background 120ms ease, color 120ms ease',
1044
- }, children: descriptionVisible ? _jsx(ChevronUp, { size: 14 }) : _jsx(ChevronDown, { size: 14 }) }))] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, fontSizeScale: 0.9, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasThroughlines) && (_jsxs("div", { style: {
1072
+ }, children: descriptionVisible ? _jsx(ChevronUp, { size: 14 }) : _jsx(ChevronDown, { size: 14 }) }))] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, fontSizeScale: 0.9, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1045
1073
  ...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
1046
1074
  minHeight: 160,
1047
1075
  flexShrink: 0,
@@ -1049,13 +1077,13 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1049
1077
  display: 'flex',
1050
1078
  flexDirection: 'column',
1051
1079
  overflow: 'hidden',
1052
- }, children: [hasThroughlines && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1080
+ }, children: [hasWalkthroughs && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1053
1081
  display: 'flex',
1054
1082
  width: '100%',
1055
1083
  flexShrink: 0,
1056
1084
  borderBottom: `1px solid ${theme.colors.border}`,
1057
1085
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
1058
- }, children: ['flows', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => setSidebarView(view), style: {
1086
+ }, children: ['walkthroughs', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => setSidebarView(view), style: {
1059
1087
  flex: 1,
1060
1088
  minWidth: 0,
1061
1089
  padding: '8px 8px',
@@ -1069,16 +1097,16 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1069
1097
  fontFamily: theme.fonts.monospace,
1070
1098
  textTransform: 'capitalize',
1071
1099
  cursor: 'pointer',
1072
- }, children: view }, view))) })), sidebarView === 'flows' && throughlines && throughlines.length > 0 ? (_jsx("div", { style: {
1100
+ }, children: view === 'walkthroughs' ? 'Walkthroughs' : 'Files' }, view))) })), sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (_jsx("div", { style: {
1073
1101
  flex: 1,
1074
1102
  minHeight: 0,
1075
1103
  overflowY: 'auto',
1076
1104
  padding: '0 0 12px',
1077
- }, children: throughlines.map((tl) => (_jsx(ThroughlineFlow, { throughline: tl, edges: edges, collapsed: !expandedThroughlines.has(tl.id), active: focusedThroughlineId === tl.id
1105
+ }, children: walkthroughs.map((tl) => (_jsx(WalkthroughFlow, { walkthrough: tl, collapsed: !expandedWalkthroughs.has(tl.id), active: focusedWalkthroughId === tl.id
1078
1106
  ? { stepIndex: focusedStepIndex }
1079
- : hoveredThroughlineStep?.throughlineId === tl.id
1080
- ? { stepIndex: hoveredThroughlineStep.stepIndex }
1081
- : null, onToggleCollapsed: toggleThroughlineCollapsed, onFocusFlow: focusThroughlineEdges, onClearFocus: clearThroughlineFocus, onFocusStep: focusThroughlineStep, onHoverStep: (tl, i) => setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredThroughlineStep(null) }, tl.id))) })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
1107
+ : hoveredWalkthroughStep?.walkthroughId === tl.id
1108
+ ? { stepIndex: hoveredWalkthroughStep.stepIndex }
1109
+ : null, onToggleCollapsed: toggleWalkthroughCollapsed, onFocusFlow: focusWalkthroughEdges, onClearFocus: clearWalkthroughFocus, onFocusStep: focusWalkthroughStep, onHoverStep: (tl, i) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredWalkthroughStep(null) }, tl.id))) })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
1082
1110
  const groupKey = group.repoKey ?? '__no-repo__';
1083
1111
  const collapsed = collapsedRepos.has(groupKey);
1084
1112
  return (_jsxs("div", { style: {
@@ -1088,7 +1116,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1088
1116
  flexDirection: 'column',
1089
1117
  borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
1090
1118
  }, children: [repoGroups.multiRepo && (_jsx(RepoGroupHeader, { group: group, collapsed: collapsed, onToggle: () => toggleRepoCollapsed(groupKey) })), !collapsed && (_jsx(SubsystemFileTree, { files: group.entries.map((e) => e.displayPath), selectedFile: selected?.file ?? openFile, hoveredFile: hoveredFile, onSelectFile: onTreeSelectFile, headerless: repoGroups.multiRepo }))] }, groupKey));
1091
- }) })) : null] }))] })), !hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasThroughlines) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
1119
+ }) })) : null] }))] })), !hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
1092
1120
  width: 3,
1093
1121
  flexShrink: 0,
1094
1122
  cursor: 'col-resize',
@@ -1151,7 +1179,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1151
1179
  flex: 1,
1152
1180
  minHeight: 0,
1153
1181
  background: theme.colors.background,
1154
- }, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 }), _jsx(Controls, { showZoom: true, showFitView: true, showInteractive: true })] }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayThroughlineTitle || overlayThroughlineStep) && (_jsxs("div", { style: {
1182
+ }, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 }), _jsx(Controls, { showZoom: true, showFitView: true, showInteractive: true })] }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayWalkthroughTitle || overlayWalkthroughStep) && (_jsxs("div", { style: {
1155
1183
  position: 'absolute',
1156
1184
  top: 10,
1157
1185
  left: '50%',
@@ -1177,9 +1205,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1177
1205
  border: `1px solid ${theme.colors.border}`,
1178
1206
  borderRadius: 6,
1179
1207
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
1180
- }, children: graphTitle })), overlayThroughlineTitle && (_jsxs("div", { style: {
1208
+ }, children: graphTitle })), overlayWalkthroughTitle && (_jsxs("div", { style: {
1181
1209
  maxWidth: '100%',
1182
- minWidth: overlayThroughlineStep ? 160 : undefined,
1210
+ minWidth: overlayWalkthroughStep ? 160 : undefined,
1183
1211
  display: 'flex',
1184
1212
  flexDirection: 'column',
1185
1213
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -1188,9 +1216,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1188
1216
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
1189
1217
  overflow: 'hidden',
1190
1218
  opacity: 0.95,
1191
- }, "aria-label": overlayThroughlineStep
1192
- ? `${overlayThroughlineTitle}, step ${overlayThroughlineStep.index} of ${overlayThroughlineStep.total}`
1193
- : overlayThroughlineTitle, children: [_jsx("div", { style: {
1219
+ }, "aria-label": overlayWalkthroughStep
1220
+ ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
1221
+ : overlayWalkthroughTitle, children: [_jsx("div", { style: {
1194
1222
  padding: '5px 14px',
1195
1223
  fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
1196
1224
  fontWeight: 600,
@@ -1200,14 +1228,14 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1200
1228
  textOverflow: 'ellipsis',
1201
1229
  whiteSpace: 'nowrap',
1202
1230
  textAlign: 'center',
1203
- }, children: overlayThroughlineTitle }), overlayThroughlineStep && overlayThroughlineStep.total > 0 && (_jsx("div", { style: {
1231
+ }, children: overlayWalkthroughTitle }), overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (_jsx("div", { style: {
1204
1232
  display: 'flex',
1205
1233
  gap: 3,
1206
1234
  padding: '0 6px 5px',
1207
- }, "aria-hidden": "true", children: Array.from({ length: overlayThroughlineStep.total }, (_, i) => {
1235
+ }, "aria-hidden": "true", children: Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
1208
1236
  const n = i + 1;
1209
- const active = n === overlayThroughlineStep.index;
1210
- const done = n < overlayThroughlineStep.index;
1237
+ const active = n === overlayWalkthroughStep.index;
1238
+ const done = n < overlayWalkthroughStep.index;
1211
1239
  return (_jsx("span", { style: {
1212
1240
  flex: 1,
1213
1241
  height: 2,
@@ -1220,7 +1248,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1220
1248
  opacity: active ? 1 : done ? 0.75 : 0.4,
1221
1249
  transition: 'opacity 120ms ease, background 120ms ease',
1222
1250
  } }, n));
1223
- }) }))] })), overlayThroughlineStep?.annotation && (_jsx("div", { style: {
1251
+ }) }))] })), overlayWalkthroughStep?.annotation && (_jsx("div", { style: {
1224
1252
  maxWidth: '100%',
1225
1253
  padding: '6px 14px',
1226
1254
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -1232,7 +1260,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1232
1260
  fontFamily: theme.fonts.body,
1233
1261
  color: theme.colors.textMuted ?? theme.colors.textSecondary,
1234
1262
  lineHeight: 1.35,
1235
- }, children: overlayThroughlineStep.annotation }))] })), selected && (_jsx("div", { style: {
1263
+ }, children: overlayWalkthroughStep.annotation }))] })), selected && (_jsx("div", { style: {
1236
1264
  position: 'absolute',
1237
1265
  top: 10,
1238
1266
  right: 10,
@@ -1244,9 +1272,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1244
1272
  border: `1px solid ${theme.colors.border}`,
1245
1273
  borderRadius: 8,
1246
1274
  boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
1247
- }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onVerify: onVerifyComponent, verification: componentVerification }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: () => setDrawerTarget(null), children: drawerTarget?.kind === 'throughline' &&
1248
- focusedThroughline &&
1249
- renderThroughlineViewer ? (_jsx(ThroughlineDrawerContent, { render: renderThroughlineDrawerContent, throughline: focusedThroughline, stepIndex: drawerTarget.stepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), _jsx(GraphLayoutCover, { revealed: layoutReady }), canvasOverlay] })] }));
1275
+ }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onVerify: onVerifyComponent, verification: componentVerification }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: () => setDrawerTarget(null), children: drawerTarget?.kind === 'walkthrough' &&
1276
+ focusedWalkthrough &&
1277
+ renderWalkthroughViewer ? (_jsx(WalkthroughDrawerContent, { render: renderWalkthroughDrawerContent, walkthrough: focusedWalkthrough, stepIndex: drawerTarget.stepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), _jsx(GraphLayoutCover, { revealed: layoutReady }), canvasOverlay] })] }));
1250
1278
  }
1251
1279
  /** Sidebar header for one repo's tree: owner avatar + repo name + file count.
1252
1280
  * Clicking toggles the tree's visibility. */
@@ -1288,15 +1316,14 @@ function RepoGroupHeader({ group, collapsed, onToggle, }) {
1288
1316
  whiteSpace: 'nowrap',
1289
1317
  }, children: label })] }));
1290
1318
  }
1291
- /** One collapsible throughline in the sidebar's flows panel. Clicking the
1319
+ /** One collapsible walkthrough in the sidebar's flows panel. Clicking the
1292
1320
  * title: closed → open + select; open and unselected → select; open and
1293
1321
  * selected → close + clear focus. The right-aligned close button collapses
1294
1322
  * without selecting. A step row focuses that step's edge. */
1295
- function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollapsed, onFocusFlow, onClearFocus, onFocusStep, onHoverStep, onHoverFlow, onLeaveStep, }) {
1323
+ function WalkthroughFlow({ walkthrough, collapsed, active, onToggleCollapsed, onFocusFlow, onClearFocus, onFocusStep, onHoverStep, onHoverFlow, onLeaveStep, }) {
1296
1324
  const { theme } = useTheme();
1297
1325
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
1298
1326
  const hoverBg = theme.colors.background;
1299
- const edgeById = useMemo(() => new Map(edges.map((e) => [e.id, e])), [edges]);
1300
1327
  const wholeFlowActive = active !== null && active.stepIndex === null;
1301
1328
  const [headerHover, setHeaderHover] = useState(false);
1302
1329
  const [closeHover, setCloseHover] = useState(false);
@@ -1318,10 +1345,10 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1318
1345
  useEffect(() => () => stopPlaying(), []);
1319
1346
  const startPlaying = useCallback(() => {
1320
1347
  if (collapsed)
1321
- onToggleCollapsed(throughline.id);
1348
+ onToggleCollapsed(walkthrough.id);
1322
1349
  if (active === null || active.stepIndex !== null)
1323
- onFocusFlow(throughline);
1324
- const stepCount = throughline.steps.length;
1350
+ onFocusFlow(walkthrough);
1351
+ const stepCount = walkthrough.steps.length;
1325
1352
  if (stepCount === 0)
1326
1353
  return;
1327
1354
  setPlaying(true);
@@ -1332,12 +1359,12 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1332
1359
  setPlaying(false);
1333
1360
  return;
1334
1361
  }
1335
- onFocusStep(throughline, i);
1362
+ onFocusStep(walkthrough, i);
1336
1363
  i += 1;
1337
- playTimerRef.current = window.setTimeout(tick, THROUGHLINE_PLAY_PAUSE_MS);
1364
+ playTimerRef.current = window.setTimeout(tick, WALKTHROUGH_PLAY_PAUSE_MS);
1338
1365
  };
1339
1366
  tick();
1340
- }, [collapsed, active, onToggleCollapsed, onFocusFlow, throughline, onFocusStep]);
1367
+ }, [collapsed, active, onToggleCollapsed, onFocusFlow, walkthrough, onFocusStep]);
1341
1368
  const togglePlay = useCallback(() => {
1342
1369
  if (playing) {
1343
1370
  stopPlaying();
@@ -1357,7 +1384,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1357
1384
  return (_jsxs("div", { children: [_jsxs("div", { onMouseEnter: () => {
1358
1385
  setHeaderHover(true);
1359
1386
  if (collapsed)
1360
- onHoverFlow(throughline);
1387
+ onHoverFlow(walkthrough);
1361
1388
  }, onMouseLeave: () => {
1362
1389
  setHeaderHover(false);
1363
1390
  if (collapsed)
@@ -1371,14 +1398,14 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1371
1398
  transition: 'background 120ms ease',
1372
1399
  }, children: [_jsx("button", { type: "button", onClick: () => {
1373
1400
  if (collapsed) {
1374
- onToggleCollapsed(throughline.id);
1375
- onFocusFlow(throughline);
1401
+ onToggleCollapsed(walkthrough.id);
1402
+ onFocusFlow(walkthrough);
1376
1403
  }
1377
1404
  else if (active === null) {
1378
- onFocusFlow(throughline);
1405
+ onFocusFlow(walkthrough);
1379
1406
  }
1380
1407
  else {
1381
- onToggleCollapsed(throughline.id);
1408
+ onToggleCollapsed(walkthrough.id);
1382
1409
  onClearFocus();
1383
1410
  }
1384
1411
  }, style: {
@@ -1399,7 +1426,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1399
1426
  fontFamily: theme.fonts.monospace,
1400
1427
  fontWeight: 600,
1401
1428
  color: theme.colors.text,
1402
- }, children: throughline.title }) }), !collapsed && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", "aria-label": playing ? `Pause ${throughline.title} autoplay` : `Play ${throughline.title}`, title: playing ? 'Pause' : 'Play through steps', onMouseEnter: () => setPlayHover(true), onMouseLeave: () => setPlayHover(false), onClick: (e) => {
1429
+ }, children: walkthrough.title }) }), !collapsed && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", "aria-label": playing ? `Pause ${walkthrough.title} autoplay` : `Play ${walkthrough.title}`, title: playing ? 'Pause' : 'Play through steps', onMouseEnter: () => setPlayHover(true), onMouseLeave: () => setPlayHover(false), onClick: (e) => {
1403
1430
  e.stopPropagation();
1404
1431
  togglePlay();
1405
1432
  }, style: {
@@ -1416,9 +1443,9 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1416
1443
  color: playing || playHover ? theme.colors.text : muted,
1417
1444
  cursor: 'pointer',
1418
1445
  transition: 'background 120ms ease, color 120ms ease',
1419
- }, children: playing ? _jsx(Pause, { size: 12, strokeWidth: 2 }) : _jsx(Play, { size: 12, strokeWidth: 2 }) }), _jsx("button", { type: "button", "aria-label": `Close ${throughline.title}`, onMouseEnter: () => setCloseHover(true), onMouseLeave: () => setCloseHover(false), onClick: (e) => {
1446
+ }, children: playing ? _jsx(Pause, { size: 12, strokeWidth: 2 }) : _jsx(Play, { size: 12, strokeWidth: 2 }) }), _jsx("button", { type: "button", "aria-label": `Close ${walkthrough.title}`, onMouseEnter: () => setCloseHover(true), onMouseLeave: () => setCloseHover(false), onClick: (e) => {
1420
1447
  e.stopPropagation();
1421
- onToggleCollapsed(throughline.id);
1448
+ onToggleCollapsed(walkthrough.id);
1422
1449
  if (active !== null)
1423
1450
  onClearFocus();
1424
1451
  }, style: {
@@ -1438,17 +1465,16 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1438
1465
  }, children: _jsx(X, { size: 12, strokeWidth: 2 }) })] }))] }), !collapsed && (_jsx("div", { style: { display: 'flex', flexDirection: 'column' }, onMouseLeave: () => {
1439
1466
  setHoveredStep(null);
1440
1467
  onLeaveStep();
1441
- }, children: throughline.steps.map((step, i) => {
1442
- const edge = edgeById.get(step.edgeId);
1443
- const mech = edge?.mechanism;
1468
+ }, children: walkthrough.steps.map((step, i) => {
1469
+ const mech = step.mechanism;
1444
1470
  const color = mech ? MECHANISM_COLOR[mech] : muted;
1445
1471
  const stepActive = active !== null && active.stepIndex === i;
1446
1472
  return (_jsxs("button", { ref: (el) => {
1447
1473
  stepButtonRefs.current[i] = el;
1448
1474
  }, type: "button", onMouseEnter: () => {
1449
1475
  setHoveredStep(i);
1450
- onHoverStep(throughline, i);
1451
- }, onClick: () => onFocusStep(throughline, i), style: {
1476
+ onHoverStep(walkthrough, i);
1477
+ }, onClick: () => onFocusStep(walkthrough, i), style: {
1452
1478
  display: 'flex',
1453
1479
  alignItems: 'center',
1454
1480
  gap: 8,
@@ -1485,7 +1511,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1485
1511
  fontSize: theme.fontSizes[0],
1486
1512
  fontFamily: theme.fonts.monospace,
1487
1513
  color,
1488
- }, children: mech ?? step.edgeId }), _jsxs("span", { style: {
1514
+ }, children: mech ?? step.mechanism }), _jsxs("span", { style: {
1489
1515
  flex: 1,
1490
1516
  minWidth: 0,
1491
1517
  overflow: 'hidden',
@@ -1494,7 +1520,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1494
1520
  fontSize: theme.fontSizes[0],
1495
1521
  fontFamily: theme.fonts.monospace,
1496
1522
  color: theme.colors.text,
1497
- }, children: [step.file.split('/').pop(), _jsxs("span", { style: { opacity: 0.7 }, children: [":", step.line] })] })] }))] }, `${step.edgeId}-${i}`));
1523
+ }, children: [step.file.split('/').pop(), _jsxs("span", { style: { opacity: 0.7 }, children: [":", step.line] })] })] }))] }, `${walkthroughStepGraphEdgeId(step)}-${i}`));
1498
1524
  }) }))] }));
1499
1525
  }
1500
1526
  export function SubsystemComponentGraph(props) {