From 36ebb53c0f8c592ef5e9876ddad703c5069d80a0 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Thu, 14 May 2026 19:49:28 -0700 Subject: [PATCH] fix(runtime): smooth scroll between slides on navigation events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 added scrollToSlide to navigate.next/previous action lists so remote-driven nav (from the presenter tab) would also scroll the audience tab. But scrollToSlide was using behavior:"instant" — which was correct for its original use as the initial state-entry action (load straight to the URL-hash slide) but wrong for nav transitions, which we want animated. Split into two actions: - scrollToSlide: behavior:"smooth", used by navigate.next/previous. - jumpToSlide: behavior:"instant", used as the entry action on initial state so the page loads positioned without animating through every preceding slide. Verified mid-scroll sampling: scrollTop transitions 0 → 33 → 900 over ~800ms, no longer a single-frame jump. Co-Authored-By: Claude Opus 4.7 --- packages/runtime/actor/machine.ts | 2 +- packages/runtime/actor/setup.ts | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/runtime/actor/machine.ts b/packages/runtime/actor/machine.ts index b64c95e..32797a7 100644 --- a/packages/runtime/actor/machine.ts +++ b/packages/runtime/actor/machine.ts @@ -26,7 +26,7 @@ export const presentation = machineBase.createMachine({ }, }, independent: { - entry: ["scrollToSlide"], + entry: ["jumpToSlide"], on: { "navigate.next": { actions: [ diff --git a/packages/runtime/actor/setup.ts b/packages/runtime/actor/setup.ts index 7779146..a1967c0 100644 --- a/packages/runtime/actor/setup.ts +++ b/packages/runtime/actor/setup.ts @@ -26,12 +26,28 @@ export const machineBase = setup({ } }), /** - * Scroll the audience-mode viewport to the current slide. No-op in - * presenter mode (no scrolling there) and on empty decks. Used both - * as the entry action for initial state and as a side effect of - * remote-driven navigation events broadcast from the presenter tab. + * Smooth-scroll the audience-mode viewport to the current slide. + * Used as a side effect of navigation events (toolbar buttons, + * keyboard, broadcast from the presenter tab) so the audience + * sees a clean animation between slides. No-op in presenter mode + * (no scrolling there) and on empty decks. */ scrollToSlide({ context }) { + if (context.role !== "audience") return; + if (context.slides.length === 0) return; + const slide = context.slides[context.currentIndex]; + if (!slide) return; + document.querySelector(`#${slide.id}`)?.scrollIntoView({ + behavior: "smooth", + }); + }, + + /** + * Instant-jump variant used as the initial state entry action so the + * page loads positioned at the URL-hash slide without animating + * through every slide above it. + */ + jumpToSlide({ context }) { if (context.role !== "audience") return; if (context.slides.length === 0) return; const slide = context.slides[context.currentIndex]; -- 2.51.2