fix(stammbaum): URL pan/zoom sync never fired — gate replaceState on router-ready (#692)
replaceState throws 'before the router is initialized' during hydration, which killed the sync $effect on its first tick so the URL never updated on pan/zoom. Gate the write behind a flag flipped after the first post-mount tick() (router started) plus a defensive try/catch. Verified live: zoom now updates ?z=. The prior component test mocked replaceState and masked this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack, tick } from 'svelte';
|
import { untrack, tick, onMount } from 'svelte';
|
||||||
import { m } from '$lib/paraglide/messages.js';
|
import { m } from '$lib/paraglide/messages.js';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { replaceState } from '$app/navigation';
|
import { replaceState } from '$app/navigation';
|
||||||
@@ -62,18 +62,33 @@ function fitToScreen() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror the view into shareable ?cx&cy&z params (OQ-003). Only `view` is a
|
// SvelteKit's replaceState throws "before the router is initialized" if called
|
||||||
// tracked dependency; the current URL is read untracked so the replaceState
|
// during hydration (the router sets `started = true` only after onMount + the
|
||||||
// write does not retrigger the effect. The state thus survives panel open/close
|
// first effect tick). Gate the URL sync on a flag flipped after the first
|
||||||
// (US-PANEL-002 AC1) and a shared link reproduces it (AC2).
|
// post-mount tick() — which resolves once hydration is complete — so the write
|
||||||
|
// only ever runs against a ready router.
|
||||||
|
let routerReady = $state(false);
|
||||||
|
onMount(() => {
|
||||||
|
tick().then(() => (routerReady = true));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mirror the view into shareable ?cx&cy&z params (OQ-003). Only `view` and
|
||||||
|
// `routerReady` are tracked; the current URL is read untracked so the
|
||||||
|
// replaceState write does not retrigger the effect. The state thus survives
|
||||||
|
// panel open/close (US-PANEL-002 AC1) and a shared link reproduces it (AC2).
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const { cx, cy, z } = serializePanZoomParams(view);
|
const { cx, cy, z } = serializePanZoomParams(view);
|
||||||
|
if (!routerReady) return;
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set('cx', cx);
|
url.searchParams.set('cx', cx);
|
||||||
url.searchParams.set('cy', cy);
|
url.searchParams.set('cy', cy);
|
||||||
url.searchParams.set('z', z);
|
url.searchParams.set('z', z);
|
||||||
replaceState(url, page.state);
|
try {
|
||||||
|
replaceState(url, page.state);
|
||||||
|
} catch {
|
||||||
|
// Router not ready yet — the next view change retries.
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user