fix(tanstack): keep the page's scroll when Studio shows a save

Merged
TL
6h ago
+57-2

Why

With #808 live, a save in Studio's site editor re-renders a TanStack page through its router. After every load the router either restores the position saved for the URL or scrolls to the top, and router.invalidate() has no resetScroll option. So an editor working on the footer was scrolled back to the top on every save. The navigation path already passed resetScroll: false, but the invalidate after it, and the refresh for the page's own URL, didn't.

What

  • StudioRefresh runs each refresh with router.options.scrollRestoration set to a function that answers false. The router reads that as "leave the scroll alone", for both restoring and resetting. The site's own setting comes back two frames after the last render, so a later navigation scrolls as usual.
  • Overlapping refreshes share one override. A failed refresh also gives the setting back.
  • Next.js isn't affected: router.refresh() and router.replace(…, { scroll: false }) already keep the scroll.

Verification

  • On a deployed TanStack storefront scrolled to the footer (yā‰ˆ7500), the page's own __decoStudioRefresh(location.href) went to y=0. Running the same router.invalidate() with scrollRestoration overridden like this kept it near the footer, and the page rendered the new data.
  • New tests in StudioRefresh.test.tsx: restoration answers false during the loads of both paths and is back to the site's value afterwards, including after a failed refresh.
  • bun run check passes. vitest run packages/tanstack: 194 pass.

šŸ¤– Generated with Claude Code


Summary by cubic

Keeps the page's scroll position when Studio shows a save on TanStack pages. Previously, re-rendering the page through router.invalidate() made the router restore the URL's saved position or scroll to the top, so an editor working on the footer jumped back up on every save.

StudioRefresh now sets router.options.scrollRestoration to a function answering false while the refresh runs, which the router reads as "leave the scroll alone". The site's own setting is restored two frames after the last render; overlapping refreshes share the override, and a failed refresh also gives it back. Next.js was already unaffected, since router.refresh() and router.replace(…, { scroll: false }) keep the scroll.