familytree

A self-hosted family tree builder.
Log | Files | Refs | README

commit 2f4b142a5dc706091df105ba54e243c99c300247
parent 2358b8078b8d5c9da860df5ec6d18f27345b5815
Author: Julian Piribauer <julian.piribauer@gmail.com>
Date:   Sat,  9 May 2026 19:02:46 +0000

Fix generation misalignment when one side of a partnership has more ancestors

Three changes to the generation-ranking fix loop:
- Partner alignment now also handles the case where both partners are
  children (had been skipped), pushing the lower one down to max
- New step: pull parents down so they sit exactly one row above their
  children (prevents Ferdinand/Christine sitting 3 rows above Julian
  when Martin was pushed to gen 2 by his partnership with Silke)
- Together these ensure adding Mutter/Vater von Omi above Ingeborg no
  longer cascades incorrectly through the Piribauer side

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Diffstat:
Mstatic/app.js | 38++++++++++++++++++++++++++++++++------
1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/static/app.js b/static/app.js @@ -264,20 +264,31 @@ function computeLayout({ people, partnerships, parentLinks: rawLinks }, opts = { } for (const p of people) { if (gen[p.id] === undefined) gen[p.id] = 0; } - // Iteratively fix generations: pull root-partners to their partner's row, - // then push children down accordingly. Must be one combined loop because - // moving a child up may require moving that child's root-partner up too. + // Iteratively fix generations until stable: + // 1. Align partners to the same row (max of the two). + // 2. Push children to be exactly one row below their parents. + // 3. Pull parents down so they are exactly one row above their children + // (handles the case where a newly-added ancestor level pushes one side + // of a partnership down but leaves the other side's ancestors too high). let changed = true; let _safetyIter = 0; while (changed && _safetyIter++ < people.length * 4) { changed = false; + // Step 1 – partner alignment for (const ps of partnerships) { - const g1 = gen[ps.person1_id], g2 = gen[ps.person2_id]; + const g1 = gen[ps.person1_id] ?? 0, g2 = gen[ps.person2_id] ?? 0; if (g1 !== g2) { - if (!childIds.has(ps.person1_id) && g2 !== undefined) { gen[ps.person1_id] = g2; changed = true; } - else if (!childIds.has(ps.person2_id) && g1 !== undefined) { gen[ps.person2_id] = g1; changed = true; } + const maxG = Math.max(g1, g2); + if (!childIds.has(ps.person1_id)) { gen[ps.person1_id] = maxG; changed = true; } + else if (!childIds.has(ps.person2_id)) { gen[ps.person2_id] = maxG; changed = true; } + else { + // Both have parents — push the lower one down to match the higher + if (g1 < maxG) { gen[ps.person1_id] = maxG; changed = true; } + else { gen[ps.person2_id] = maxG; changed = true; } + } } } + // Step 2 – push children down for (const pl of parentLinks) { let parentGen; if (pl.partnership_id) { @@ -291,6 +302,21 @@ function computeLayout({ people, partnerships, parentLinks: rawLinks }, opts = { if ((gen[pl.child_id] ?? 0) < expected) { gen[pl.child_id] = expected; changed = true; } } } + // Step 3 – pull parents down (increase their gen so they sit exactly one + // row above their child, not two or three rows above) + for (const pl of parentLinks) { + const expectedParentGen = (gen[pl.child_id] ?? 0) - 1; + if (pl.partnership_id) { + const ps = partnerships.find(p => p.id === pl.partnership_id); + if (ps) { + for (const pid of [ps.person1_id, ps.person2_id]) { + if ((gen[pid] ?? 0) < expectedParentGen) { gen[pid] = expectedParentGen; changed = true; } + } + } + } else if (pl.parent_id != null) { + if ((gen[pl.parent_id] ?? 0) < expectedParentGen) { gen[pl.parent_id] = expectedParentGen; changed = true; } + } + } } const byGen = {};