Skip to content

Architecture

Doug Blank edited this page Sep 20, 2026 · 2 revisions

🌐 Also available in: Deutsch

Architecture

Gramps Connect is a React frontend (app/) talking to a gramps-web-api backend over its REST API — it doesn't fork or replace gramps-web-api, it's a different client for the same server. Two mechanisms make it possible for that frontend to feel fast and collaborative at the same time: a local-first cache, and a lightweight live-sync poll layered on top of an endpoint gramps-web-api already ships.

Local-first cache

A WASM build of SQLite runs inside the browser, mirroring server data into local tables and persisting them to OPFS (the browser's own private filesystem) so a repeat visit can skip the network entirely. Data is fetched via gramps-web-api's fast, SQL-pushed-down /api/<type>/query/ endpoints — the same endpoints GOQL conditions and Gramplet where= clauses compile down to — rather than paging through full per-object REST responses.

The payoff is what the Overview describes: once you've looked at part of your tree, browsing, sorting, and searching that part again feel instant, even on trees with tens of thousands of people, where this kind of searching can otherwise take well over a minute against a plain unindexed REST backend.

This is also why the fixed admin/admin desktop build and a real server deployment can share the exact same frontend code with no special casing: the cache only cares that it's talking to a gramps-web-api-shaped /api/, not who's hosting it.

A gotcha worth knowing: the browser-side cache is keyed by a fixed filename per view, not by backend URL or tree ID. Switching which backend you're pointed at, or re-importing/recreating a tree against the same backend, can leave a browser profile serving stale cached rows with no automatic invalidation — the only check performed is schema compatibility, not data identity. If data ever looks stale after a backend swap, clear it manually: DevTools → Application → Storage → clear site data (or OPFS specifically).

Live sync

The client polls gramps-web-api's existing GET /api/transactions/history/ endpoint (the object-edit audit/undo log it already ships, not something added for this) on a short interval. For each object that endpoint reports as changed, Gramps Connect refetches and patches just that one row in the local cache.

This is deliberately simple: no persistent WebSocket-style connection and no Postgres-specific change-data-capture is required, just a plain authenticated GET on a timer — so it works against any gramps-web-api backend, not only a Postgres-backed one. It's how "someone else corrects a date and your screen updates on its own" (see Overview) actually works, and it's also how the dev fixtures can exercise sync without needing a real Postgres instance — even the plain-SQLite ones support it, since it's just a poll.

What live sync does not do yet: there's no presence layer (who's currently viewing or editing what) and no user-facing history browser — see Roadmap and Known Limitations.

Repo layout

  • app/ — the production React client: all ten object-type views (person, family, event, place, repository, source, citation, media, note, tag — see Data Model and Editing), where_expr/GOQL filtering, an OPFS-persisted WASM SQLite cache, and live sync, behind a useSyncExternalStore-based store layer (app/src/store/) with @tanstack/react-virtual for scrolling.
  • dev-fixtures/ — real gramps-web-api backends to run app/ against locally; not part of the shipped product, just what makes local development possible without hand-configuring a server. See Development for the three flavors (layer2-local-cache/api-fixture, api-fixture-example, and layer3-sync/api-fixture) and what each is for.
  • packages/gramps-date/ — a TypeScript port of Gramps' Date model, calendar conversion, and locale-aware date display, used by app/ so it can render or build a Gramps Date struct without a slow per-object round trip through Gramps' own Python date displayer for every row in a table. It handles calendar conversion for five calendars (Gregorian, Julian, French Republican, Islamic, Swedish — Hebrew and Persian display correctly but can't yet be validated on entry, since their SDN conversion needs more machinery), structured date entry and validation, and locale-pluggable display (registerLocale(); only English ships today). It's a translation of Gramps core's own gramps/gen/lib/date.py/gcalendar.py/_datedisplay.py from Python to TypeScript, cross-checked against the real Python implementation in its own test suite — see its own README for the full provenance and license story (it's GPL-2.0-or-later code folded into this AGPL-3.0-or-later project, the same way gramps-web's own gcalendar.js port already does).
  • standalone/ — the PyInstaller-based build of gramps-connect-desktop: a Python launcher (launcher.py) that bundles app/'s built frontend with gramps-web-api and SQLite into a single native-window (or browser-fallback) app. See Installing.
  • deploy/ — the containerized multi-user deployment (app/ + gramps-web-api + Postgres + Caddy + Redis/Celery). See Deploying.
  • gramplet_examples/ and gramplet-store/ — example Gramplets and the source content for the in-app Gramplet Store catalog. See Gramplets.

A root-level npm workspace (packages/*, app) ties app/ and packages/gramps-date together as real workspace dependencies. The fast /query/ endpoints app/ depends on live in gramps-web-api itself (a separate repo, extended in place, backward compatible) via gramps-object-query-language, GOQL's own implementation — not in this repo.

Add-ons run Python in the browser

Gramplets — the app's add-ons — run under Pyodide (CPython compiled to WebAssembly) directly in the browser tab, against locally-built wheels of gramps.gen.lib (Gramps' own data model, so a Gramplet's Python code sees real Person/Family/... objects, not a reimplementation). No server-side execution and nothing installed on your machine — see Development for how those wheels get built, and Gramplets for how the sandboxed API (people(), filter(), db, row(), html(), ...) is put together.

Clone this wiki locally