tech-debt: replace Stammbaum DIY layout with a graph-layout dep (dagre) when it stops scaling #361

Open
opened 2026-04-27 22:45:26 +02:00 by marcel · 0 comments
Owner

Context

StammbaumTree.svelte currently lays the family graph out itself. The
algorithm is a single-pass heuristic:

  1. Iterative longest-path generation assignment (each node = max(parent gen) + 1,
    spouses pulled to share the deeper row, run to fixpoint).
  2. Per generation, build "blocks": sibling-groups (children of the same parent
    set) with loose spouses attached on the outer edge of their partner. Each
    block's parented members are centred under the parents' midpoint, then
    blocks are packed left-to-right.
  3. Render each block as a parent-midpoint drop → horizontal sibling bar →
    per-child vertical (so all connectors are 90°).

This works well for the canonical Familienarchiv shape — a tree with a few
married-in spouses (PR #360 covers this) — but the architecture has known
limits.

When this becomes painful — switch triggers

Open this issue (or stop deferring it) once any one of these is true:

  • A person has more than one spouse at the same generation (currently
    spousePairs is a Map<string, string>, only one spouse per person is
    tracked; remarriages will be silently dropped).
  • The graph has cross-cousin / consanguineous marriages — two members of
    the same family marrying each other create a cycle the heuristic cannot
    position cleanly.
  • A loose spouse has their own siblings or parents in the graph (today they
    attach as a single node next to their partner; if they have a sibling the
    attachment breaks).
  • More than ~50 family members are in the tree and edge crossings start to
    appear regularly.
  • A second, parallel family tree is added (Raddatz + de Gruyter shown side-
    by-side).

Proposed direction

Adopt dagre (~30 kB, layered DAG layout with edge-crossing minimisation).
Wrap it so the input is still our PersonNodeDTO[] + RelationshipDTO[] and
the output remains the same { positions, viewBox } shape StammbaumTree
already consumes — keeps the SVG render and connector style untouched.

Alternatives evaluated:

  • d3-hierarchy — tree-only, doesn't model multi-parent (DAG) correctly
  • family-chart — opinionated full component; would replace our SVG layer too
  • elkjs — more capable layout but ~10× larger bundle

Acceptance criteria

  • Layout responsibility is moved to dagre behind a small adapter
  • Existing visual contract holds: parent-pair drop + sibling bar +
    per-child vertical, spouse line with midpoint dot, navy/mint via existing
    --c-primary token
  • All existing component tests still pass (StammbaumTree.svelte.test.ts)
  • Add at least one regression test for a multi-marriage family
  • Bundle impact documented in the PR description

Out of scope

  • Pan / zoom-on-scroll / drag-to-rearrange (separate enhancement)
  • Persisting layout overrides per user
  • Full re-skin of the side panel

Open questions

  • Do we want dagre's layered layout for everything, or only fall back to it
    when the heuristic can't handle the shape?
  • Should we render orthogonal connectors via dagre's edge router, or keep
    the existing manual SVG render?
## Context `StammbaumTree.svelte` currently lays the family graph out itself. The algorithm is a single-pass heuristic: 1. Iterative longest-path generation assignment (each node = `max(parent gen) + 1`, spouses pulled to share the deeper row, run to fixpoint). 2. Per generation, build "blocks": sibling-groups (children of the same parent set) with loose spouses attached on the outer edge of their partner. Each block's parented members are centred under the parents' midpoint, then blocks are packed left-to-right. 3. Render each block as a parent-midpoint drop → horizontal sibling bar → per-child vertical (so all connectors are 90°). This works well for the canonical Familienarchiv shape — a tree with a few married-in spouses (PR #360 covers this) — but the architecture has known limits. ## When this becomes painful — switch triggers Open this issue (or stop deferring it) once **any one** of these is true: - A person has **more than one spouse** at the same generation (currently `spousePairs` is a `Map<string, string>`, only one spouse per person is tracked; remarriages will be silently dropped). - The graph has **cross-cousin / consanguineous marriages** — two members of the same family marrying each other create a cycle the heuristic cannot position cleanly. - A loose spouse has their own siblings or parents in the graph (today they attach as a single node next to their partner; if they have a sibling the attachment breaks). - More than ~50 family members are in the tree and edge crossings start to appear regularly. - A second, parallel family tree is added (Raddatz + de Gruyter shown side- by-side). ## Proposed direction Adopt **dagre** (~30 kB, layered DAG layout with edge-crossing minimisation). Wrap it so the input is still our `PersonNodeDTO[] + RelationshipDTO[]` and the output remains the same `{ positions, viewBox }` shape `StammbaumTree` already consumes — keeps the SVG render and connector style untouched. Alternatives evaluated: - `d3-hierarchy` — tree-only, doesn't model multi-parent (DAG) correctly - `family-chart` — opinionated full component; would replace our SVG layer too - `elkjs` — more capable layout but ~10× larger bundle ## Acceptance criteria - [ ] Layout responsibility is moved to dagre behind a small adapter - [ ] Existing visual contract holds: parent-pair drop + sibling bar + per-child vertical, spouse line with midpoint dot, navy/mint via existing `--c-primary` token - [ ] All existing component tests still pass (`StammbaumTree.svelte.test.ts`) - [ ] Add at least one regression test for a multi-marriage family - [ ] Bundle impact documented in the PR description ## Out of scope - Pan / zoom-on-scroll / drag-to-rearrange (separate enhancement) - Persisting layout overrides per user - Full re-skin of the side panel ## Open questions - Do we want dagre's layered layout for everything, or only fall back to it when the heuristic can't handle the shape? - Should we render orthogonal connectors via dagre's edge router, or keep the existing manual SVG render?
marcel added the P3-laterrefactorui labels 2026-04-27 22:45:34 +02:00
Sign in to join this conversation.
No Label P3-later refactor ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#361