thermograph/frontend/server
emi da82abda27
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 14s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 42s
Deploy frontend to beta VPS / deploy (push) Successful in 1m5s
frontend: fetch City() concurrently with the page's primary API call (#37)
MonthPage and RecordsPage each made two backend calls sequentially
(CityMonth/CityRecords, then City) where the second never depended on the
first's result. fetchWithCity launches both concurrently via goroutines
and a WaitGroup.

Verified live against a stub with an injected 400ms delay on both
endpoints: city page (1 call) and month/records pages (2 calls each) all
cost ~0.404s now, not double for the two-call pages.

Error priority preserved exactly: primary's error wins even when City
also fails, matching the old sequential code. One trade-off: City() is
now always launched even on a request about to 404 from primary, costing
one extra cheap lookup on that rare path.

Tests include a deterministic concurrency proof via rendezvous channels
(the old sequential code would deadlock this test, not just run it
slower). Full suite green under -race -count=2.
2026-07-24 06:25:06 +00:00
..
internal frontend: fetch City() concurrently with the page's primary API call (#37) 2026-07-24 06:25:06 +00:00
go.mod frontend: rewrite the SSR content service in Go (#28) 2026-07-24 00:53:48 +00:00
go.sum frontend: rewrite the SSR content service in Go (#28) 2026-07-24 00:53:48 +00:00
main.go frontend: rewrite the SSR content service in Go (#28) 2026-07-24 00:53:48 +00:00
README.md frontend: rewrite the SSR content service in Go (#28) 2026-07-24 00:53:48 +00:00

thermograph-frontend (Go)

The SSR frontend service, ported from the Python implementation one directory up (app.py / content.py / api_client.py / format.py): server-rendered content pages, the interactive tool's SPA shells, and every static asset. It is I/O-bound glue over the backend's /content/* JSON API — no climate maths, no database, no auth.

Layout

go.mod                        module thermograph/frontend (Go 1.26)
main.go                       config + mux + static + graceful shutdown
internal/config/              every env var the service reads (same names,
                              defaults and required/optional split as the
                              Python — see config.go's field docs)
internal/contentapi/          backend /content/* client: TTL cache, bounded
                              LRU, per-key single-flight, origin forwarding;
                              typed payloads in types.go
internal/render/              html/template engine over an embed.FS +
                              response ETag helpers (W/"sha1[:20]",
                              If-None-Match handling)
internal/render/templates/    the page templates (embedded; *.tmpl)
internal/contentdata/         glossary.yaml / pages.yaml loader (fail-loud
                              validation, file order preserved)

Dependencies: stdlib plus gopkg.in/yaml.v3 — the committed SSR copy in frontend/content/*.yaml is shared with the rest of the repo and uses block/ folded scalars, so a YAML parser is genuinely required.

Run locally

cd frontend/server
go build -o thermograph-frontend .
cd ..            # static/ and content/ resolve relative to the working dir
THERMOGRAPH_API_BASE_INTERNAL=http://127.0.0.1:8137 \
THERMOGRAPH_BASE=/thermograph \
  ./server/thermograph-frontend

THERMOGRAPH_API_BASE_INTERNAL is required — the boot fails loudly without it, same as the Python raised at import. Other env vars (all optional): THERMOGRAPH_BASE (default /thermograph; the image sets /), THERMOGRAPH_API_VERSION (default v2 — bump only per the API-version pinning contract in frontend/CLAUDE.md), THERMOGRAPH_API_BASE_PUBLIC, THERMOGRAPH_SSR_CACHE_TTL (seconds, default 600), THERMOGRAPH_GOOGLE_VERIFY / THERMOGRAPH_BING_VERIFY, and PORT (default 8080).

The process expects static/ and content/ in its working directory (frontend/ locally, /app in the image). Templates are embedded in the binary; static assets and the YAML copy are read from disk.

Test / verify

cd frontend/server
go build ./... && go vet ./... && go test ./...

The deployed binary is /usr/local/bin/thermograph-frontend inside the emi/thermograph/frontend image; the image name, frontend-* CI workflows and deploy path are unchanged from the Python service.