|
Some checks failed
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
secrets-guard / encrypted (pull_request) Successful in 14s
PR build (required check) / changes (pull_request) Successful in 16s
PR build (required check) / validate-observability (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 28s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m58s
Deploy frontend to LAN dev server / build (push) Successful in 1m59s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 2m5s
Deploy backend to LAN dev server / build (push) Successful in 2m20s
PR build (required check) / build-frontend (pull_request) Successful in 1m44s
Deploy frontend to LAN dev server / deploy (push) Successful in 30s
PR build (required check) / build-backend (pull_request) Successful in 2m0s
PR build (required check) / gate (pull_request) Successful in 2s
Deploy backend to LAN dev server / deploy (push) Failing after 1m58s
|
||
|---|---|---|
| .. | ||
| content | ||
| scripts | ||
| static | ||
| templates | ||
| tests | ||
| tools | ||
| .gitignore | ||
| api_client.py | ||
| app.py | ||
| CLAUDE.md | ||
| content.py | ||
| content_loader.py | ||
| DESIGN.md | ||
| docker-compose.test.yml | ||
| Dockerfile | ||
| format.py | ||
| Makefile | ||
| paths.py | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
Thermograph frontend
The server-rendered content pages, the interactive-tool SPA shells, and every
static asset for Thermograph — grades recent local
weather against ~45 years of climate history. This repo holds no climate
data and does no compute; it renders pages and serves the JS/CSS/image
assets that call the backend's API from the browser. It was split out of the
emi/thermograph monorepo (frontend_ssr/ + frontend/ there) so it can
build its own image and deploy independently of the backend. See
CLAUDE.md for the full agent-facing detail on the split
topology and deploy contract, and
thermograph-docs (sibling repo) for the
architecture decision record behind the split.
Layout
api_client.py HTTP client for the backend's content API (api/content_routes.py
on the backend). Fails loud at import if
THERMOGRAPH_API_BASE_INTERNAL is unset. TTL-cached (10 min
default) in front of every call.
app.py FastAPI app: /healthz, the interactive tool's SPA-shell
routes (calendar/day/score/compare/legend/alerts), and the
StaticFiles mount for everything else. Calls
content.register(app) for the SSR routes below.
content.py Server-rendered pages: climate hub, per-city, month,
records, glossary, about, privacy, robots.txt, sitemap.xml.
content_loader.py Loads content/glossary.yaml + content/pages.yaml (SSR copy),
validated fail-loud at load time.
format.py Unit-aware (°C/°F) formatting for the SSR templates,
ContextVar-scoped active unit.
paths.py Canonical filesystem locations (STATIC_DIR, TEMPLATES_DIR,
CONTENT_DIR), resolved from the repo root.
templates/ Jinja templates content.py renders (base, home, city, month,
hub, records, glossary, glossary_term, about, privacy).
static/ Every static asset: the interactive tool's JS
(app.js, calendar.js, day.js, score.js, compare.js,
account.js, cache.js, shared.js, units.js, mappicker.js, …),
the SPA-shell HTML pages, style.css (all design tokens —
see DESIGN.md), icons/favicons, manifest.webmanifest, sw.js.
content/ Structured SSR copy: glossary.yaml, pages.yaml.
tests/ pytest suite — see the KNOWN GAP note below before relying on it.
tools/ shoot.py — the make-shots visual-verify screenshot tool
(see DESIGN.md).
Dockerfile Builds this repo's own image (python:3.12-slim,
uvicorn app:app), independent of the backend's.
How it fits the split
Four sibling repos replace the old monorepo:
thermograph-backend— the FastAPI API, grading/scoring/grid compute, and DB. This repo's only runtime dependency.thermograph-frontend(this repo).thermograph-infra— Terraform, compose files,deploy/deploy.sh(the script both repos' deploy workflows SSH into), the secrets vault.thermograph-docs— architecture/decision docs and runbooks.
Each app repo now builds and publishes its own image
(git.thermograph.org/emi/thermograph-frontend/app, tagged by git SHA and
semver) instead of the old shared emi/thermograph/app image, and deploys via
its own .forgejo/workflows/deploy.yml (main → beta) and deploy-prod.yml
(release → prod), each rolling only the frontend compose service on the
target VPS.
Talking to the backend
All data comes from the backend's content API over HTTP
(api_client.py) — this process holds nothing in-process. Two env vars
control the topology:
THERMOGRAPH_API_BASE_INTERNAL(required) — where this process reaches the backend server-side, e.g.http://backend:8137in compose, orhttp://127.0.0.1:8137for a local backend checkout.api_client.pyraises at import if this is unset — a missing backend URL should break boot, not silently 500 the first request.THERMOGRAPH_API_BASE_PUBLIC(optional) — the backend's browser-facing origin, used only when frontend and backend are served cross-origin (e.g. a genuinely separate LAN-dev topology). Left empty (the default), asset/API references stay relative and same-origin, which is today's real deployed topology.
Same-origin cookie-auth caveat: the interactive tool's auth
(static/account.js) is an HttpOnly session cookie. It uses
credentials: "include" (not the fetch default) specifically so the cookie
still rides along if this script is ever loaded cross-origin, but the backend
must actually be configured to accept credentialed cross-origin requests
(CORS + SameSite) for that case to work — in the default same-origin
deployment this is a non-issue. Don't assume a fully decoupled, independently-
hosted frontend "just works" for logged-in features without checking that
cookie/CORS configuration first.
THERMOGRAPH_BASE (default /thermograph) must be set identically on both
frontend and backend in every real deployment — it's how both processes agree
on the shared URL prefix (or the deployed clean-root /, which the Dockerfile
sets by default).
Build & run
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
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
Or build the image directly:
docker build -t thermograph-frontend .
docker run -p 8080:8080 -e THERMOGRAPH_API_BASE_INTERNAL=http://backend:8137 thermograph-frontend
The image healthchecks GET /healthz (liveness only — it does no I/O, so it
stays cheap; it does not prove the backend is reachable).
More detail
CLAUDE.md— agent-facing instructions: deploy contract, API-version pinning, the cross-repo/cell/ETag andpctOrd()contracts, and the current known test-suite gap.DESIGN.md— the visual system (tokens, components, breakpoints) andmake shotsvisual verification.thermograph-docs— the repo-topology decision record this split implements.