feat(stammbaum): mobile read path — pan, zoom, fit-to-view (#692) #694

Merged
marcel merged 39 commits from feat/issue-692-stammbaum-mobile-panzoom into main 2026-05-30 07:43:44 +02:00
Showing only changes of commit 578bebbd8b - Show all commits

View File

@@ -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>