From e0573e6be266a4fbb6a266e87f8296563da29cd0 Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Thu, 11 Jun 2026 09:21:30 -0400 Subject: [PATCH] Move cardToMiddle vertical-centering fix into the family-chart patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fold the fly-to vertical-centering fix into our patch-package patch (alongside the existing spouse-layout fix) instead of compensating in app code, and revert the in-app workaround so the two don't double-correct. - patches/family-chart+0.9.0.patch: cardToMiddle now scales datum.y by the zoom k in both dist builds (.js + .esm.js), matching datum.x. Verified the patch applies cleanly (patch-package --error-on-fail). - tree/page.tsx: the cardToMiddle caller passes raw y again (the patched library does the scaling now); pre-scaling here too would double-correct. Behavior is identical to the previous in-app fix — both center the node exactly. - CLAUDE.md: documents the two family-chart patches, how to regenerate them, and that both should be upstreamed. The cardToMiddle fix is submitted upstream (donatso/family-chart#103, issue #102); the spouse-layout fix is a TODO. The frontend Dockerfile already COPYs patches/ before npm ci, so the fix is in the production build. Signed-off-by: Justin Paul --- CLAUDE.md | 13 +++++++++++++ frontend/app/trees/[id]/tree/page.tsx | 10 +++++----- frontend/patches/family-chart+0.9.0.patch | 22 ++++++++++++++++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c35375c..1a8ed9c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,6 +77,19 @@ Don't get ahead of the phases. GEDCOM and the assistant's propose-diff foundatio - **Privacy/assistant/hint code gets extra care** — these are the areas where bugs do real harm. Prefer a design note before a large change. - **No secrets in the repo.** Config via env; provide `.env.example` with placeholders. +## Patched dependencies (family-chart) + +The tree view uses **family-chart** (d3-based). Two adjustments live in the repo: + +- **CSS is vendored** at `frontend/app/trees/[id]/tree/chart.css` — the package blocks its CSS subpath export, so we copy it in. +- **The library is patched** via `patch-package` (`frontend/patches/family-chart+0.9.0.patch`, applied by the `postinstall` hook; the backend/frontend Dockerfiles `COPY patches` before install). Both hunks touch `dist/family-chart.js` **and** `dist/family-chart.esm.js` (the app loads the `esm` build). Current fixes: + 1. **Spouse-centering layout** (`setupSpouses` / `sortChildrenWithSpouses`) — center a person between two spouses with children under the correct pair. + 2. **`cardToMiddle` vertical centering** — the lib scaled `datum.x` by the zoom factor `k` but not `datum.y`, so "fly to a node" drifted vertically at any zoom ≠ 1; we add the missing `* k`. + +To change a patch: edit the file(s) under `node_modules/family-chart/dist/`, then `cd frontend && npx patch-package family-chart` to regenerate, and verify with `npx patch-package --error-on-fail`. + +**Upstream these.** Both are general library bugfixes, not app-specific. The `cardToMiddle` fix is submitted — **donatso/family-chart#103** (issue **#102**). The spouse-layout fix still needs upstreaming; do it when there's time. When a fixed release ships, drop the corresponding patch hunk **and** remove any in-app compensation (e.g. the `cardToMiddle` caller in `tree/page.tsx` passes raw `y` precisely because the patch fixes it — pre-scaling there too would double-correct). + ## License & contribution terms Provenance is **source-available** under **BUSL-1.1** (see [LICENSE](LICENSE)): free for personal/family/non-commercial use, no third-party commercial hosting, and each release converts to **AGPL-3.0** four years after it ships. The DCO sign-off keeps the licensing chain clean so the maintainer can manage that conversion and a possible future hosted offering. Don't add code under an incompatible license, and don't vendor dependencies whose licenses conflict with eventual AGPL distribution. diff --git a/frontend/app/trees/[id]/tree/page.tsx b/frontend/app/trees/[id]/tree/page.tsx index 02b3c35..12bd82d 100644 --- a/frontend/app/trees/[id]/tree/page.tsx +++ b/frontend/app/trees/[id]/tree/page.tsx @@ -315,12 +315,12 @@ export default function TreePage() { try { const rect = svg.getBoundingClientRect(); const scale = handlers.getCurrentZoom ? handlers.getCurrentZoom(svg).k : 1; - // family-chart's cardToMiddle scales datum.x by the zoom but NOT - // datum.y (a library bug), so vertical centering is only correct at - // scale 1 and drifts by datum.y·(k−1) otherwise — landing "below the - // tree". Pre-multiply y by the scale to cancel the missing ·k. + // cardToMiddle centers the datum at the current zoom. (Its vertical + // centering at non-1 zoom is fixed in our family-chart patch — see + // CLAUDE.md / upstream PR donatso/family-chart#103 — so we pass the + // raw y; do NOT pre-scale it here or it double-corrects.) handlers.cardToMiddle({ - datum: { x: xy.x, y: xy.y * scale }, + datum: xy, svg, svg_dim: { width: rect.width, height: rect.height }, scale, diff --git a/frontend/patches/family-chart+0.9.0.patch b/frontend/patches/family-chart+0.9.0.patch index 544fb9f..9d34daf 100644 --- a/frontend/patches/family-chart+0.9.0.patch +++ b/frontend/patches/family-chart+0.9.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/family-chart/dist/family-chart.esm.js b/node_modules/family-chart/dist/family-chart.esm.js -index 3867be0..560c99e 100644 +index 3867be0..656fafa 100644 --- a/node_modules/family-chart/dist/family-chart.esm.js +++ b/node_modules/family-chart/dist/family-chart.esm.js @@ -10,10 +10,10 @@ function sortChildrenWithSpouses(children, datum, data) { @@ -61,8 +61,17 @@ index 3867be0..560c99e 100644 if (!d.spouses) d.spouses = []; d.spouses.push(spouse); +@@ -1073,7 +1091,7 @@ function calculateTreeFit(svg_dim, tree_dim) { + return { k, x, y }; + } + function cardToMiddle({ datum, svg, svg_dim, scale, transition_time }) { +- const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y, t = { k, x: x / k, y: y / k }; ++ const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y * k, t = { k, x: x / k, y: y / k }; + positionTree({ t, svg, transition_time }); + } + function manualZoom({ amount, svg, transition_time = 500 }) { diff --git a/node_modules/family-chart/dist/family-chart.js b/node_modules/family-chart/dist/family-chart.js -index 1c750d4..47efcc2 100644 +index 1c750d4..edeb804 100644 --- a/node_modules/family-chart/dist/family-chart.js +++ b/node_modules/family-chart/dist/family-chart.js @@ -33,10 +33,9 @@ @@ -116,3 +125,12 @@ index 1c750d4..47efcc2 100644 if (!d.spouses) d.spouses = []; d.spouses.push(spouse); +@@ -1096,7 +1106,7 @@ + return { k, x, y }; + } + function cardToMiddle({ datum, svg, svg_dim, scale, transition_time }) { +- const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y, t = { k, x: x / k, y: y / k }; ++ const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y * k, t = { k, x: x / k, y: y / k }; + positionTree({ t, svg, transition_time }); + } + function manualZoom({ amount, svg, transition_time = 500 }) { -- 2.52.0