Skip to content

Development

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

Development

This page covers setting up a local development environment for app/ itself — as opposed to Installing (running the packaged desktop build) or Deploying (running the containerized production shape). See Architecture first for how the pieces fit together.

Getting started

npm install                 # installs the workspace (app/ + packages/gramps-date)
cp app/.env.example app/.env.local   # points at a running gramps-web-api instance
npm run dev -w app          # starts the Vite dev server

app/ needs a real gramps-web-api backend to talk to — it's a pure client, there's no mock-data mode. The lightest option is dev-fixtures/layer2-local-cache/api-fixture-example/setup.sh (see Dev fixtures below).

Every fixture runs gramps-web-api from a source checkout in the same Python environment as the fixture script, so that environment needs:

pip install -e ~/gramps/gramps --no-deps          # gramps itself
pip install -e ~/gramps/gramps-web-api --no-deps  # + its deps, see below
python3 deploy/webapi-requirements.py ~/gramps/gramps-web-api/pyproject.toml \
  | pip install -r /dev/stdin

gramps-web-api's const.py does gi.require_version("Gtk", "3.0") at import time, so real PyGObject and the GTK3 typelibs have to be present (apt install python3-gi gir1.2-gtk-3.0, or conda install -c conda-forge pygobject gtk3); PyICU is optional but silences a localization warning and fixes name sorting.

Note that the /api/<type>/query/ endpoints app/ is built on landed in gramps-project/gramps-web-api master — an older fork or branch 404s on every one of them.

Dev fixtures

dev-fixtures/ holds real gramps-web-api backends for running app/ against locally. They're not part of the shipped product — just what makes local development possible without hand-configuring a server of your own. Read a script before running it — none of them are idempotent against an already-populated tree. Every fixture logs in as gramps/gramps.

  • layer2-local-cache/api-fixture-example/ — the lightest option: a plain-SQLite instance on :5002 loaded with Gramps' own official example.gramps sample database, useful for real date variety (modifiers, quality, ranges/spans). Set VITE_API_BASE=http://localhost:5002 in app/.env.local to point at it. Live sync works against it too, since it's just a poll against /api/transactions/history/, not tied to Postgres.
  • layer2-local-cache/api-fixture/ — another plain-SQLite instance, loaded with gramps-bench-generated synthetic data instead, for scale testing against a large tree.
  • layer3-sync/api-fixture/ — a real Postgres (SharedPostgreSQL) -backed instance, useful for exercising genuinely concurrent multi-writer edits against the same tree. This is what app/.env.example's default VITE_API_BASE points at, and it needs a running Postgres plus the SharedPostgreSQL addon in addition to everything the SQLite fixtures need.

Building Gramplet wheels

Gramplets run Python in the browser under Pyodide, against locally-built wheels that npm install's postinstall step skips (with a log line) when they aren't there yet — leaving Gramplets failing at run time with No known package with name 'gramps-gen-lib'. Build them once:

python3 scripts/build-stub-wheels.py    # gi + orjson stand-ins for Pyodide
python3 scripts/build-gramps-wheel.py   # gramps.gen.lib as a Pyodide wheel
node app/scripts/copy-wasm.mjs          # registers them in public/pyodide/

Testing

npm run test -w app         # Vitest: pure store/sync logic, not full-app rendering
npm run typecheck -w app    # tsc --noEmit
npm run test -w packages/gramps-date
pytest tests/               # GOQL built-in filter presets vs. real gramps-core rules

The pytest suite (tests/gql_presets/, see its own README) checks every built-in Filters preset (app/src/data/gqlFilterPresets.ts) against the real gramps-core Rule class it's meant to port, run against gramps-core's own bundled example tree — catching not just "does this compile" but "does it select the same rows the desktop rule would." Needs gramps/gramps-object-query-language importable (already true anywhere gramps-web-api itself runs); no separate step needed to keep its own JSON snapshot of the presets in sync, that happens automatically.

Translations

app/'s UI strings go through t() (app/src/i18n/i18n.ts), which mirrors gramps-web's own approach — a plain {english: translated} lookup, no i18n library — merged from two sources per language, chosen at setLanguage() time and cached in-memory until the next language switch:

  • The Gramps desktop vocabulary — translated live, per request, by POSTing the strings actually in use to gramps-web-api's existing GET/POST /api/translations/<lang> endpoint, which runs them through the installed gramps package's own gettext catalog. No static copy to keep in sync; always as fresh as whatever gramps version the server has installed. The fixed list of which desktop-vocabulary strings to request lives in i18n.ts's desktopStrings array — grown by hand, one entry per string, whenever a newly-wrapped t() call turns out to be real Gramps vocabulary rather than something gramps-connect-specific.
  • gramps-web's own UI strings, and Gramps addons' strings — bootstrapped as static app/public/lang/{locale}.json files (tracked in git, so the app works without anyone needing a Weblate connection) by scripts/bootstrap-translations.py, which reads ../gramps-web/lang/ and ../addons-source/*/po/*-local.po — sibling checkouts of this repo, not a network call. Run it (python3 scripts/bootstrap-translations.py, needs pip install polib) whenever those sibling checkouts get updated and you want the static corpus refreshed; it's not wired into any build step, so nothing runs it automatically. Safe to re-run: without --force it skips any locale .json that already exists, and even with --force it only ever overwrites files under app/public/lang/ — no network calls, no git operations, and any bad result is a git checkout away from undone.

Wrapping more of the app's own strings in t() is ongoing, incremental work — app/scripts/wrap-translations.mjs is a one-time codemod (kept around as a reusable tool) that mechanically wraps plain JSX text and a safe attribute allowlist (label/title/placeholder); anything sourced from a variable or object-literal property (view/column configs in app/src/store/views.ts, dynamic API data, notifications.show() calls) needs a manual t(...) at whatever component renders it instead.

Contributing

Discussion happens on the Gramps Discourse forum; issues and pull requests against the gramps-connect repo are welcome.

License

AGPL-3.0-or-later, matching gramps-web-api and gramps-web. packages/gramps-date translates GPL-2.0-or-later Gramps core code into this project's AGPL-3.0-or-later codebase — see its own README (and Architecture) for how those two licenses combine.

Clone this wiki locally