2026-07-25 07:08:54 +00:00
|
|
|
# frontend/ — agent instructions
|
2026-07-22 18:59:59 +00:00
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
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.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
Read the root `CLAUDE.md` first for the branch model, deploy contract and image
|
|
|
|
|
names.
|
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 live service is Go — `server/`
|
|
|
|
|
|
|
|
|
|
`server/` (module `thermograph/frontend`, Go 1.26) is what builds, tests, ships
|
|
|
|
|
and runs. It is the only thing in the `emi/thermograph/frontend` image.
|
|
|
|
|
|
|
|
|
|
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 implementation**, kept as the reference the port was made
|
|
|
|
|
from. Nothing deploys them. Don't add features there; don't take their file
|
|
|
|
|
paths as current. (They still carry duplicate copies of some contracts below —
|
|
|
|
|
notably `api_client.py`'s `API_VERSION` pin and `format.py`'s `F_COUNTRIES`. If
|
|
|
|
|
you use them locally, keep them in step or you will debug a phantom.)
|
|
|
|
|
|
|
|
|
|
Deleting them is a live question, not a settled one — the Python test tiers
|
|
|
|
|
still run against the fixtures the Go tests share. Raise it rather than doing it
|
|
|
|
|
in passing.
|
|
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
## Build & verify
|
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
|
|
|
- `cd server && go build ./... && go vet ./... && go test ./...` — **the suite
|
|
|
|
|
that matters.**
|
|
|
|
|
- **CI runs it inside `Dockerfile`'s builder stage**, which does `gofmt -l` +
|
|
|
|
|
`go vet` + `go test ./...` before compiling. A failing Go test therefore fails
|
|
|
|
|
the image build — that is the whole frontend check. There is no separate
|
|
|
|
|
frontend test step in any workflow.
|
|
|
|
|
- `make test-unit` / `make test-integration` — the **Python** tiers. Local only;
|
|
|
|
|
CI runs neither. The unit tier is hermetic (SSR rendering fed committed
|
|
|
|
|
fixtures); the integration tier pulls and runs a real backend image.
|
2026-07-25 07:08:54 +00:00
|
|
|
- `make backend-up` / `make backend-down` — a local backend container for dev.
|
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 image tag is derived from this checkout (last commit touching `backend/`,
|
|
|
|
|
the same domain-keyed rule build-push and deploy use); override with
|
|
|
|
|
`THERMOGRAPH_BACKEND_TEST_TAG=sha-<12hex>`.
|
2026-07-25 07:08:54 +00:00
|
|
|
- `make capture-fixtures` — refresh `tests/fixtures/*.json` from a live 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
|
|
|
**These fixtures feed the Go tests too**, not just the Python ones.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
## Running it
|
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
|
|
|
`THERMOGRAPH_API_BASE_INTERNAL` is **required** — the boot fails loudly without
|
|
|
|
|
it, as the Python raised at import.
|
|
|
|
|
|
|
|
|
|
Build in `server/`, run from `frontend/`: `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-25 07:08:54 +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
|
|
|
```
|
|
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
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
|
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
|
|
|
default), `THERMOGRAPH_SSR_CACHE_TTL` (default 600s),
|
|
|
|
|
`THERMOGRAPH_GOOGLE_VERIFY` / `THERMOGRAPH_BING_VERIFY`, `PORT` (default 8080).
|
|
|
|
|
|
|
|
|
|
Boot is fail-loud on bad **config or content**: a missing backend URL, a garbage
|
|
|
|
|
TTL, or malformed `content/*.yaml` all stop the process rather than 500 on the
|
|
|
|
|
first request.
|
2026-07-25 07:08:54 +00:00
|
|
|
|
|
|
|
|
## Layout
|
|
|
|
|
|
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/internal/content/`** — the SSR page handlers (climate hub, city,
|
|
|
|
|
month, records, glossary, about, privacy) + `robots.txt` + `sitemap.xml`,
|
|
|
|
|
plus the template funcmap and SEO helpers. The backend's
|
|
|
|
|
`content_payloads.py` owns `page_title` / `canonical_path` / `breadcrumb` /
|
|
|
|
|
`jsonld`, **not this domain**.
|
|
|
|
|
- **`server/internal/contentapi/`** — the backend `/content/*` client: TTL
|
|
|
|
|
cache, bounded LRU, per-key single-flight, origin forwarding. All four
|
|
|
|
|
properties are load-bearing; read the file before changing caching.
|
|
|
|
|
- **`server/internal/render/`** + `render/templates/*.tmpl` — `html/template`
|
|
|
|
|
over an `embed.FS`, plus the ETag helpers. Templates are **embedded**, so a
|
|
|
|
|
template change needs a rebuild.
|
|
|
|
|
- **`server/internal/{config,format,contentdata,handlers}/`** — env parsing,
|
|
|
|
|
unit-aware °C/°F formatting and band names, the glossary/pages loader, and
|
|
|
|
|
the SPA-shell + static handlers.
|
2026-07-25 07:08:54 +00:00
|
|
|
- **`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 + `/cell` bundle prefetch), `shared.js`.
|
|
|
|
|
- **`static/style.css`** — the single hand-written stylesheet; design tokens live
|
|
|
|
|
here, documented in `DESIGN.md`.
|
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
|
|
|
- **`content/*.yaml`** — structured SSR copy, loaded by
|
|
|
|
|
`server/internal/contentdata`.
|
2026-07-25 07:08:54 +00:00
|
|
|
|
|
|
|
|
## Contracts with 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
|
|
|
- **API version is pinned in exactly two places** — `server/internal/config`'s
|
|
|
|
|
`THERMOGRAPH_API_VERSION` (Go) 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.
|
2026-07-25 07:08:54 +00:00
|
|
|
- **`shared.js::pctOrd()` must mirror `backend`'s `data/grading.py::pct_ordinal()`**
|
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
|
|
|
in behaviour — floor into `1..99`, never 0 or 100 — and so must
|
|
|
|
|
`server/internal/format`'s `PctOrdinal`. Every percentile on every surface
|
|
|
|
|
goes through one of the three; if they diverge, the same reading says two
|
2026-07-25 07:08:54 +00:00
|
|
|
different things in two places.
|
|
|
|
|
- **`/cell` bundle + ETag** — `cache.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.
|
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
|
|
|
- **Fahrenheit country set** — `server/internal/format`'s `FCountries` and
|
|
|
|
|
`static/units.js`'s `F_REGIONS` must stay identical to the backend's
|
|
|
|
|
`F_COUNTRIES`. Both are now asserted by tests in
|
|
|
|
|
`server/internal/format/format_test.go`: the backend cross-check is a
|
|
|
|
|
checkout-only guard (the image build context is `frontend/`, so `backend/` is
|
|
|
|
|
unreachable there), while the `units.js` check runs in the image build too —
|
|
|
|
|
`Dockerfile` copies that one file into the builder stage for exactly that
|
|
|
|
|
reason.
|
|
|
|
|
- **Boot must survive an unreachable backend.** The IndexNow-key fetch is tried
|
|
|
|
|
once at boot, caught, and falls back to a lazy per-request lookup.
|
|
|
|
|
Asynchronous FE/BE deploys depend on this — don't make it fatal.
|
2026-07-22 18:59:59 +00:00
|
|
|
|
|
|
|
|
## Commits & PRs
|
|
|
|
|
|
2026-07-25 07:08:54 +00:00
|
|
|
Concise and technical. Never mention AI, assistants or automated authorship.
|