Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m35s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m38s
CI / fail2ban Regex (pull_request) Successful in 41s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
- Rail chip background opaque (was /85) so G{n} labels stay AA-legible over
tree content (Leonie).
- Rail effect: replace the reactKey hack with an inputsFinite guard that both
tracks deps and guards NaN; name the fallback-stack magics; correct the stale
'xMidYMid' comment (the CTM mapping is preserveAspectRatio-agnostic) (Felix/Markus).
- GLOSSARY zoom range 0.25–3.0 → 0.25–10; ADR-027 preserveAspectRatio note
xMidYMid → xMinYMin (Elicit traceability).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 lines
2.9 KiB
Markdown
58 lines
2.9 KiB
Markdown
# ADR-027 — 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="xMinYMin meet"` so a fresh visit anchors to the
|
|
tree's top-left corner) — 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.
|