diff --git a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts index 267b1191..82d8f11e 100644 --- a/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts +++ b/frontend/src/lib/person/genealogy/layout/buildLayout.test.ts @@ -581,4 +581,36 @@ describe('buildLayout — ancestor centring invariant (#724)', () => { expect(intrudes, `foreign node ${id} interleaved into the R1 band`).toBe(false); } }); + + it('no bloodline is smeared across the canvas (per-bloodline span regression)', () => { + // Golden constant: measured 2026-06-04, the OLD per-generation block + // packer laid the canonical fixture out 4860px wide and smeared a single + // bloodline (Albert de Gruyter G0 … a far-right descendant) across that + // whole span — the bug. Total canvas width is NOT the right metric for the + // fix (centring every ancestor makes a 24-root forest wider overall, and + // pan/zoom from #692 handles navigation); per-bloodline compactness is. + // Each contiguous bloodline must occupy far less than the old full-canvas + // smear. Today the widest is Albert's at ~960px. + const OLD_FULL_CANVAS = 4860; + + const layout = buildLayout(fixtureNodes, fixtureEdges); + const forest = buildFamilyForest(fixtureNodes, fixtureEdges); + const bloodlineSpan = (root: Unit): number => { + const ids: string[] = []; + const collect = (u: Unit) => { + ids.push(...u.members); + u.children.forEach(collect); + }; + collect(root); + const xs = ids.map((id) => layout.positions.get(id)!.x); + return Math.max(...xs) + NODE_W - Math.min(...xs); + }; + + const spans = forest.roots.map(bloodlineSpan); + const widest = Math.max(...spans); + // Every bloodline is dramatically more compact than the old smear … + expect(widest).toBeLessThan(OLD_FULL_CANVAS); + // … and in fact comfortably under a quarter of it (no smear creeps back). + expect(widest).toBeLessThanOrEqual(OLD_FULL_CANVAS / 4); + }); });