fix(tanstack): keep the page's scroll when Studio shows a save
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
StudioRefreshruns each refresh withrouter.options.scrollRestorationset to a function that answersfalse. 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()androuter.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 samerouter.invalidate()withscrollRestorationoverridden like this kept it near the footer, and the page rendered the new data. - New tests in
StudioRefresh.test.tsx: restoration answersfalseduring the loads of both paths and is back to the site's value afterwards, including after a failed refresh. bun run checkpasses.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.