2026-07-22 18:59:59 +00:00
|
|
|
# Thermograph frontend
|
|
|
|
|
|
|
|
|
|
The server-rendered content pages, the interactive-tool SPA shells, and every
|
|
|
|
|
static asset for [Thermograph](https://thermograph.org) — grades recent local
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
weather against ~45 years of climate history. This domain 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 builds its own image
|
|
|
|
|
(`emi/thermograph/frontend`) and deploys independently of the backend.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
See [`CLAUDE.md`](CLAUDE.md) for the agent-facing detail on the deploy contract
|
|
|
|
|
and the cross-service contracts, and [`DESIGN.md`](DESIGN.md) for the visual
|
|
|
|
|
system.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
## The service is Go
|
2026-07-22 18:59:59 +00:00
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
`server/` is the live implementation. The Python files at this level (`app.py`,
|
|
|
|
|
`content.py`, `api_client.py`, `format.py`, `content_loader.py`, `paths.py`,
|
|
|
|
|
`templates/*.html.j2`) are the **superseded original**, kept as the reference
|
|
|
|
|
the port was made from — nothing deploys them. See `server/README.md` for the
|
|
|
|
|
port's own notes.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
```
|
|
|
|
|
server/ the service (module thermograph/frontend, Go 1.26)
|
|
|
|
|
main.go config + mux + static + graceful shutdown
|
|
|
|
|
internal/config/ every env var it reads
|
|
|
|
|
internal/contentapi/ backend /content/* client: TTL cache, bounded LRU,
|
|
|
|
|
per-key single-flight, origin forwarding
|
|
|
|
|
internal/content/ SSR page handlers, template funcmap, SEO
|
|
|
|
|
internal/contentdata/ glossary.yaml / pages.yaml loader (fail-loud)
|
|
|
|
|
internal/format/ unit-aware °C/°F formatting + band names
|
|
|
|
|
internal/handlers/ SPA shells + static serving
|
|
|
|
|
internal/render/ html/template over embed.FS + ETag helpers
|
|
|
|
|
internal/render/templates/ the page templates (embedded, *.tmpl)
|
|
|
|
|
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/fixtures/ golden payloads — consumed by the GO tests as well
|
|
|
|
|
as the Python tiers
|
|
|
|
|
tests/{unit,integration}/ the Python tiers (local only; CI runs neither)
|
|
|
|
|
tools/shoot.py the screenshot sweep for visual verification
|
|
|
|
|
Dockerfile golang builder (gofmt + vet + test) → alpine runtime
|
|
|
|
|
```
|
2026-07-22 18:59:59 +00:00
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
Dependencies are **stdlib plus `gopkg.in/yaml.v3`** — the committed SSR copy in
|
|
|
|
|
`content/*.yaml` uses block/folded scalars, so a real YAML parser is required.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
|
|
|
|
## Talking to the backend
|
|
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
All data comes from the backend's content API over HTTP — this process holds
|
|
|
|
|
nothing. Two env vars control the topology:
|
2026-07-22 18:59:59 +00:00
|
|
|
|
|
|
|
|
- **`THERMOGRAPH_API_BASE_INTERNAL`** (required) — where this process reaches
|
|
|
|
|
the backend server-side, e.g. `http://backend:8137` in compose, or
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
`http://127.0.0.1:8137` for a local backend. Boot **fails loudly** if unset:
|
|
|
|
|
a missing backend URL should break the boot, not silently 500 on the first
|
|
|
|
|
request.
|
|
|
|
|
- **`THERMOGRAPH_API_BASE_PUBLIC`** (optional) — the backend's browser-facing
|
|
|
|
|
origin, used only when frontend and backend are served cross-origin. Left
|
|
|
|
|
empty (the default), asset/API references stay relative and same-origin,
|
|
|
|
|
which is today's real deployed topology.
|
|
|
|
|
|
|
|
|
|
`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).
|
2026-07-22 18:59:59 +00:00
|
|
|
|
|
|
|
|
**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
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
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 configuration first.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
|
|
|
|
## Build & run
|
|
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
Build in `server/`, run from here — `static/` and `content/` resolve relative to
|
|
|
|
|
the working directory, while templates are embedded in the binary:
|
|
|
|
|
|
2026-07-22 18:59:59 +00:00
|
|
|
```bash
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
cd server && go build -o thermograph-frontend .
|
|
|
|
|
cd ..
|
2026-07-22 18:59:59 +00:00
|
|
|
THERMOGRAPH_API_BASE_INTERNAL=http://127.0.0.1:8137 \
|
|
|
|
|
THERMOGRAPH_BASE=/thermograph \
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
./server/thermograph-frontend
|
2026-07-22 18:59:59 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Or build the image directly:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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).
|
|
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
## Test
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd server && go build ./... && go vet ./... && go test ./...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`Dockerfile`'s builder stage runs `gofmt -l` + `go vet` + `go test ./...` before
|
|
|
|
|
compiling, so **a failing Go test fails the image build** — and that is the
|
|
|
|
|
whole frontend check in CI. There is no separate frontend test step in any
|
|
|
|
|
workflow.
|
|
|
|
|
|
|
|
|
|
The Python tiers are local-only:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
make test-unit # hermetic: SSR rendering fed committed fixtures
|
|
|
|
|
make test-integration # pulls + runs a real backend image
|
|
|
|
|
make backend-up # just the backend container, for dev
|
|
|
|
|
make capture-fixtures # refresh tests/fixtures/*.json from a live backend
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`make backend-up` derives the backend image tag from this checkout (the last
|
|
|
|
|
commit touching `backend/` — the same domain-keyed rule build-push and deploy
|
|
|
|
|
use); pin one with `THERMOGRAPH_BACKEND_TEST_TAG=sha-<12hex>`.
|
|
|
|
|
|
2026-07-22 18:59:59 +00:00
|
|
|
## More detail
|
|
|
|
|
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
- [`CLAUDE.md`](CLAUDE.md) — agent-facing instructions: build/verify, layout,
|
|
|
|
|
API-version pinning, and the `/cell`/ETag, `pctOrd()` and Fahrenheit-set
|
|
|
|
|
contracts.
|
|
|
|
|
- [`server/README.md`](server/README.md) — the Go port's own notes.
|
2026-07-22 18:59:59 +00:00
|
|
|
- [`DESIGN.md`](DESIGN.md) — the visual system (tokens, components,
|
frontend: fix the three inconsistencies the onboarding guide found
1. CLAUDE.md and README.md described the superseded Python service. Both now
describe server/ (Go), say plainly that the Python files at that level are
the original the port was made from, and drop CLAUDE.md's claim that
`make test-unit` is "the tier CI runs" — CI's only frontend check is the
Dockerfile builder stage's gofmt + vet + go test.
2. static/units.js's F_REGIONS was guarded by nothing, despite three source
comments claiming "a test asserts all three stay identical": the only check
compared the Go set against the backend's Python. TestFCountriesMatchesUnitsJS
now diffs the browser copy both directions.
That backend cross-check also skips in CI — the image build context is
frontend/, so backend/ is unreachable from the builder stage, which is the
only place CI runs these tests. static/ IS in the context, so the Dockerfile
copies units.js into the builder and the new assertion runs during the image
build. Verified by mutating units.js and confirming the build fails.
3. Both docker-compose.test.yml files defaulted to the retired
emi/thermograph-backend/app path, and the frontend harness pinned the
split-era v0.0.2-split-ci tag. Path corrected in both. Rather than swap one
hardcoded pin for another, backend-for-tests.sh now derives the tag from the
checkout — sha-<12hex of `git log -1 -- backend/`>, the same domain-keyed
rule build-push.yml and deploy.yml use — and compose requires the variable
so a stale pin cannot creep back in.
Verified: backend 429 passed/8 skipped; frontend go vet clean and all packages
ok; frontend image builds; `make backend-up` pulls and serves on the derived
tag; shellcheck zero findings across the tree.
Unrelated pre-existing issue noted in the docs, not fixed here:
`make test-integration` fails 7/16 with 503 against a cold throwaway backend
(empty database, nothing warm). Reproduced identically on the old image, so it
predates this change.
Claude-Session: https://claude.ai/code/session_01AfXqHrxCJLs2D7hpQkiUiJ
2026-07-25 18:42:16 +00:00
|
|
|
breakpoints) and `tools/shoot.py` visual verification.
|
|
|
|
|
- [`docs/onboarding/`](../docs/onboarding/README.md) — the developer onboarding
|
|
|
|
|
path for the whole monorepo.
|