Skip to content

fix(client): sync every window that moves, without echoing followed positions - #2805

Open
CurlyPeter wants to merge 1 commit into
slidevjs:mainfrom
CurlyPeter:fix/sync-every-window-leads
Open

CurlyPeter wants to merge 1 commit into
slidevjs:mainfrom
CurlyPeter:fix/sync-every-window-leads

Conversation

@CurlyPeter

Copy link
Copy Markdown

Description

When more than one window drives a presentation, the navigation sync goes wrong in a few ways. Typical setups: a presenter view on a laptop plus a second presenter view on a tablet, a presenter view plus an audience window on localhost, either of them over a slow connection.

  1. A presenter view ignores another presenter view. onPatch skips updates whose lastUpdate.type equals the receiver's own type, so a move in one presenter view is not followed by the other presenter views (likewise for viewers on a trusted origin).
  2. Following writes the position back. updateSharedState runs after every navigation, including the one onPatch starts to follow an update, so every follower sends the position it just received under its own lastUpdate. On a slow connection such a write can reach the server after a newer move, and the other windows jump back.
  3. Loading a window moves everyone. The initial navigation calls updateSharedState too, so a window that loads at an older URL (a reload after a lost connection, a restored tab, a presenter view opened at /presenter/) moves the whole presentation to that slide.

This PR, all in packages/client/setup/root.ts:

  • onPatch skips only the window's own updates (lastUpdate.id).
  • The position a follow navigates to is remembered. While the window stands there it sends nothing; its first own move clears it. A failed or cancelled navigation sends nothing. An incoming update is compared with a follow still under way, so a quick move back is not taken as already done.
  • A window that loads joins the position the server holds instead of sending its URL's slide. It compares the URL's clicks, since the clicks context reads 0 until the slide is mounted. Without a shared position yet (page: 0), or without a server, the loading window's position is shared as before.

Viewers on untrusted origins are unchanged: they never send.

Behaviour change to note: with a server that already holds a position, opening a URL with a slide number (/12, /presenter/5) now joins the current slide instead of moving everyone there; navigating afterwards works as before. If you prefer to honour an explicitly opened URL, I can limit joining to reloads and to the redirect from /.

Verification

A Playwright script against a dev server: two presenter views and two viewers on a trusted origin, each in its own browser context, later a fifth presenter view whose sync requests are delayed by 150 ms. Every window's nav POSTs are recorded.

  • Each window that moves is followed by all others, and only the moving window writes a new lastUpdate. With main's setup/root.ts, two or three followers wrote the position back on every move.
  • Eight trials of two key presses 100 to 200 ms apart on the slow connection all end two slides on.
  • A viewer and a presenter view loading at an older URL join the current slide, and the others stay. With main's version, they moved everyone back.
  • A window reloading on another click step of the same slide joins the current step.
  • A window with sending switched off moves only itself.
  • Click steps are followed by every window and written only by the window that clicked.

Measured on v53.0.0; the change applies unchanged to main. eslint on the changed file and vue-tsc --noEmit pass on main. vitest shows the same 11 failures as on main (API snapshots and the syntax integration test, unrelated).

Related: #2611 / #2612 are about coordinating several presenters (who may drive). This PR adds no such concept; it makes the existing "everyone follows the latest move" consistent.

…ositions

- skip incoming updates by the sender's id instead of its type, so a presenter
  view follows another presenter view (and a viewer on a trusted origin
  another such viewer)
- do not write the shared state back after a navigation that only followed it,
  or a failed one: on a slow connection that write could arrive after a newer
  move and send the other windows back
- compare an update with a follow still under way, not only with the current
  route, so a quick move back is not taken as already done
- a window that loads joins the shared position instead of moving everyone to
  the slide in its URL (comparing the URL's clicks, since the clicks context
  reads 0 before the slide mounts); without a shared position yet, or without
  a server, its own position is shared as before
@netlify

netlify Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for slidev ready!

Name Link
🔨 Latest commit c20abfb
🔍 Latest deploy log https://app.netlify.com/projects/slidev/deploys/6ae3b08aa92ac8516f84e85a
😎 Deploy Preview https://deploy-preview-2805--slidev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant