Most family-tree software assumes a family shape that doesn't exist. It wants a father and a mother, one marriage each, no adoptions, and dates you actually know. Real families — certainly mine, tracing back through Nagpur and Amravati into the 1800s — are messier than that. Someone's second marriage produced half of the cousins. Someone's mother is simply unrecorded. Someone was raised by an uncle and everyone politely calls him a brother.
So I built our own.
Modelling a family without lying about it
The interesting decisions were all in the schema. Three that mattered:
Marriages are a first-class table, descent is a separate edge table. The
obvious design — father_id and mother_id columns on a person — collapses the
moment you have an adoption plus a step-parent, because a child can legitimately
have four parents. The next-most-obvious design, a generic
relationships(from, to, type) table, gives a marriage no identity to hang a
date or a divorce on. So marriages get their own row with their own attributes,
and parent→child is a separate edge with its own relation_type.
A single parent is not an error state. The GEDCOM-derived approach hangs children off marriages only, which means every unknown ancestor spouse needs a phantom person invented to have something to attach to. For a tree reaching back two centuries, "spouse unknown" is the common case. Descent edges stand alone.
Nothing is stored as coordinates. Node positions are a pure function of the graph, computed identically on every device. This deletes an entire category of bug where two relatives drag the same node somewhere different.
There's also a small, slightly grim detail I'm fond of: every date has a
precision alongside it — day, year, decade, approx — plus a free-text
note. So a birthday can be recorded as "the monsoon of '47" without pretending
to a precision nobody has.
Keeping it genuinely private
The whole thing lives inside this portfolio, which is otherwise a fully static site. That's a slightly unusual constraint: the marketing pages must stay prerendered and CDN-cached while one route becomes a stateful, authenticated app.
Access is invite-only, and the check runs in the database at account-creation time rather than in application code — so an uninvited person never gets an account at all, rather than getting one that's later filtered out. Row Level Security is the real authorization boundary; the middleware and the server-side gate above it exist for the user experience, not for security. Column-level grants handle the thing RLS can't: a relative can edit their own profile without being able to promote themselves to admin.
Still to come
Photographs with face tagging. A hereditary-health view — the genuinely useful reason a family app might hold medical information, kept in separate tables with consent recorded per person and access logged, because that data deserves more care than a birth year does. And eventually a wedding-invitation tool, which is why marriages are first-class: a guest list attaches to a marriage, not to a person.
The app itself is private. The engineering is what's on display here.