fix(nav): scroll-restore key auf nav.from.url, nicht location
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 2m51s

Live-Test auf kochwas-dev offenbarte: bei Browser-Back hat der Browser
die History bereits gepoppt, bevor SvelteKit beforeNavigate feuert —
location.pathname war damit schon die Ziel-URL. recordScroll() schrieb
also den 0-Wert der Recipe-Page in den Slot der Home-Page und wischte
den eigentlich gemerkten Wert (z. B. 500) raus. Restore las dann 0,
fiel unter MIN_RESTORE_Y und tat nichts.

Fix: recordScroll(nav.from?.url) und restoreScroll(type, nav.to?.url).
Helper bekommen die URL explizit reingereicht — keine Abhängigkeit
mehr von location und damit kein Race mit der Browser-History.

Tests: zusätzliche Regression "does not overwrite a stored URL when
called with a different from-url" plus Skip-Pfade fuer null URLs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-22 09:28:20 +02:00
parent 442076a278
commit f3e2cebfb4
3 changed files with 57 additions and 32 deletions

View File

@@ -5,9 +5,11 @@
// wishlist, shopping-list) are still empty at that point, so the saved
// scrollY can't be reached and the browser clamps to 0.
//
// We patch this by saving scrollY on beforeNavigate and re-applying it
// after popstate as soon as the document is tall enough — using rAF
// polling with a hard time budget so we never spin.
// We patch this by saving scrollY on beforeNavigate (keyed by the URL
// we're leaving — NOT location.pathname, which on popstate is already
// the new URL by the time the callback fires) and re-applying it after
// popstate as soon as the document is tall enough — rAF-polled with a
// hard time budget so we never spin.
const STORAGE_KEY = 'kochwas:scroll';
const POLL_BUDGET_MS = 1500;
@@ -34,26 +36,26 @@ function writeMap(map: ScrollMap): void {
}
}
function urlKey(): string {
if (typeof location === 'undefined') return '';
return location.pathname + location.search;
function keyFor(url: URL): string {
return url.pathname + url.search;
}
export function recordScroll(): void {
export function recordScroll(fromUrl: URL | null | undefined): void {
if (typeof window === 'undefined') return;
const key = urlKey();
if (!key) return;
if (!fromUrl) return;
const map = readMap();
map[key] = window.scrollY;
map[keyFor(fromUrl)] = window.scrollY;
writeMap(map);
}
export function restoreScroll(navType: string | null | undefined): void {
export function restoreScroll(
navType: string | null | undefined,
toUrl: URL | null | undefined
): void {
if (typeof window === 'undefined') return;
if (navType !== 'popstate') return;
const key = urlKey();
if (!key) return;
const target = readMap()[key];
if (!toUrl) return;
const target = readMap()[keyFor(toUrl)];
if (!target || target < MIN_RESTORE_Y) return;
const start = performance.now();

View File

@@ -98,8 +98,8 @@
}
}
beforeNavigate(() => {
recordScroll();
beforeNavigate((nav) => {
recordScroll(nav.from?.url);
});
afterNavigate((nav) => {
@@ -112,7 +112,7 @@
// wurde.
void wishlistStore.refresh();
void shoppingCartStore.refresh();
restoreScroll(nav.type);
restoreScroll(nav.type, nav.to?.url);
});
onMount(() => {