Skip to content

Develop, test and deploy

Hussein Jarrar edited this page Sep 12, 2026 · 2 revisions

This page covers running Radd locally: the test suite, migrations, and the demo scripts. It also covers how to verify that a UI change renders, and how a change reaches the live instance.

Running the server

The recommended path runs everything — the API and its runtime dependencies — inside a container, with the server source bind-mounted so edits reload immediately:

podman compose -f compose.dev.yaml up      # db + API on :8000

— CLAUDE.md, "Running"

The container brings pg_dump/pg_restore, which the app now requires at startup — not an optional capability. A backup system that silently cannot back anything up is worse than one that refuses to start:

# pg_dump/pg_restore are runtime requirements, not optional capabilities:
# a backup system that silently cannot back up is worse than one that
# refuses to start (RADD_BACKUP_TOOLS_OPTIONAL downgrades this to a warning).
await backup_postgres.preflight()

— server/src/radd/app.py

If you run the server directly on the host, not in the container, your machine needs a working Postgres client for that preflight check to pass. Without one, set RADD_BACKUP_TOOLS_OPTIONAL=true. It downgrades the missing tools from a startup failure to a warning:

podman compose up -d db          # Postgres 16 on localhost:5455 (from repo root)
cd server
uv sync
uv run alembic upgrade head
uv run python -m radd.seed --email hussein@hjarrar.com --password radd-dev-1 --name "Hussein Jarrar"
uv run uvicorn --factory radd.app:create_app --host 0.0.0.0 --port 8000

— CLAUDE.md, "Running"

Either way, the full app — API and the built web UI together — serves at http://localhost:8000, with interactive API docs at /docs. Log in with the credentials you passed to radd.seed.

Frontend

For active frontend work, run the Vite dev server on port 5173 against the API container:

cd web && npm run dev

— CLAUDE.md, "Running"

To refresh the bundle the API serves at :8000 without a dev server running, or to build without npm on the path at all:

cd web && ./node_modules/.bin/tsc -b && ./node_modules/.bin/vite build

— CLAUDE.md, "Frontend conventions"

Tests

uv run pytest

— CLAUDE.md, "Running"

The suite is self-contained: it re-creates and migrates its own throwaway radd_test database on a session-scoped fixture and never touches your dev database.

"""conftest repoints the app at a throwaway `radd_test` database (same Postgres
server; override with RADD_TEST_DATABASE_URL), and a session-scoped fixture
re-creates it and migrates it once per run."""

— server/tests/conftest.py

As of 2026-08 the suite runs roughly 1,600 tests. CLAUDE.md rule 4 sets the bar for what earns a test. A worthwhile test covers a core invariant many modules depend on — custom field validation, event emission, permission checks. It is not one unit test per endpoint. server/tests/test_route_shadowing.py is a good example of a test that earns its place. It asserts one invariant — no literal route sits behind an earlier parameterized one — over the whole assembled app, not one endpoint.

Migrations

After a model change:

uv run alembic revision --autogenerate -m "..."   # review the generated file
uv run alembic upgrade head

— CLAUDE.md, "Running"

Review the autogenerated file before you apply it. Autogenerate is a starting point, not a guarantee. If two people, or two agent sessions, create migrations in parallel, Alembic ends up with sibling heads. Resolve that with alembic merge, rather than picking one arbitrarily and discarding the other's history.

Demo scripts

server/scripts/demo*.sh (demo.sh, plus demo_webhooks.sh, demo_permissions.sh, demo_views.sh, demo_planning.sh, demo_automations.sh, demo_reporting.sh, demo_forms.sh, demo_timelogging.sh) are feature walkthroughs, run over curl against a running server:

#!/usr/bin/env bash
# End-to-end tour of the current slices: registry-driven custom fields,
# workflow states as data, labels, epic/issue/subtask hierarchy, assignment,
# comments, action RBAC + field-level visibility, event stream.
# Rerunnable (fresh workspace each run). Signs in as the seeded admin —
# override with DEMO_EMAIL/DEMO_PASSWORD.

— server/scripts/demo.sh

They assume a fresh database. Each script creates fixed project keys, for example TD, and project keys are unique instance-wide. Running one against a database that already has real data risks a key collision. Running one repeatedly against the same database accumulates junk projects. Point RADD_DATABASE_URL at a throwaway database before you run a demo script. Use pytest instead for regression coverage against a populated database.

Verifying a UI change

Building is not verifying. A clean tsc and a clean vite build prove that the code compiles, not that it renders correctly. Several real bugs shipped past a green build because nobody looked at the output:

  • A flex container squashed 92 cards into 12px strips.
  • A collapse toggled aria-expanded while it stayed the same width.
  • Board columns clipped 75 cards down to five.

The house pattern for looking is web/scripts/render-proof.mjs and its siblings under web/scripts/ — dozens, and growing. Each drives headless Chromium directly over the DevTools Protocol, with zero npm dependencies:

/**
 * Headless render proof for the frontend module-federation platform (spec 94, LOCKED-3).
 * ZERO npm deps: drives Chrome via the DevTools Protocol using Node 22's built-in WebSocket + fetch.
 *
 * Proves the whole chain in a REAL browser:
 *   1. host boots, reads /capabilities, imports the participants REMOTE bundle at runtime;
 *   2. the remote's activate() registers into the host's shared slot registry (one singleton);
 *   3. the issue view's <Slot> RENDERS the remote's Participants section into the DOM;
 *   ...
 */

— web/scripts/render-proof.mjs

A proof takes a screenshot and probes the real DOM (getBoundingClientRect/getComputedStyle/scrollHeight), rather than trusting that matching CSS classes mean matching layout. Know these sharp edges before you write one, all from CLAUDE.md's "Verifying UI work":

  • Headless Chrome reports (hover: none) at baseline. Tailwind v4 gates every hover: utility on @media (hover: hover). A proof that does not force hover-capable media flags shows hover-revealed chrome as permanently absent — which reads exactly like a missing feature.
  • A leaked browser from an earlier run is worse than a stale bundle. Its assertions describe code that this run never configured. The shared openBrowser helper refuses to start against a debug port something is already listening on.
  • Screenshots at reduced scale lie about small UI. Check computed styles too, not only pixels, for anything under about 16px.

Release and deploy

Two repositories, two pipelines, and they are not interchangeable. This repo builds a container image; a separate private deployment repository decides which image runs where.

git tag -a v0.2.0 -m "Radd 0.2.0" && git push origin v0.2.0

— docs/contributing.md

The pushed tag makes CI build and publish git.radd-hq.com/radd/radd:0.2.0. Nothing runs in production yet. That requires bumping the image tag in the deployment repository, which triggers CD:

helm upgrade radd deploy/helm/radd -n radd -f my-values.yaml \
  --set image.tag=0.2.0

— docs/deploy-k3s.md

There is no latest tag. Every deployment pins an immutable version number. A rollback edits that value back to the previous tag — never a race over where a moving tag happens to point right now.

Never change the cluster by hand. CD reverts a kubectl edit against a running deployment the next time it runs. The deployment repository, not the live cluster state, is the source of truth for what should be running.

Migrations run as a pre-install/pre-upgrade Helm hook Job, ahead of the new pods:

kubectl -n radd get jobs   # if a release seems to hang, check here first

— docs/deploy-k3s.md

helm rollback reverses the release, but not the migration it ran. For anything schema-breaking, restore from a backup or a snapshot. Do not roll back and hope the old code still matches the new schema.

main is protected: direct pushes and merges are restricted to the owner. CI does not run on pull requests. Forgejo executes the target branch's workflows for a fork PR, so a workflow file added inside the PR itself never runs. Run the test suite locally before you open one.


Mirrored from project.radd-hq.com on 2026-09-12. Documentation is written there; this copy is regenerated by scripts/publish_wiki.py and hand edits do not survive it.

Clone this wiki locally