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
3.5 KiB
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— refreshtests/fixtures/*.jsonfrom a live backend.
Running it
THERMOGRAPH_API_BASE_INTERNAL is required — api_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 viaapi_client.py; the backend'scontent_payloads.pyownspage_title/canonical_path/breadcrumb/jsonld, not this domain.static/*.js—app.js(map/search/results + inline SVG chart),calendar.js/day.js/score.js/compare.js(SPA shells),account.js(auth),cache.js(IndexedDB cache +/cellbundle prefetch),shared.js.static/style.css— the single hand-written stylesheet; design tokens live here, documented inDESIGN.md.templates/*.html.j2,content/*.yaml(structured SSR copy, loaded bycontent_loader.py).
Contracts with the backend
- API version is pinned in exactly two places —
api_client.py'sAPI_VERSION(Python) andstatic/account.js's exportedAPI_VERSIONplus theuv(path)helper (JS). Never hardcodeapi/v2/...anywhere else. Bump both in one PR, and only after the target backend's/api/versionconfirms it serves that version and itsmin_frontenddoesn't exclude the one you're leaving. shared.js::pctOrd()must mirrorbackend'sdata/grading.py::pct_ordinal()in behaviour — floor into1..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./cellbundle + ETag —cache.jsfetches/api/v2/cellonce per view-set and slices it, revalidating withIf-None-Match. The slice→view map must track the backend's payload shape.- Fahrenheit country set —
format.py::F_COUNTRIESandstatic/units.js'sF_REGIONSmust stay identical to the backend'sF_COUNTRIES. - Boot must survive an unreachable backend.
content.py'sregister()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.