diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index 6a6f840d..dcfc8ad6 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -125,6 +125,10 @@ _Not to be confused with [parented](#parented-layout)_ — loose is the absence **canonical fixture** (Stammbaum) — `frontend/src/lib/person/genealogy/__fixtures__/stammbaum.json`, a pinned `/api/network` snapshot used by `buildLayout.test.ts` for structural-property assertions against real data. Captured locally via `frontend/scripts/capture-network-fixture.mjs` with explicit credentials and a localhost backend; never invoked from CI. Sanity-gated by `validateFixture.ts` (≥ 50 nodes / ≥ 5 generations / ≥ 1 SPOUSE_OF edge / ≥ 1 multi-spouse person). +**pan/zoom view state** `[#692]` — the `{ x, y, z }` triple (`PanZoomState` in `frontend/src/lib/person/genealogy/panZoom.ts`) describing the Stammbaum canvas position: `x`/`y` are pan offsets applied to the SVG viewBox centre, `z` is the zoom factor (clamped 0.25–3.0). Mirrored into the shareable URL as `?cx&cy&z` and seeded server-side from those params. See [ADR-026](adr/026-stammbaum-custom-viewbox-pan-zoom.md). + +**fit-to-screen** `[user-facing, #692]` — the Stammbaum control (`⤢`) and initial state that frames the whole tree in the viewport. Because the base viewBox already encloses the layout at `z=1`, fit-to-screen is simply the default view `{x:0, y:0, z:1}`. + --- ## Other Domain Terms diff --git a/docs/adr/026-stammbaum-custom-viewbox-pan-zoom.md b/docs/adr/026-stammbaum-custom-viewbox-pan-zoom.md new file mode 100644 index 00000000..65b2ae9d --- /dev/null +++ b/docs/adr/026-stammbaum-custom-viewbox-pan-zoom.md @@ -0,0 +1,56 @@ +# ADR-026 — Stammbaum pan/zoom is a custom viewBox layer, not a third-party library + +**Date:** 2026-05-29 +**Status:** Accepted +**Issue:** #692 (mobile read path — pan, zoom, fit-to-view); supersedes OQ-007 +**Milestone:** Stammbaum mobile read path + +--- + +## Context + +#692 makes `/stammbaum` usable on phones: drag-to-pan, pinch/keyboard/wheel zoom, +fit-to-screen, recentre-on-person, a shareable URL view state, and an edge-fade +affordance. During issue grooming, **OQ-007 was resolved to adopt the `panzoom` +library** (timmywil v4.x) on the team's recommendation, pinned per NFR-MAINT-001. + +That recommendation predated a load-bearing implementation detail: `StammbaumTree.svelte` +already renders zoom by **deriving the SVG `viewBox`** (`w = baseW / z`, centred on the +layout bounding box, `preserveAspectRatio="xMidYMid meet"`) — not by applying a CSS +`transform`. The `panzoom` library operates by writing `transform` to a DOM node. Adopting +it would mean: + +- abandoning the proven viewBox derivation and the in-SVG generation gutter (#689), which + lives in SVG user-space coordinates and would have to be reconciled with a CSS-transformed + parent; +- re-deriving fit-to-screen, recentre, and the `?cx&cy&z` URL state against the library's + transform coordinate system; +- a client-only lazy import to keep the SSR-rendered tree from touching `window` at module + load; and +- ~8 KB of bundle for behaviour we can express in a few pure functions. + +## Decision + +**Build pan/zoom as a thin custom layer over the existing viewBox**, with no third-party +dependency. This reverses OQ-007. + +- All geometry is pure and unit-tested in `frontend/src/lib/person/genealogy/panZoom.ts`: + `clampZoom`, `parsePanZoomParams`/`serializePanZoomParams`, `screenDeltaToSvg`, + `zoomAtPoint` (centroid-anchored), `clampPan` (edge-clamp), `recentreOn`, `lerpView`. +- Pan offsets shift the viewBox centre; zoom scales its width/height. The default + `{x:0, y:0, z:1}` already frames the whole tree, so **fit-to-screen is a reset to the + default** — no bounding-box recomputation. +- DOM event wiring lives in the `panZoomGestures` action (pointer/wheel/pinch + inertia, + reduced-motion aware) and a keyboard handler on the SVG; both delegate to the pure module. + +## Consequences + +- **NFR-MAINT-001 (library pinning + feature-flag fallback) is moot** — no library is + adopted. The "swap-out point" is `panZoom.ts` + `panZoomGestures.ts`. +- Text stays vector-crisp at any zoom (SVG-native scaling), satisfying US-PAN-002 AC5. +- The #689 gutter and the #361 seeded-rank invariant are untouched by the pan/zoom layer. +- Geometry is testable in the fast node project; only the DOM glue needs the browser project. +- Trade-off: we own the inertia/pinch code (~a few hundred lines across the action) rather + than delegating it. This is acceptable given the testability and zero-dependency wins. + +The issue body's OQ-007 row is updated to point at this ADR.