thermograph/frontend/CLAUDE.md
emi e84b1f7937
Some checks failed
shell-lint / shellcheck (push) Has been cancelled
secrets-guard / encrypted (push) Has been cancelled
Validate observability stack / validate (push) Successful in 33s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 3m5s
Deploy frontend to LAN dev server / build (push) Successful in 3m21s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 3m27s
Deploy backend to LAN dev server / build (push) Successful in 4m16s
Deploy frontend to LAN dev server / deploy (push) Successful in 31s
Deploy backend to LAN dev server / deploy (push) Successful in 1m19s
docs: rewrite the agent context layer to match the live system (#81)
2026-07-25 07:08:54 +00:00

3.5 KiB

frontend/ — agent instructions

The SSR + static-asset service: server-rendered crawlable pages, the interactive tool's SPA shells, and every static asset. No climate data, no DB, no compute — everything comes from backend/'s content API over HTTP.

Read the root CLAUDE.md first for the branch model, deploy contract and image names.

Build & verify

  • make test — whole suite (scripts/test.sh).
  • make test-unit — hermetic unit tier: SSR rendering fed committed fixtures, no Docker. This is the tier CI runs.
  • make test-integration — pulls and runs the real backend image and tests the live contract. Local only; CI does not run it.
  • make backend-up / make backend-down — a local backend container for dev.
  • make capture-fixtures — refresh tests/fixtures/*.json from a live backend.

Running it

THERMOGRAPH_API_BASE_INTERNAL is requiredapi_client.py raises at import if unset.

THERMOGRAPH_API_BASE_INTERNAL=http://127.0.0.1:8137 THERMOGRAPH_BASE=/thermograph \
  .venv/bin/uvicorn app:app --host 0.0.0.0 --port 8080

Other env: THERMOGRAPH_BASE (default /thermograph; the Dockerfile sets /), THERMOGRAPH_API_VERSION (default v2), THERMOGRAPH_API_BASE_PUBLIC (browser-facing backend origin when cross-origin; empty = same-origin, today's default), THERMOGRAPH_SSR_CACHE_TTL (default 600s).

Layout

  • content.py — SSR routes (climate hub, city, month, records, glossary, about, privacy) + robots.txt + sitemap.xml. Each fetches from the backend via api_client.py; the backend's content_payloads.py owns page_title / canonical_path / breadcrumb / jsonld, not this domain.
  • static/*.jsapp.js (map/search/results + inline SVG chart), calendar.js/day.js/score.js/compare.js (SPA shells), account.js (auth), cache.js (IndexedDB cache + /cell bundle prefetch), shared.js.
  • static/style.css — the single hand-written stylesheet; design tokens live here, documented in DESIGN.md.
  • templates/*.html.j2, content/*.yaml (structured SSR copy, loaded by content_loader.py).

Contracts with the backend

  • API version is pinned in exactly two placesapi_client.py's API_VERSION (Python) and static/account.js's exported API_VERSION plus the uv(path) helper (JS). Never hardcode api/v2/... anywhere else. Bump both in one PR, and only after the target backend's /api/version confirms it serves that version and its min_frontend doesn't exclude the one you're leaving.
  • shared.js::pctOrd() must mirror backend's data/grading.py::pct_ordinal() in behaviour — floor into 1..99, never 0 or 100. Every percentile on every surface goes through one of the two; if they diverge, the same reading says two different things in two places.
  • /cell bundle + ETagcache.js fetches /api/v2/cell once per view-set and slices it, revalidating with If-None-Match. The slice→view map must track the backend's payload shape.
  • Fahrenheit country setformat.py::F_COUNTRIES and static/units.js's F_REGIONS must stay identical to the backend's F_COUNTRIES.
  • Boot must survive an unreachable backend. content.py's register() tries the IndexNow-key fetch once, catches failure, and falls back to a lazy per-request lookup. Asynchronous FE/BE deploys depend on this — don't make it fatal.

Commits & PRs

Concise and technical. Never mention AI, assistants or automated authorship.