@principal-ai/subsystems-react 0.48.0 → 0.50.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 (48) hide show
  1. package/dist/graphify/consolidated.d.ts +7 -1
  2. package/dist/graphify/consolidated.d.ts.map +1 -1
  3. package/dist/pierre/PierreFileView.d.ts +1 -1
  4. package/dist/pierre/PierreFileView.d.ts.map +1 -1
  5. package/dist/pierre/PierreFileView.js +2 -1
  6. package/dist/pierre/PierreFileView.js.map +1 -1
  7. package/dist/pierre/PierreSnippetView.d.ts +1 -1
  8. package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
  9. package/dist/pierre/PierreSnippetView.js +2 -1
  10. package/dist/pierre/PierreSnippetView.js.map +1 -1
  11. package/dist/pierre/PierreTrailCodeView.d.ts +7 -1
  12. package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
  13. package/dist/pierre/PierreTrailCodeView.js +71 -18
  14. package/dist/pierre/PierreTrailCodeView.js.map +1 -1
  15. package/dist/pierre/pierreBackground.d.ts +36 -2
  16. package/dist/pierre/pierreBackground.d.ts.map +1 -1
  17. package/dist/pierre/pierreBackground.js +40 -13
  18. package/dist/pierre/pierreBackground.js.map +1 -1
  19. package/dist/pierre/visibleStep.d.ts +48 -0
  20. package/dist/pierre/visibleStep.d.ts.map +1 -0
  21. package/dist/pierre/visibleStep.js +53 -0
  22. package/dist/pierre/visibleStep.js.map +1 -0
  23. package/dist/subsystem/FileDrawer.d.ts +10 -1
  24. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  25. package/dist/subsystem/FileDrawer.js +61 -40
  26. package/dist/subsystem/FileDrawer.js.map +1 -1
  27. package/dist/subsystem/SubsystemComponentGraph.d.ts +6 -0
  28. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  29. package/dist/subsystem/SubsystemComponentGraph.js +21 -10
  30. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  31. package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
  32. package/dist/subsystem/formatDeclaration.js +10 -2
  33. package/dist/subsystem/formatDeclaration.js.map +1 -1
  34. package/dist/subsystem/tokenizeComponent.js +4 -4
  35. package/dist/subsystem/tokenizeComponent.js.map +1 -1
  36. package/package.json +3 -3
  37. package/src/graphify/consolidated.ts +7 -1
  38. package/src/pierre/PierreFileView.tsx +2 -1
  39. package/src/pierre/PierreSnippetView.tsx +2 -1
  40. package/src/pierre/PierreTrailCodeView.tsx +81 -22
  41. package/src/pierre/pierreBackground.ts +62 -12
  42. package/src/pierre/visibleStep.test.ts +108 -0
  43. package/src/pierre/visibleStep.ts +76 -0
  44. package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +6 -0
  45. package/src/subsystem/FileDrawer.tsx +112 -63
  46. package/src/subsystem/SubsystemComponentGraph.tsx +29 -5
  47. package/src/subsystem/formatDeclaration.ts +10 -2
  48. package/src/subsystem/tokenizeComponent.ts +4 -4
@@ -42,6 +42,7 @@ import type { SubsystemOpenFileOptions } from '../subsystem/declarationRef';
42
42
  import { buildPierreOptions, PIERRE_FILE_STYLE } from './pierreBackground';
43
43
  import { pierreLangForPath } from './pierreFileLang';
44
44
  import { resolvePierreSyntaxThemeName } from './pierreSyntaxTheme';
45
+ import { resolveVisibleStepIndex, type VisibleStepRect } from './visibleStep';
45
46
  import { fileUnavailableNotice, isFileUnavailableError } from './fileAvailability';
46
47
  import {
47
48
  remapSnippetLineNumbers,
@@ -67,6 +68,16 @@ function stepIndexFromItemContext(context: unknown): number | null {
67
68
  return Number.isFinite(index) ? index : null;
68
69
  }
69
70
 
71
+ /**
72
+ * Assumed height of the compact file header, in px.
73
+ *
74
+ * Must track the header chrome passed to `buildPierreOptions` (fontSizes[0] at
75
+ * line-height 1.15 plus 2px vertical padding) — CodeView sizes its
76
+ * virtualization window and sticky offset from this, and the library's own
77
+ * default of 44 is more than double the compact header.
78
+ */
79
+ const TRAIL_HEADER_HEIGHT = 18;
80
+
70
81
  /** Pointer/dotted-underline affordance for a clickable construct token. */
71
82
  function paintSymbolToken(el: HTMLElement | undefined, active: boolean): void {
72
83
  if (el == null) return;
@@ -88,6 +99,12 @@ export interface PierreTrailCodeViewProps {
88
99
  background?: string;
89
100
  /** Open the step's full source file (header button or double-click snippet body). */
90
101
  onOpenFile?: (path: string, opts?: SubsystemOpenFileOptions) => void;
102
+ /**
103
+ * Fires when scrolling brings a different step to the top of the view, with
104
+ * that step's index. Drives a progress readout that tracks the viewport
105
+ * rather than the selected step. Only fires on change, not per scroll event.
106
+ */
107
+ onVisibleStepChange?: (index: number) => void;
91
108
  /**
92
109
  * Aliases of components marked `proposed`. A step whose file can't be read
93
110
  * but whose endpoint is proposed reads as "planned" rather than "missing".
@@ -193,12 +210,15 @@ export function PierreTrailCodeView({
193
210
  contextLines = 8,
194
211
  background,
195
212
  onOpenFile,
213
+ onVisibleStepChange,
196
214
  proposedAliases,
197
215
  resolveSymbol,
198
216
  onSymbolClick,
199
217
  }: PierreTrailCodeViewProps) {
200
218
  const { theme, mode } = useTheme();
201
219
  const viewRef = useRef<CodeViewHandle<undefined>>(null);
220
+ // CodeView's scroll container — also the box we measure items against.
221
+ const containerRef = useRef<HTMLDivElement | null>(null);
202
222
  const [load, setLoad] = useState<FileLoadState>({ status: 'loading' });
203
223
 
204
224
  // Keep the newest symbol callbacks in refs so the Pierre options object stays
@@ -207,6 +227,43 @@ export function PierreTrailCodeView({
207
227
  resolveSymbolRef.current = resolveSymbol;
208
228
  const onSymbolClickRef = useRef(onSymbolClick);
209
229
  onSymbolClickRef.current = onSymbolClick;
230
+ const onVisibleStepChangeRef = useRef(onVisibleStepChange);
231
+ onVisibleStepChangeRef.current = onVisibleStepChange;
232
+ const visibleStepRef = useRef<number | null>(null);
233
+
234
+ // Report which step the viewport is on. The geometry lives in `visibleStep` so
235
+ // it can be tested — the answer depends on how tall the final snippet is
236
+ // relative to the viewport, which is not something to eyeball.
237
+ const reportVisibleStep = useCallback(() => {
238
+ const container = containerRef.current;
239
+ const viewer = viewRef.current?.getInstance();
240
+ if (container == null || viewer == null) return;
241
+ const rect = container.getBoundingClientRect();
242
+ const rects: VisibleStepRect[] = [];
243
+ for (const rendered of viewer.getRenderedItems()) {
244
+ const index = Number.parseInt(rendered.id.split(':').pop() ?? '', 10);
245
+ if (!Number.isFinite(index)) continue;
246
+ const itemRect = rendered.element.getBoundingClientRect();
247
+ rects.push({ index, top: itemRect.top, bottom: itemRect.bottom });
248
+ }
249
+ const next = resolveVisibleStepIndex(
250
+ {
251
+ top: rect.top,
252
+ bottom: rect.bottom,
253
+ scrollTop: container.scrollTop,
254
+ clientHeight: container.clientHeight,
255
+ scrollHeight: container.scrollHeight,
256
+ },
257
+ rects,
258
+ );
259
+ if (next == null || next === visibleStepRef.current) return;
260
+ visibleStepRef.current = next;
261
+ onVisibleStepChangeRef.current?.(next);
262
+ }, []);
263
+
264
+ const onCodeViewScroll = useCallback(() => {
265
+ reportVisibleStep();
266
+ }, [reportVisibleStep]);
210
267
 
211
268
  // A step onto a proposed component is planned work; label its missing file
212
269
  // accordingly instead of showing a bare "not found".
@@ -346,32 +403,19 @@ export function PierreTrailCodeView({
346
403
  context?: { item?: { id?: string } },
347
404
  ) => {
348
405
  const id = context?.item?.id;
406
+ // Item-level renders don't move the viewport, but the first paint does
407
+ // decide which step is on screen.
408
+ reportVisibleStep();
349
409
  if (id == null) return;
350
410
  const sliceStart = sliceStartByItemId.get(id);
351
411
  if (sliceStart == null) return;
352
412
  remapSnippetLineNumbers(node, sliceStart);
353
413
  },
354
- [sliceStartByItemId],
414
+ [sliceStartByItemId, reportVisibleStep],
355
415
  );
356
416
 
357
- const renderHeaderPrefix = useMemo(() => {
358
- return (item: CodeViewItem) => {
359
- const index = Number.parseInt(item.id.split(':').pop() ?? '', 10);
360
- if (!trail.steps[index]) return null;
361
- return (
362
- <span
363
- style={{
364
- fontFamily: theme.fonts.monospace,
365
- fontSize: theme.fontSizes[0],
366
- color: theme.colors.textSecondary,
367
- marginRight: 8,
368
- }}
369
- >
370
- {index + 1}.
371
- </span>
372
- );
373
- };
374
- }, [trail.steps, theme]);
417
+ // No step numbers in the file header: the sidebar already numbers the steps,
418
+ // and the drawer header carries a segmented progress readout.
375
419
 
376
420
  const renderHeaderMetadata = useMemo(() => {
377
421
  return (item: CodeViewItem) => {
@@ -495,15 +539,29 @@ export function PierreTrailCodeView({
495
539
  light: resolvePierreSyntaxThemeName('light'),
496
540
  },
497
541
  stickyHeaders: true,
542
+ // CodeView never injects `unsafeCSS` into its own shadow root, but it
543
+ // forwards the option to each file item (CODE_VIEW_FILE_OPTION_KEYS),
544
+ // which does — so this retints the file surfaces and headers. It cannot
545
+ // reach CodeView's host, hence the spread only carries
546
+ // PIERRE_BASE_OPTIONS and `disableFileHeader` must follow it.
547
+ ...buildPierreOptions(background, {
548
+ background: theme.colors.backgroundSecondary,
549
+ fontSize: `${theme.fontSizes[0]}px`,
550
+ lineHeight: '1.15',
551
+ padding: '2px 8px',
552
+ }),
498
553
  disableFileHeader: false,
554
+ // The library assumes a 44px header (DEFAULT_VIRTUAL_FILE_METRICS); the
555
+ // compact chrome above is ~18px. CodeView uses this for its
556
+ // virtualization window and sticky offset, so leaving it at 44 drifts.
557
+ itemMetrics: { diffHeaderHeight: TRAIL_HEADER_HEIGHT },
499
558
  layout: { paddingTop: 0, paddingBottom: 0, gap: 4 },
500
559
  onPostRender,
501
560
  ...(onOpenFile ? { onLineClick } : {}),
502
561
  ...symbolHandlers,
503
- ...(background ? buildPierreOptions(background) : {}),
504
562
  ...(mode === 'light' || mode === 'dark' ? { themeType: mode } : {}),
505
563
  };
506
- }, [background, mode, onPostRender, onOpenFile, onLineClick, symbolHandlers]);
564
+ }, [background, theme.colors.backgroundSecondary, theme.fontSizes, mode, onPostRender, onOpenFile, onLineClick, symbolHandlers]);
507
565
 
508
566
  useEffect(() => {
509
567
  if (load.status !== 'ready' || stepIndex == null) return;
@@ -549,10 +607,11 @@ export function PierreTrailCodeView({
549
607
  return (
550
608
  <CodeViewLoose
551
609
  ref={viewRef}
610
+ containerRef={containerRef}
552
611
  items={items}
553
612
  options={options}
613
+ onScroll={onCodeViewScroll}
554
614
  selectedLines={selectedLines}
555
- renderHeaderPrefix={renderHeaderPrefix}
556
615
  renderHeaderMetadata={renderHeaderMetadata}
557
616
  renderAnnotation={renderAnnotation}
558
617
  style={{ ...PIERRE_FILE_STYLE, height: '100%', overflow: 'auto' }}
@@ -6,28 +6,78 @@ export const PIERRE_BASE_OPTIONS = {
6
6
  disableFileHeader: true,
7
7
  } as const;
8
8
 
9
- export function buildBackgroundCSS(background: string): string {
9
+ /** Chrome overrides for the per-file header (`[data-diffs-header]`). */
10
+ export interface PierreHeaderStyle {
11
+ /** Fill behind the header row. */
12
+ background?: string;
13
+ /** Rule under the header row. */
14
+ borderBottom?: string;
15
+ /** Explicit size — the library leaves the header to inherit its line box. */
16
+ fontSize?: string;
17
+ lineHeight?: string;
18
+ /** Vertical padding. The library ships none, so this is the only breathing room. */
19
+ padding?: string;
20
+ }
21
+
22
+ /**
23
+ * Retint Pierre's code surface to `background`, optionally restyling the file
24
+ * header.
25
+ *
26
+ * Every background in `@pierre/diffs` resolves to `--diffs-bg`, which is
27
+ * `light-dark(var(--diffs-light-bg, #fff), var(--diffs-dark-bg, #000))` — so
28
+ * overriding both inputs re-tints the whole surface from one variable and lets
29
+ * the diff / selection / hover layers keep recomputing their `color-mix` tints
30
+ * against it.
31
+ *
32
+ * `:host` still needs a direct override: the theme layer writes a literal
33
+ * `background-color` there from the Shiki theme, which does not go through
34
+ * `--diffs-bg`.
35
+ *
36
+ * Header rules land in each file item's shadow root, which is where
37
+ * `FileRenderer` puts the header — `CodeView` forwards `unsafeCSS` to its file
38
+ * items even though it never injects it into its own shadow root.
39
+ *
40
+ * Changing header height also invalidates `itemMetrics.diffHeaderHeight`, which
41
+ * `CodeView` uses for its virtualization window and sticky offset. Keep the two
42
+ * in step when overriding `fontSize` / `lineHeight` / `padding`.
43
+ */
44
+ export function buildBackgroundCSS(
45
+ background: string,
46
+ header?: PierreHeaderStyle,
47
+ ): string {
48
+ const headerDeclarations = header
49
+ ? Object.entries(header)
50
+ .map(([property, value]) => ` ${kebab(property)}: ${value};`)
51
+ .join("\n")
52
+ : "";
53
+
10
54
  return `
11
55
  :host {
56
+ --diffs-light-bg: ${background};
57
+ --diffs-dark-bg: ${background};
12
58
  background: ${background} !important;
13
59
  }
14
- pre, code,
15
- [data-gutter], [data-content],
16
- [data-line], [data-column-number],
17
- [data-gutter-buffer], [data-line-annotation], [data-no-newline],
18
- [data-separator], [data-separator-wrapper] {
19
- background: ${background} !important;
20
- }
21
- [data-line] span {
22
- background: ${background} !important;
60
+ ${
61
+ headerDeclarations
62
+ ? `[data-diffs-header] {
63
+ ${headerDeclarations}
64
+ }`
65
+ : ""
23
66
  }
24
67
  `;
25
68
  }
26
69
 
27
- export function buildPierreOptions(background?: string) {
70
+ function kebab(property: string): string {
71
+ return property.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
72
+ }
73
+
74
+ export function buildPierreOptions(
75
+ background?: string,
76
+ header?: PierreHeaderStyle,
77
+ ) {
28
78
  if (!background) return PIERRE_BASE_OPTIONS;
29
79
  return {
30
80
  ...PIERRE_BASE_OPTIONS,
31
- unsafeCSS: buildBackgroundCSS(background),
81
+ unsafeCSS: buildBackgroundCSS(background, header),
32
82
  };
33
83
  }
@@ -0,0 +1,108 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ AT_END_TOLERANCE_PX,
5
+ resolveVisibleStepIndex,
6
+ type VisibleStepRect,
7
+ type VisibleStepViewport,
8
+ } from "./visibleStep";
9
+
10
+ /**
11
+ * Four steps in *content* coordinates. Steps 2 and 3 are short tails, which is
12
+ * the case that matters: neither can fill a 600px viewport, so at full scroll
13
+ * the topmost thing on screen is step 1 while the true position is step 3.
14
+ */
15
+ const CONTENT_RECTS = [
16
+ { index: 0, top: 0, bottom: 200 },
17
+ { index: 1, top: 200, bottom: 900 },
18
+ { index: 2, top: 900, bottom: 1100 },
19
+ { index: 3, top: 1100, bottom: 1200 },
20
+ ];
21
+
22
+ const CONTENT_HEIGHT = 1200;
23
+ const CLIENT_HEIGHT = 600;
24
+ /** Furthest the view can scroll with this content. */
25
+ const MAX_SCROLL = CONTENT_HEIGHT - CLIENT_HEIGHT;
26
+
27
+ /** Project the content layout into viewport coordinates at `scrollTop`. */
28
+ function rectsAt(scrollTop: number): VisibleStepRect[] {
29
+ return CONTENT_RECTS.map((rect) => ({
30
+ index: rect.index,
31
+ top: rect.top - scrollTop,
32
+ bottom: rect.bottom - scrollTop,
33
+ }));
34
+ }
35
+
36
+ function viewport(
37
+ scrollTop: number,
38
+ overrides: Partial<VisibleStepViewport> = {},
39
+ ): VisibleStepViewport {
40
+ return {
41
+ top: 0,
42
+ bottom: CLIENT_HEIGHT,
43
+ scrollTop,
44
+ clientHeight: CLIENT_HEIGHT,
45
+ scrollHeight: CONTENT_HEIGHT,
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ function at(scrollTop: number, overrides: Partial<VisibleStepViewport> = {}) {
51
+ return resolveVisibleStepIndex(
52
+ viewport(scrollTop, overrides),
53
+ rectsAt(scrollTop),
54
+ );
55
+ }
56
+
57
+ describe('resolveVisibleStepIndex', () => {
58
+ test('reports the topmost step on screen', () => {
59
+ expect(at(0)).toBe(0);
60
+ expect(at(250)).toBe(1);
61
+ });
62
+
63
+ test('reports the last step once the end of the content is on screen', () => {
64
+ // The regression: at full scroll step 1's tail is still the topmost thing
65
+ // on screen, but the end of the content has been reached.
66
+ expect(at(MAX_SCROLL)).toBe(3);
67
+ });
68
+
69
+ test('does not report the end while content remains below', () => {
70
+ expect(at(300)).toBe(1);
71
+ // Extra scrollable content below must not be mistaken for the end: step 3
72
+ // is still well below the viewport here.
73
+ expect(at(400, { scrollHeight: CONTENT_HEIGHT + 4000 })).toBe(1);
74
+ });
75
+
76
+ test('trusts the scroll extent when the container box is not authoritative', () => {
77
+ // Nested scroller: the container's own box reports a bottom edge far above
78
+ // the real end, so only scrollTop/clientHeight/scrollHeight say we're done.
79
+ expect(at(MAX_SCROLL, { bottom: 100 })).toBe(3);
80
+ });
81
+
82
+ test('ignores the scroll extent when the container does not actually scroll', () => {
83
+ // An ancestor scrolls instead: scrollTop is pinned at 0 and scrollHeight
84
+ // equals clientHeight, which must not read as 'at the end' on first paint.
85
+ const notAScroller = { scrollTop: 0, clientHeight: 600, scrollHeight: 600 };
86
+ expect(at(0, { ...notAScroller, bottom: 100 })).toBe(0);
87
+ });
88
+
89
+ test('tolerates a bottom edge a fraction of a pixel off', () => {
90
+ expect(
91
+ at(MAX_SCROLL, { scrollHeight: CONTENT_HEIGHT + AT_END_TOLERANCE_PX }),
92
+ ).toBe(3);
93
+ });
94
+
95
+ test('returns null when nothing is on screen', () => {
96
+ expect(resolveVisibleStepIndex(viewport(0), [])).toBeNull();
97
+ expect(
98
+ resolveVisibleStepIndex(viewport(0), [
99
+ { index: 0, top: -800, bottom: -400 },
100
+ ]),
101
+ ).toBeNull();
102
+ });
103
+
104
+ test('uses the highest index even when rects arrive out of order', () => {
105
+ const shuffled = [...rectsAt(MAX_SCROLL)].reverse();
106
+ expect(resolveVisibleStepIndex(viewport(MAX_SCROLL), shuffled)).toBe(3);
107
+ });
108
+ });
@@ -0,0 +1,76 @@
1
+ /**
2
+ * visibleStep — pick which step a scroll position should report as current.
3
+ *
4
+ * Kept separate from the component so the geometry can be tested directly:
5
+ * the answer depends on how tall the final snippet is relative to the
6
+ * viewport, which is not something to eyeball.
7
+ */
8
+
9
+ /** A mounted CodeView item reduced to what the choice depends on. */
10
+ export interface VisibleStepRect {
11
+ /** Step index parsed from the item id. */
12
+ index: number;
13
+ /** Viewport-relative top edge, in px. */
14
+ top: number;
15
+ /** Viewport-relative bottom edge, in px. */
16
+ bottom: number;
17
+ }
18
+
19
+ /** The scroll container's box plus its scroll metrics, in px. */
20
+ export interface VisibleStepViewport {
21
+ top: number;
22
+ bottom: number;
23
+ scrollTop: number;
24
+ clientHeight: number;
25
+ scrollHeight: number;
26
+ }
27
+
28
+ /** Sub-pixel slack so a bottom edge that lands a hair off still counts. */
29
+ export const AT_END_TOLERANCE_PX = 2;
30
+
31
+ /**
32
+ * Index of the step to report as current.
33
+ *
34
+ * Normally the topmost step still on screen. The exception is the end of the
35
+ * content: when the final snippet is shorter than the viewport, reaching the
36
+ * bottom still leaves the previous step's tail on screen, and reporting that
37
+ * would strand a progress readout one step short of full.
38
+ *
39
+ * 'At the end' is accepted two ways, because either can be the authoritative
40
+ * one depending on how the view is nested:
41
+ * - the lowest mounted item's bottom edge has entered the container's box, or
42
+ * - the container's own scroll offset has reached its scroll extent.
43
+ *
44
+ * The second signal is only consulted when the container actually scrolls. If
45
+ * an ancestor scrolls instead, `scrollHeight` equals `clientHeight` and
46
+ * `scrollTop` is pinned at 0, which would otherwise read as 'at the end' from
47
+ * the very first pixel.
48
+ *
49
+ * Returns `null` when nothing measurable is on screen.
50
+ */
51
+ export function resolveVisibleStepIndex(
52
+ viewport: VisibleStepViewport,
53
+ rects: readonly VisibleStepRect[],
54
+ ): number | null {
55
+ let topIndex: number | null = null;
56
+ let lastIndex: number | null = null;
57
+ let lowestBottom = Number.NEGATIVE_INFINITY;
58
+
59
+ for (const rect of rects) {
60
+ if (topIndex == null && rect.bottom > viewport.top) topIndex = rect.index;
61
+ if (lastIndex == null || rect.index > lastIndex) lastIndex = rect.index;
62
+ if (rect.bottom > lowestBottom) lowestBottom = rect.bottom;
63
+ }
64
+ if (topIndex == null) return null;
65
+
66
+ const reachedLastItem = lowestBottom <= viewport.bottom + AT_END_TOLERANCE_PX;
67
+ const containerScrolls = viewport.scrollHeight > viewport.clientHeight;
68
+ const reachedScrollEnd =
69
+ containerScrolls &&
70
+ viewport.scrollTop + viewport.clientHeight >=
71
+ viewport.scrollHeight - AT_END_TOLERANCE_PX;
72
+
73
+ return reachedLastItem || reachedScrollEnd
74
+ ? (lastIndex ?? topIndex)
75
+ : topIndex;
76
+ }
@@ -173,6 +173,7 @@ function FlowsDemo() {
173
173
  proposedAliases,
174
174
  resolveSymbol,
175
175
  onSymbolClick,
176
+ onVisibleStepChange,
176
177
  }: TrailViewerContext) => (
177
178
  <PierreTrailCodeView
178
179
  trail={trail}
@@ -183,6 +184,7 @@ function FlowsDemo() {
183
184
  proposedAliases={proposedAliases}
184
185
  resolveSymbol={resolveSymbol}
185
186
  onSymbolClick={onSymbolClick}
187
+ onVisibleStepChange={onVisibleStepChange}
186
188
  />
187
189
  ),
188
190
  [],
@@ -281,6 +283,7 @@ function ProposedMissingStepDemo() {
281
283
  proposedAliases,
282
284
  resolveSymbol,
283
285
  onSymbolClick,
286
+ onVisibleStepChange,
284
287
  }: TrailViewerContext) => (
285
288
  <PierreTrailCodeView
286
289
  trail={trail}
@@ -291,6 +294,7 @@ function ProposedMissingStepDemo() {
291
294
  proposedAliases={proposedAliases}
292
295
  resolveSymbol={resolveSymbol}
293
296
  onSymbolClick={onSymbolClick}
297
+ onVisibleStepChange={onVisibleStepChange}
294
298
  />
295
299
  ),
296
300
  [],
@@ -480,6 +484,7 @@ function ClickableConstructsDemo() {
480
484
  proposedAliases,
481
485
  resolveSymbol,
482
486
  onSymbolClick,
487
+ onVisibleStepChange,
483
488
  }: TrailViewerContext) => (
484
489
  <PierreTrailCodeView
485
490
  trail={trail}
@@ -490,6 +495,7 @@ function ClickableConstructsDemo() {
490
495
  proposedAliases={proposedAliases}
491
496
  resolveSymbol={resolveSymbol}
492
497
  onSymbolClick={onSymbolClick}
498
+ onVisibleStepChange={onVisibleStepChange}
493
499
  />
494
500
  ),
495
501
  [],