@principal-ai/subsystems-react 0.20.6 → 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 (73) 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/index.d.ts +5 -5
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/pierre/PierreWalkthroughCodeView.d.ts +22 -0
  13. package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -0
  14. package/dist/pierre/PierreWalkthroughCodeView.js +198 -0
  15. package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -0
  16. package/dist/pierre/index.d.ts +2 -2
  17. package/dist/pierre/index.js +1 -1
  18. package/dist/pierre/pierreFileLang.d.ts +1 -1
  19. package/dist/pierre/pierreFileLang.js +1 -1
  20. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +14 -2
  21. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  22. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +86 -20
  23. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  24. package/dist/subsystem/FileDrawer.d.ts +2 -2
  25. package/dist/subsystem/FileDrawer.js +2 -2
  26. package/dist/subsystem/SubsystemComponentGraph.d.ts +25 -24
  27. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  28. package/dist/subsystem/SubsystemComponentGraph.js +204 -199
  29. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  30. package/dist/subsystem/formatDeclaration.d.ts +2 -1
  31. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  32. package/dist/subsystem/formatDeclaration.js +57 -33
  33. package/dist/subsystem/formatDeclaration.js.map +1 -1
  34. package/dist/subsystem/model.d.ts +85 -35
  35. package/dist/subsystem/model.d.ts.map +1 -1
  36. package/dist/subsystem/model.js +85 -6
  37. package/dist/subsystem/model.js.map +1 -1
  38. package/dist/subsystem/nodes.d.ts.map +1 -1
  39. package/dist/subsystem/nodes.js +30 -13
  40. package/dist/subsystem/nodes.js.map +1 -1
  41. package/dist/subsystem/tokenizeComponent.js +2 -2
  42. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/graphify/anchor.test.ts +31 -2
  45. package/src/graphify/anchor.ts +46 -0
  46. package/src/graphify/consolidated.ts +43 -1
  47. package/src/index.ts +13 -5
  48. package/src/pierre/{PierreThroughlineCodeView.tsx → PierreWalkthroughCodeView.tsx} +39 -47
  49. package/src/pierre/index.ts +2 -2
  50. package/src/pierre/pierreFileLang.ts +1 -1
  51. package/src/stories/Pierre/CodeView.stories.tsx +3 -3
  52. package/src/stories/Subsystem/ComponentDeclarationAudit.stories.tsx +14 -14
  53. package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +13 -15
  54. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +8 -8
  55. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +14 -14
  56. package/src/stories/Subsystem/ComponentGraph/CustomEntities.stories.tsx +11 -10
  57. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +13 -14
  58. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +31 -66
  59. package/src/stories/Subsystem/ComponentGraph/FrameworkStereotype.stories.tsx +2 -2
  60. package/src/stories/Subsystem/ComponentGraph/GraphTitle.stories.tsx +4 -4
  61. package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +3 -3
  62. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +1 -1
  63. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +518 -33
  64. package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +13 -13
  65. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +105 -21
  66. package/src/subsystem/FileDrawer.tsx +2 -2
  67. package/src/subsystem/SubsystemComponentGraph.tsx +268 -259
  68. package/src/subsystem/formatDeclaration.test.ts +76 -1
  69. package/src/subsystem/formatDeclaration.ts +57 -33
  70. package/src/subsystem/model.test.ts +27 -15
  71. package/src/subsystem/model.ts +175 -45
  72. package/src/subsystem/nodes.tsx +47 -13
  73. 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,77 +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
744
+ // Focus a single step's edge on the canvas and open/scroll the walkthrough
738
745
  // drawer to that step's snippet. Clicking the already-focused step clears
739
746
  // step focus and returns to whole-flow (high-level) framing.
740
747
  // In `dim` mode: only dim non-participants (same as hovering a step) —
741
748
  // no camera move, no drawer, no hide.
742
- const focusThroughlineStep = useCallback((tl, stepIndex) => {
749
+ const focusWalkthroughStep = useCallback((tl, stepIndex) => {
743
750
  const step = tl.steps[stepIndex];
744
751
  if (!step)
745
752
  return;
746
753
  setSelected(null);
747
754
  setSelectedEdgeId(null);
748
- if (throughlineStepMode === 'dim') {
755
+ if (walkthroughStepMode === 'dim') {
749
756
  setFocusedStepIndex(null);
750
- setFocusedThroughlineId(null);
751
- setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex });
752
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
757
+ setFocusedWalkthroughId(null);
758
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
759
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
753
760
  return;
754
761
  }
755
762
  // Toggle: clicking the selected step unselects it (back to whole flow).
756
- if (focusedThroughlineId === tl.id && focusedStepIndex === stepIndex) {
763
+ if (focusedWalkthroughId === tl.id && focusedStepIndex === stepIndex) {
757
764
  setFocusedStepIndex(null);
758
765
  // Pointer is still over the row — keep hover preview so dimming doesn't
759
766
  // flash off until mouseleave.
760
- setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex });
761
- setDrawerTarget((prev) => (prev?.kind === 'throughline' ? null : prev));
767
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex });
768
+ setDrawerTarget((prev) => (prev?.kind === 'walkthrough' ? null : prev));
762
769
  if (pendingFocusFitRef.current != null) {
763
770
  window.clearTimeout(pendingFocusFitRef.current);
764
771
  pendingFocusFitRef.current = null;
765
772
  }
766
- fitFocusBounds(new Set(tl.steps.map((s) => s.edgeId)));
773
+ fitFocusBounds(new Set(tl.steps.map((s) => walkthroughStepGraphEdgeId(s))));
767
774
  return;
768
775
  }
769
776
  setFocusedStepIndex(stepIndex);
770
- setFocusedThroughlineId(tl.id);
771
- setHoveredThroughlineStep(null);
777
+ setFocusedWalkthroughId(tl.id);
778
+ setHoveredWalkthroughStep(null);
772
779
  // Open the drawer before fitting. If it was closed, wait for its height
773
780
  // transition so fitView uses the reduced canvas — not full height.
774
781
  const drawerWasOpen = drawerOpenRef.current;
775
- if (renderThroughlineViewer) {
782
+ if (renderWalkthroughViewer) {
776
783
  setDrawerTarget({
777
- kind: 'throughline',
778
- throughlineId: tl.id,
784
+ kind: 'walkthrough',
785
+ walkthroughId: tl.id,
779
786
  stepIndex,
780
787
  });
781
788
  }
@@ -786,7 +793,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
786
793
  startLine: step.line,
787
794
  });
788
795
  }
789
- const edgeIds = new Set([step.edgeId]);
796
+ const edgeIds = new Set([walkthroughStepGraphEdgeId(step)]);
790
797
  if (pendingFocusFitRef.current != null) {
791
798
  window.clearTimeout(pendingFocusFitRef.current);
792
799
  pendingFocusFitRef.current = null;
@@ -803,9 +810,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
803
810
  }, [
804
811
  fitFocusBounds,
805
812
  focusedStepIndex,
806
- focusedThroughlineId,
807
- renderThroughlineViewer,
808
- throughlineStepMode,
813
+ focusedWalkthroughId,
814
+ renderWalkthroughViewer,
815
+ walkthroughStepMode,
809
816
  ]);
810
817
  useEffect(() => {
811
818
  return () => {
@@ -823,10 +830,10 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
823
830
  useEffect(() => {
824
831
  const prev = prevPreviewEdgeIdsRef.current;
825
832
  prevPreviewEdgeIdsRef.current = previewEdgeIds;
826
- if (!zoomOnThroughlineFocus || throughlineStepMode === 'dim')
833
+ if (!zoomOnWalkthroughFocus || walkthroughStepMode === 'dim')
827
834
  return;
828
835
  // Camera follows hover only when a specific step is already focused.
829
- if (focusedThroughlineId == null || focusedStepIndex == null)
836
+ if (focusedWalkthroughId == null || focusedStepIndex == null)
830
837
  return;
831
838
  if (previewEdgeIds) {
832
839
  fitFocusBounds(previewEdgeIds);
@@ -838,33 +845,33 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
838
845
  }, [
839
846
  previewEdgeIds,
840
847
  focusEdgeIds,
841
- focusedThroughlineId,
848
+ focusedWalkthroughId,
842
849
  focusedStepIndex,
843
850
  fitFocusBounds,
844
- zoomOnThroughlineFocus,
845
- throughlineStepMode,
851
+ zoomOnWalkthroughFocus,
852
+ walkthroughStepMode,
846
853
  ]);
847
- // Graph-only embeds: cycle throughline steps with hover-style dimming.
854
+ // Graph-only embeds: cycle walkthrough steps with hover-style dimming.
848
855
  useEffect(() => {
849
- if (!autoPlayThroughlines || !throughlines?.length || !layoutReady)
856
+ if (!autoPlayWalkthroughs || !walkthroughs?.length || !layoutReady)
850
857
  return;
851
- const playable = throughlines.filter((tl) => tl.steps.length > 0);
858
+ const playable = walkthroughs.filter((tl) => tl.steps.length > 0);
852
859
  if (playable.length === 0)
853
860
  return;
854
861
  let cancelled = false;
855
862
  let tlIdx = 0;
856
863
  let stepIdx = 0;
857
864
  let timer = null;
858
- const interval = Math.max(400, throughlineAutoPlayIntervalMs);
865
+ const interval = Math.max(400, walkthroughAutoPlayIntervalMs);
859
866
  const tick = () => {
860
867
  if (cancelled)
861
868
  return;
862
869
  const tl = playable[tlIdx];
863
870
  setSelected(null);
864
871
  setSelectedEdgeId(null);
865
- setFocusedThroughlineId(null);
872
+ setFocusedWalkthroughId(null);
866
873
  setFocusedStepIndex(null);
867
- setHoveredThroughlineStep({ throughlineId: tl.id, stepIndex: stepIdx });
874
+ setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: stepIdx });
868
875
  stepIdx += 1;
869
876
  if (stepIdx >= tl.steps.length) {
870
877
  stepIdx = 0;
@@ -877,16 +884,16 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
877
884
  cancelled = true;
878
885
  if (timer != null)
879
886
  window.clearTimeout(timer);
880
- setHoveredThroughlineStep(null);
887
+ setHoveredWalkthroughStep(null);
881
888
  };
882
- }, [autoPlayThroughlines, throughlines, throughlineAutoPlayIntervalMs, layoutReady]);
883
- // 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
884
891
  // (sidebar click or drawer open). Ignores typing targets and chords.
885
892
  useEffect(() => {
886
- if (focusedThroughlineId == null || focusedStepIndex == null || !throughlines) {
893
+ if (focusedWalkthroughId == null || focusedStepIndex == null || !walkthroughs) {
887
894
  return;
888
895
  }
889
- const tl = throughlines.find((t) => t.id === focusedThroughlineId);
896
+ const tl = walkthroughs.find((t) => t.id === focusedWalkthroughId);
890
897
  if (!tl || tl.steps.length === 0)
891
898
  return;
892
899
  const onKeyDown = (e) => {
@@ -913,19 +920,19 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
913
920
  if (next === focusedStepIndex)
914
921
  return;
915
922
  e.preventDefault();
916
- focusThroughlineStep(tl, next);
923
+ focusWalkthroughStep(tl, next);
917
924
  };
918
925
  window.addEventListener('keydown', onKeyDown);
919
926
  return () => window.removeEventListener('keydown', onKeyDown);
920
927
  }, [
921
- focusedThroughlineId,
928
+ focusedWalkthroughId,
922
929
  focusedStepIndex,
923
- throughlines,
924
- focusThroughlineStep,
930
+ walkthroughs,
931
+ focusWalkthroughStep,
925
932
  ]);
926
- const toggleThroughlineCollapsed = useCallback((tlId) => {
927
- const collapsingLast = expandedThroughlines.has(tlId) && expandedThroughlines.size === 1;
928
- setExpandedThroughlines((prev) => {
933
+ const toggleWalkthroughCollapsed = useCallback((tlId) => {
934
+ const collapsingLast = expandedWalkthroughs.has(tlId) && expandedWalkthroughs.size === 1;
935
+ setExpandedWalkthroughs((prev) => {
929
936
  const next = new Set(prev);
930
937
  if (next.has(tlId))
931
938
  next.delete(tlId);
@@ -933,7 +940,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
933
940
  next.add(tlId);
934
941
  return next;
935
942
  });
936
- if (!collapsingLast || !zoomOnThroughlineFocus)
943
+ if (!collapsingLast || !zoomOnWalkthroughFocus)
937
944
  return;
938
945
  // Cancel any pending focus fit so it doesn't fight the overview zoom.
939
946
  if (pendingFocusFitRef.current != null) {
@@ -948,7 +955,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
948
955
  pendingFocusFitRef.current = null;
949
956
  requestAnimationFrame(() => fitOverview());
950
957
  }, drawerWasOpen ? FILE_DRAWER_HEIGHT_MS + 20 : 20);
951
- }, [expandedThroughlines, fitOverview, zoomOnThroughlineFocus]);
958
+ }, [expandedWalkthroughs, fitOverview, zoomOnWalkthroughFocus]);
952
959
  // Filename-badge clicks on nodes open the drawer through the same path as
953
960
  // the declaration panel's file link (toggle + tree sync, no start line).
954
961
  useEffect(() => {
@@ -973,7 +980,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
973
980
  return;
974
981
  setSelected(comp);
975
982
  setSelectedEdgeId(null);
976
- setFocusedThroughlineId(null);
983
+ setFocusedWalkthroughId(null);
977
984
  setFocusedStepIndex(null);
978
985
  onSelect?.(comp.id);
979
986
  }, [components, onSelect]);
@@ -1021,10 +1028,10 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1021
1028
  const fileViewerRef = useRef(fileViewer);
1022
1029
  fileViewerRef.current = fileViewer;
1023
1030
  const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
1024
- const throughlineViewerRef = useRef(renderThroughlineViewer);
1025
- throughlineViewerRef.current = renderThroughlineViewer;
1026
- const renderThroughlineDrawerContent = useCallback((ctx) => throughlineViewerRef.current?.(ctx) ?? null, []);
1027
- 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: {
1028
1035
  width: sidebarWidth,
1029
1036
  minWidth: sidebarWidth,
1030
1037
  borderRight: `1px solid ${theme.colors.border}`,
@@ -1062,7 +1069,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1062
1069
  color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
1063
1070
  cursor: 'pointer',
1064
1071
  transition: 'background 120ms ease, color 120ms ease',
1065
- }, 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: {
1066
1073
  ...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
1067
1074
  minHeight: 160,
1068
1075
  flexShrink: 0,
@@ -1070,13 +1077,13 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1070
1077
  display: 'flex',
1071
1078
  flexDirection: 'column',
1072
1079
  overflow: 'hidden',
1073
- }, children: [hasThroughlines && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1080
+ }, children: [hasWalkthroughs && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1074
1081
  display: 'flex',
1075
1082
  width: '100%',
1076
1083
  flexShrink: 0,
1077
1084
  borderBottom: `1px solid ${theme.colors.border}`,
1078
1085
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
1079
- }, 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: {
1080
1087
  flex: 1,
1081
1088
  minWidth: 0,
1082
1089
  padding: '8px 8px',
@@ -1090,16 +1097,16 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1090
1097
  fontFamily: theme.fonts.monospace,
1091
1098
  textTransform: 'capitalize',
1092
1099
  cursor: 'pointer',
1093
- }, 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: {
1094
1101
  flex: 1,
1095
1102
  minHeight: 0,
1096
1103
  overflowY: 'auto',
1097
1104
  padding: '0 0 12px',
1098
- }, 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
1099
1106
  ? { stepIndex: focusedStepIndex }
1100
- : hoveredThroughlineStep?.throughlineId === tl.id
1101
- ? { stepIndex: hoveredThroughlineStep.stepIndex }
1102
- : 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) => {
1103
1110
  const groupKey = group.repoKey ?? '__no-repo__';
1104
1111
  const collapsed = collapsedRepos.has(groupKey);
1105
1112
  return (_jsxs("div", { style: {
@@ -1109,7 +1116,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1109
1116
  flexDirection: 'column',
1110
1117
  borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
1111
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));
1112
- }) })) : 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: {
1113
1120
  width: 3,
1114
1121
  flexShrink: 0,
1115
1122
  cursor: 'col-resize',
@@ -1172,7 +1179,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1172
1179
  flex: 1,
1173
1180
  minHeight: 0,
1174
1181
  background: theme.colors.background,
1175
- }, 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: {
1176
1183
  position: 'absolute',
1177
1184
  top: 10,
1178
1185
  left: '50%',
@@ -1198,9 +1205,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1198
1205
  border: `1px solid ${theme.colors.border}`,
1199
1206
  borderRadius: 6,
1200
1207
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
1201
- }, children: graphTitle })), overlayThroughlineTitle && (_jsxs("div", { style: {
1208
+ }, children: graphTitle })), overlayWalkthroughTitle && (_jsxs("div", { style: {
1202
1209
  maxWidth: '100%',
1203
- minWidth: overlayThroughlineStep ? 160 : undefined,
1210
+ minWidth: overlayWalkthroughStep ? 160 : undefined,
1204
1211
  display: 'flex',
1205
1212
  flexDirection: 'column',
1206
1213
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -1209,9 +1216,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1209
1216
  boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
1210
1217
  overflow: 'hidden',
1211
1218
  opacity: 0.95,
1212
- }, "aria-label": overlayThroughlineStep
1213
- ? `${overlayThroughlineTitle}, step ${overlayThroughlineStep.index} of ${overlayThroughlineStep.total}`
1214
- : overlayThroughlineTitle, children: [_jsx("div", { style: {
1219
+ }, "aria-label": overlayWalkthroughStep
1220
+ ? `${overlayWalkthroughTitle}, step ${overlayWalkthroughStep.index} of ${overlayWalkthroughStep.total}`
1221
+ : overlayWalkthroughTitle, children: [_jsx("div", { style: {
1215
1222
  padding: '5px 14px',
1216
1223
  fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
1217
1224
  fontWeight: 600,
@@ -1221,14 +1228,14 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1221
1228
  textOverflow: 'ellipsis',
1222
1229
  whiteSpace: 'nowrap',
1223
1230
  textAlign: 'center',
1224
- }, children: overlayThroughlineTitle }), overlayThroughlineStep && overlayThroughlineStep.total > 0 && (_jsx("div", { style: {
1231
+ }, children: overlayWalkthroughTitle }), overlayWalkthroughStep && overlayWalkthroughStep.total > 0 && (_jsx("div", { style: {
1225
1232
  display: 'flex',
1226
1233
  gap: 3,
1227
1234
  padding: '0 6px 5px',
1228
- }, "aria-hidden": "true", children: Array.from({ length: overlayThroughlineStep.total }, (_, i) => {
1235
+ }, "aria-hidden": "true", children: Array.from({ length: overlayWalkthroughStep.total }, (_, i) => {
1229
1236
  const n = i + 1;
1230
- const active = n === overlayThroughlineStep.index;
1231
- const done = n < overlayThroughlineStep.index;
1237
+ const active = n === overlayWalkthroughStep.index;
1238
+ const done = n < overlayWalkthroughStep.index;
1232
1239
  return (_jsx("span", { style: {
1233
1240
  flex: 1,
1234
1241
  height: 2,
@@ -1241,7 +1248,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1241
1248
  opacity: active ? 1 : done ? 0.75 : 0.4,
1242
1249
  transition: 'opacity 120ms ease, background 120ms ease',
1243
1250
  } }, n));
1244
- }) }))] })), overlayThroughlineStep?.annotation && (_jsx("div", { style: {
1251
+ }) }))] })), overlayWalkthroughStep?.annotation && (_jsx("div", { style: {
1245
1252
  maxWidth: '100%',
1246
1253
  padding: '6px 14px',
1247
1254
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
@@ -1253,7 +1260,7 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1253
1260
  fontFamily: theme.fonts.body,
1254
1261
  color: theme.colors.textMuted ?? theme.colors.textSecondary,
1255
1262
  lineHeight: 1.35,
1256
- }, children: overlayThroughlineStep.annotation }))] })), selected && (_jsx("div", { style: {
1263
+ }, children: overlayWalkthroughStep.annotation }))] })), selected && (_jsx("div", { style: {
1257
1264
  position: 'absolute',
1258
1265
  top: 10,
1259
1266
  right: 10,
@@ -1265,9 +1272,9 @@ function Inner({ components, edges, throughlines, onSelect, onEdgeSelect, measur
1265
1272
  border: `1px solid ${theme.colors.border}`,
1266
1273
  borderRadius: 8,
1267
1274
  boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
1268
- }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onVerify: onVerifyComponent, verification: componentVerification }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: () => setDrawerTarget(null), children: drawerTarget?.kind === 'throughline' &&
1269
- focusedThroughline &&
1270
- 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] })] }));
1271
1278
  }
1272
1279
  /** Sidebar header for one repo's tree: owner avatar + repo name + file count.
1273
1280
  * Clicking toggles the tree's visibility. */
@@ -1309,15 +1316,14 @@ function RepoGroupHeader({ group, collapsed, onToggle, }) {
1309
1316
  whiteSpace: 'nowrap',
1310
1317
  }, children: label })] }));
1311
1318
  }
1312
- /** One collapsible throughline in the sidebar's flows panel. Clicking the
1319
+ /** One collapsible walkthrough in the sidebar's flows panel. Clicking the
1313
1320
  * title: closed → open + select; open and unselected → select; open and
1314
1321
  * selected → close + clear focus. The right-aligned close button collapses
1315
1322
  * without selecting. A step row focuses that step's edge. */
1316
- 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, }) {
1317
1324
  const { theme } = useTheme();
1318
1325
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
1319
1326
  const hoverBg = theme.colors.background;
1320
- const edgeById = useMemo(() => new Map(edges.map((e) => [e.id, e])), [edges]);
1321
1327
  const wholeFlowActive = active !== null && active.stepIndex === null;
1322
1328
  const [headerHover, setHeaderHover] = useState(false);
1323
1329
  const [closeHover, setCloseHover] = useState(false);
@@ -1339,10 +1345,10 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1339
1345
  useEffect(() => () => stopPlaying(), []);
1340
1346
  const startPlaying = useCallback(() => {
1341
1347
  if (collapsed)
1342
- onToggleCollapsed(throughline.id);
1348
+ onToggleCollapsed(walkthrough.id);
1343
1349
  if (active === null || active.stepIndex !== null)
1344
- onFocusFlow(throughline);
1345
- const stepCount = throughline.steps.length;
1350
+ onFocusFlow(walkthrough);
1351
+ const stepCount = walkthrough.steps.length;
1346
1352
  if (stepCount === 0)
1347
1353
  return;
1348
1354
  setPlaying(true);
@@ -1353,12 +1359,12 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1353
1359
  setPlaying(false);
1354
1360
  return;
1355
1361
  }
1356
- onFocusStep(throughline, i);
1362
+ onFocusStep(walkthrough, i);
1357
1363
  i += 1;
1358
- playTimerRef.current = window.setTimeout(tick, THROUGHLINE_PLAY_PAUSE_MS);
1364
+ playTimerRef.current = window.setTimeout(tick, WALKTHROUGH_PLAY_PAUSE_MS);
1359
1365
  };
1360
1366
  tick();
1361
- }, [collapsed, active, onToggleCollapsed, onFocusFlow, throughline, onFocusStep]);
1367
+ }, [collapsed, active, onToggleCollapsed, onFocusFlow, walkthrough, onFocusStep]);
1362
1368
  const togglePlay = useCallback(() => {
1363
1369
  if (playing) {
1364
1370
  stopPlaying();
@@ -1378,7 +1384,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1378
1384
  return (_jsxs("div", { children: [_jsxs("div", { onMouseEnter: () => {
1379
1385
  setHeaderHover(true);
1380
1386
  if (collapsed)
1381
- onHoverFlow(throughline);
1387
+ onHoverFlow(walkthrough);
1382
1388
  }, onMouseLeave: () => {
1383
1389
  setHeaderHover(false);
1384
1390
  if (collapsed)
@@ -1392,14 +1398,14 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1392
1398
  transition: 'background 120ms ease',
1393
1399
  }, children: [_jsx("button", { type: "button", onClick: () => {
1394
1400
  if (collapsed) {
1395
- onToggleCollapsed(throughline.id);
1396
- onFocusFlow(throughline);
1401
+ onToggleCollapsed(walkthrough.id);
1402
+ onFocusFlow(walkthrough);
1397
1403
  }
1398
1404
  else if (active === null) {
1399
- onFocusFlow(throughline);
1405
+ onFocusFlow(walkthrough);
1400
1406
  }
1401
1407
  else {
1402
- onToggleCollapsed(throughline.id);
1408
+ onToggleCollapsed(walkthrough.id);
1403
1409
  onClearFocus();
1404
1410
  }
1405
1411
  }, style: {
@@ -1420,7 +1426,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1420
1426
  fontFamily: theme.fonts.monospace,
1421
1427
  fontWeight: 600,
1422
1428
  color: theme.colors.text,
1423
- }, 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) => {
1424
1430
  e.stopPropagation();
1425
1431
  togglePlay();
1426
1432
  }, style: {
@@ -1437,9 +1443,9 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1437
1443
  color: playing || playHover ? theme.colors.text : muted,
1438
1444
  cursor: 'pointer',
1439
1445
  transition: 'background 120ms ease, color 120ms ease',
1440
- }, 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) => {
1441
1447
  e.stopPropagation();
1442
- onToggleCollapsed(throughline.id);
1448
+ onToggleCollapsed(walkthrough.id);
1443
1449
  if (active !== null)
1444
1450
  onClearFocus();
1445
1451
  }, style: {
@@ -1459,17 +1465,16 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1459
1465
  }, children: _jsx(X, { size: 12, strokeWidth: 2 }) })] }))] }), !collapsed && (_jsx("div", { style: { display: 'flex', flexDirection: 'column' }, onMouseLeave: () => {
1460
1466
  setHoveredStep(null);
1461
1467
  onLeaveStep();
1462
- }, children: throughline.steps.map((step, i) => {
1463
- const edge = edgeById.get(step.edgeId);
1464
- const mech = edge?.mechanism;
1468
+ }, children: walkthrough.steps.map((step, i) => {
1469
+ const mech = step.mechanism;
1465
1470
  const color = mech ? MECHANISM_COLOR[mech] : muted;
1466
1471
  const stepActive = active !== null && active.stepIndex === i;
1467
1472
  return (_jsxs("button", { ref: (el) => {
1468
1473
  stepButtonRefs.current[i] = el;
1469
1474
  }, type: "button", onMouseEnter: () => {
1470
1475
  setHoveredStep(i);
1471
- onHoverStep(throughline, i);
1472
- }, onClick: () => onFocusStep(throughline, i), style: {
1476
+ onHoverStep(walkthrough, i);
1477
+ }, onClick: () => onFocusStep(walkthrough, i), style: {
1473
1478
  display: 'flex',
1474
1479
  alignItems: 'center',
1475
1480
  gap: 8,
@@ -1506,7 +1511,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1506
1511
  fontSize: theme.fontSizes[0],
1507
1512
  fontFamily: theme.fonts.monospace,
1508
1513
  color,
1509
- }, children: mech ?? step.edgeId }), _jsxs("span", { style: {
1514
+ }, children: mech ?? step.mechanism }), _jsxs("span", { style: {
1510
1515
  flex: 1,
1511
1516
  minWidth: 0,
1512
1517
  overflow: 'hidden',
@@ -1515,7 +1520,7 @@ function ThroughlineFlow({ throughline, edges, collapsed, active, onToggleCollap
1515
1520
  fontSize: theme.fontSizes[0],
1516
1521
  fontFamily: theme.fonts.monospace,
1517
1522
  color: theme.colors.text,
1518
- }, 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}`));
1519
1524
  }) }))] }));
1520
1525
  }
1521
1526
  export function SubsystemComponentGraph(props) {