Files
joulenap/CONTRIBUTING.md
Catubba 5ed03db20c feat(demo): route-aware demo replay and 1.0 documentation
Rewrite demoTimeline.ts around the three-route scenario and rebuild the
scripted replay inside devStub.ts on top of the 1.0 endpoints, then bring
the shipped documentation in line with what actually ships.

Demo:
- demoTimeline.ts keys its online windows by PBS id, so one field covers a
  single-box backup and a sync route's two; steps carry the per-device
  names the backend really emits, plus their detail.
- The demo auto-plays: it opens mid-backup on Nightly and the queued Lab
  route starts by itself when that lands, with the target left awake
  between them and the skipped power-off recording why. The queue and the
  power lease are visible without a click.
- The clock ticks and the fixture calendar shifts by whole weeks, so
  weekdays and times survive and the schedules stay self-consistent. Dev
  stub mode keeps its frozen clock; every replay mutation sits behind the
  demo flag.
- Restore the "fake data" banner and make logout reload rather than strand
  the visitor on a login form.
- build:demo now type-checks first, which it never did.

Fix the expanded run history row refetching its detail only once, so a run
in flight showed a frozen step timeline while its task log kept streaming.

Docs:
- ARCHITECTURE: the route model, the queue and lease, a cycle per kind, the
  migration, and REST tables rebuilt from the shipped routers.
- CONFIG-WIZARD: the two device flows, and the /remote grant a sync route
  needs on a peer configured before 1.0.
- INTEGRATIONS: the new dashboard payload, snippets matching the ones the
  app generates, the labelled metric names, and a 0.9 mapping table.
- README, INSTALL: routes, the five settings tabs, upgrading from 0.9, and
  the Node version CI and the image actually build with.
- SECURITY: transport pinning, auth hardening, the two API-key endpoints
  outside the session, and what Joulenap deliberately does not do.
- CONTRIBUTING: npm test is a separate CI step, and the demo section now
  describes the demo that exists.
- CHANGELOG: the 1.0.0 entry, including the breaking dashboard and metrics
  shapes and the exclude guest mode widening to all.
2026-08-04 14:09:19 +02:00

6.2 KiB

Contributing to Joulenap

Thanks for your interest in improving Joulenap! This guide covers the local dev setup, how to run the checks CI runs, and the conventions we follow.

By contributing you agree that your contributions are licensed under the project's AGPL-3.0.

Project layout

  • backend/ — Python 3.12 + FastAPI + APScheduler. The app package is backend/app.
  • frontend/ — React + TypeScript SPA (Vite). Built output is served by the backend.
  • docs/ — design and setup docs. Start with ARCHITECTURE.md (the route model, the queue and lease, the REST API) and CONFIG-WIZARD.md (the two device flows). INSTALL.md covers deployment and INTEGRATIONS.md the dashboard and Prometheus endpoints.

Dev setup

You need Python 3.12+ and Node 24.

Node 24 is not a suggestion: npm test runs node --test directly over .ts files using Node's type-stripping, so it does not run at all on Node 20. CI and the Docker image are both pinned to 24, and a CI job fails the build if those two ever drift apart.

Backend

cd backend
python -m venv .venv
# Windows: .venv\Scripts\activate   |   macOS/Linux: source .venv/bin/activate
pip install -e ".[dev]"

ruff check .    # lint
pytest          # tests

Frontend

cd frontend
npm ci
npm run dev      # Vite dev server (proxies /api to the backend on :8080)
npm run build    # type-check (tsc --noEmit) + production build
npm test         # node --test over the pure-logic modules

The test harness has no DOM, so anything worth asserting lives in a plain module under src/utils/ (route form rules, topology geometry, wizard flow) rather than inside a component. Logic put in a component is untestable here by construction.

For a running app, copy the config first and start the backend:

cp config.example.yaml config.yaml
cd backend && python -m app.main    # serves the API (and the built SPA if you ran `npm run build`)

The example config ships unconfigured, so the app drops you into the first-run registration and then a banner pointing at Settings → Devices, where the two wizards add a Proxmox host and a backup server. Routes are drawn from the homepage once at least one of each exists. No real Proxmox is needed to click around the UI.

Frontend without a backend

To work on the UI alone — layout, styling, i18n — you can run the SPA against a built-in stub instead of a real backend and a real Proxmox:

cd frontend
npm run dev -- --mode stub

frontend/src/devStub.ts answers every /api/* request from fixtures — three Proxmox hosts (one a two-node cluster), two backup servers, three routes covering backup, a fan-in and a PBS→PBS sync, run history including a failure and an aborted run, and enough wizard responses to click both device flows end to end. It also pins the clock, so the UI renders identically on every run — which is what makes it useful for screenshots and layout comparisons. Route and device writes really mutate the fixtures (with the real 409s), so a route you create shows up in the strip and the topology.

It is loaded only when VITE_STUB_API=1, which frontend/.env.stub sets for the stub mode, and Vite eliminates it from production builds.

Add --host 0.0.0.0 to reach the dev server from a phone on the same network.

The public demo

npm run build:demo bundles that same stub into frontend/dist-demo/, which is what the demo at joulenap.com/demo serves. On top of the stub it adds a replay, driven by the scripted arcs in frontend/src/demoTimeline.ts:

  • a moving clock, and the whole fixture calendar shifted forward by a whole number of weeks — whole weeks so every weekday and time-of-day survives untouched, which is what lets the schedules, the upcoming-runs list and the history stay consistent with no scheduler in the stub;
  • an orange banner saying the data is fake;
  • the run auto-plays on load: you land on the Nightly route already mid-backup with its task log streaming, it finishes, and the queued Lab route then starts by itself while the backup server stays awake between the two — the run queue and the power lease, without a click. Run now, Stop and the per-server GC/verify buttons all drive the same machinery.

Everything in the replay sits behind VITE_DEMO, so --mode stub stays frozen and deterministic — if you touch devStub.ts, keep it that way or dev screenshots stop being reproducible.

--mode demo is the only build that ships the stub; npm run build — what CI and the Docker image run — never does. To check a change to it, build and serve the output rather than trusting the build alone:

npm run build:demo
npx vite preview --outDir dist-demo

Before you open a PR

Run what CI runs (see .github/workflows/ci.yml); all of it must pass:

  • Backend: ruff check . and pytest (CI runs the suite on Python 3.12 and 3.13).
  • Frontend: npm run build (this type-checks with tsc --noEmit) and npm test. Both are separate CI steps — a green build is not a green suite.

CI also runs dependency (pip-audit / npm audit) and Docker image (Trivy) security scans.

Conventions

  • Small, reviewable commits; work on a feature branch and open a PR against main.
  • Tests for the connectors and backup-cycle logic — that's where correctness matters most.
  • Keep it config-driven — nothing hard-coded (no specific IPs/MACs in code); validate config with pydantic and fail clearly.
  • Secrets never get committed. config.yaml is git-ignored; don't add real hosts/tokens/MACs to code, tests, or docs — use placeholder/192.0.2.x (TEST-NET) values.
  • i18n: user-facing UI strings go through t('key') with entries in both frontend/src/i18n/en.json and frontend/src/i18n/it.json (English is the source language). Backend-facing strings (errors, notifications) use the server-side dictionary. Never concatenate translated strings — use interpolation.

Reporting bugs and requesting features

Use the issue templates (bug report / feature request). For security vulnerabilities, do not open a public issue — follow SECURITY.md instead.