Zero-backend, encrypted family tree visualizer stored entirely in the URL.
No accounts. No servers. No tracking. Just share a link + passphrase.
Live Demo · How It Works · Getting Started · Contributing
License React 19 TypeScript Strict Tailwind v4 Vite 7
- Completely client-side — no server, no database, no accounts
- End-to-end encrypted — AES-256-GCM via the Web Crypto API; PBKDF2 with 600k iterations
- URL-as-database — the entire family tree is Brotli-compressed, encrypted, and stored in the URL hash
- Share by link — give someone the URL + passphrase and they can view & edit the tree
- Interactive tree visualization — union-aware layered layout: derives unions/couples from relationships, assigns generations, lays out each connected component with a contour packing, and draws cross-links (cycles, extra parents) as reference edges; pan/zoom, smooth animations
- Relationship types — parent-child, spouse, and sibling relationships with auto-inferred suggestions
- Multilingual — English, Arabic (RTL), and Turkish
- Export/Import — JSON, PNG, and SVG export; JSON import
- Undo/Redo — full history stack (up to 50 snapshots)
- Privacy-first — data never leaves your browser; passphrase kept in memory only
Roots stores the entire family tree in the URL fragment (hash). The data pipeline runs entirely in the browser:
┌──────────┐ ┌────────────────┐ ┌─────────────────┐ ┌────────────┐ ┌──────────┐
│ JSON │────▸│ Brotli │────▸│ AES-256-GCM │────▸│ Base64url │────▸│ URL │
│ (Family │ │ Compress │ │ Encrypt │ │ Encode │ │ Hash │
│ Tree) │ │ (WASM, q=11) │ │ (Web Crypto) │ │ │ │ #... │
└──────────┘ └────────────────┘ └─────────────────┘ └────────────┘ └──────────┘
Loading reverses the pipeline: URL hash → base64url decode → AES-GCM decrypt → Brotli decompress → JSON
Encrypted bytes have maximum entropy and don't compress well. By compressing the raw JSON first (which is highly compressible), we get the best size reduction before encryption makes the data incompressible.
| Parameter | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key derivation | PBKDF2 with 600,000 iterations |
| Salt | 16 bytes (random, prepended to ciphertext) |
| IV | 12 bytes (random, prepended after salt) |
| Wire format | [16B salt][12B IV][ciphertext] |
The tree visualization uses a union-aware layered layout implemented in pure TypeScript (src/lib/layout/):
- Build unions (
unions.ts) — derives unions fromrelationships: co-parents who share ≥1 child form a union; childless spouse pairs form a union with no children; a person may belong to multiple unions (remarriage, co-parenting); single and unmarried co-parents are handled naturally. - Tier assignment (
tiers.ts) — longest-path layering over a spanning DAG; both partners of a union share a tier; cycle edges are excluded from tiering and emitted as reference links instead of breaking the layout. - Connected components (
components.ts) — partitions the layout graph so disconnected branches still render, packed side-by-side with a clear gap. - Per-component layout (
layout.ts) — a Buchheim–Reingold–Tilford contour pass over an alternating person/union tree: union nodes are virtual couple anchors; partners straddle the union centre; children are centred underneath. Components are then packed left-to-right and the whole layout is centred. - Reference links — cycle edges and extra-parent edges (a child's parents beyond its positioning union) are marked
kind: 'reference'so the renderer can draw them distinctly without affecting positions.
Handles: multiple marriages / half-siblings (grouped per union), single parents, unmarried co-parents, both-sided ancestors, disconnected branches, cycles (drawn as reference links), and adoption (>2 parents). Covered by a Vitest fixture suite (24 tests, npm test).
| Layer | Technology |
|---|---|
| Framework | React 19 |
| Language | TypeScript (strict) |
| State | Zustand |
| Layout math | Pure TypeScript (union-aware layered engine) |
| Animation | Framer Motion |
| Styling | Tailwind CSS v4 |
| Theming | Light/dark via CSS custom properties (OKLCH) |
| Build | Vite 7 |
| Validation | Zod |
| Testing | Vitest |
| Compression | Brotli (WASM) |
| Encryption | Web Crypto API |
Roots uses an editorial-paper aesthetic — Spectral (serif) for headings, Hanken Grotesk for body text, and a green-monochrome OKLCH palette with a dark forest-green default. A light/dark theme toggle is available in the header and persists across sessions via localStorage.
- Node.js 18+ (20+ recommended)
- npm 9+
git clone https://github.com/AbdAsh/FamilyTreeVisualizer.git
cd FamilyTreeVisualizer
npm installnpm run dev # Start Vite dev server (http://localhost:5173) npm run build # Production build with TypeScript checking npm run preview # Preview the production build locally npm run typecheck # Run TypeScript compiler without emitting npm test # Run Vitest layout fixture suite
src/
├── app/
│ └── App.tsx # Root component — header, canvas, modals
├── components/
│ ├── editor/ # DetailsModal + MemberForm (in-canvas editing)
│ ├── tree/
│ │ ├── FamilyTreeView.tsx # SVG canvas renderer (pan/zoom/interactions)
│ │ ├── NodeCard.tsx # Expand-to-card node with inline add form
│ │ ├── AddAffordances.tsx # +parent / +child / +spouse / +sibling buttons
│ │ └── EdgeLayer.tsx # Primary + reference link rendering
│ └── ui/ # Reusable UI primitives (Button, Modal, etc.)
├── hooks/
│ ├── useAuth.ts # Passphrase auth (Zustand store)
│ ├── useKeyboardShortcuts.ts
│ ├── useSave.ts # Auto-save (debounced hash write)
│ └── useTree.ts # Tree state + undo/redo (Zustand store)
├── lib/
│ ├── compression.ts # Brotli WASM compress/decompress
│ ├── crypto.ts # AES-256-GCM encrypt/decrypt via Web Crypto
│ ├── i18n.tsx # Translations (en, ar, tr) + context
│ ├── layout/ # Union-aware layout engine + Vitest fixtures
│ │ ├── unions.ts # Derive unions from relationships
│ │ ├── tiers.ts # Longest-path tier assignment + cycle detection
│ │ ├── components.ts # Connected-component partitioning
│ │ └── layout.ts # BRT contour pass + component packing
│ ├── passphrase.ts # Strength scoring + brute-force throttle
│ ├── tree-utils.ts # Relationship queries + layout entry (delegates to lib/layout)
│ ├── url.ts # Save/load pipeline (JSON ↔ compress ↔ encrypt ↔ hash)
│ └── validation.ts # Zod schemas for all data types
├── styles/
│ └── globals.css # Tailwind v4 theme + OKLCH custom properties
└── types/
└── family.ts # Core data types (FamilyMember, Relationship, FamilyTree)
- URL size limit: ~8 KB max hash (
MAX_HASH_BYTES). All tree data must fit after compression + encryption. - No server calls: Everything runs in the browser. Web Crypto API and Brotli WASM are the only non-trivial runtime dependencies.
- Brotli WASM is excluded from Vite's
optimizeDepsto avoid bundling issues. - Relationship semantics:
from= parent,to= child forparent-childtype. Spouse/sibling are symmetric but stored with a canonical direction.
Roots is deployed on Cloudflare Pages. The wrangler.jsonc config points to the dist/ build output. To deploy:
npm run build npx wrangler pages deploy
The public/_redirects file handles SPA routing for Cloudflare Pages (and Netlify). Any static hosting service works — just ensure all paths resolve to index.html.
See CONTRIBUTING.md for development guidelines, code conventions, and how to submit changes.
For reporting security vulnerabilities (especially crypto-related), see SECURITY.md.
This project is licensed under the Apache License 2.0 — see LICENSE for details.
Abdulrahman Mahmutoglu — Senior Frontend Engineer
- Website: abdash.net
- GitHub: @AbdAsh
- LinkedIn: abdash