From 8fb2f9ef9d5a4257af38b1c37bbdfb5100ed628c Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Fri, 18 Sep 2026 15:07:49 +0200 Subject: [PATCH] =?UTF-8?q?v0.255.0=20=E2=80=94=20the=20globe=20on=20the?= =?UTF-8?q?=20sign-in-flow=20pages:=20styled,=20and=20inside=20the=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects in v0.254.0's globe, both plain on a browser and neither catchable by anything that existed — every test read the MARKUP, and the fault was in which CSS file the browser fetched. The shells requested /static/style.css with NO ?v=, while layout.html has carried one since v0.166.0. A browser holding a copy from before v0.254.0 kept serving CSS with no .lang-globe rules, so the globe came out as a bare unstyled
— a stray triangle and two plain words at the edge of the window. It was FIVE shells, not the three named: both guest share pages have the same fault for any CSS change, and their visitor is the likeliest of all to be holding an old copy. And .Version was missing from three of those five data maps, which is exactly how the next one would be forgotten — it is now filled at the one choke point every shell renders through. The globe also floated outside the card, pinned to the corner of the VIEWPORT, reading as part of the browser rather than the page. It now sits inside the card, centred under the footer, with the menu opening upward via the shared rule — so the dashboard and the shells cannot drift. AND A THIRD, caught by a test that already existed: putting the version on the guest share pages would have printed the controller build onto a page a stranger with a capability URL can open. TestShareGuest_HeadersTilesNoAdminChrome refused it. Those two now take an opaque per-build tag — same cache-busting, no disclosure. The fill is ONE function shared with the parity harness, because a fixture rendered through a different data path is a picture of a page nobody serves, which the previous release got wrong twice. 15 shell fixtures re-captured; 91 identical, every dashboard page among them. MinAgent: 0.131.0 (unchanged). No hub release needed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CHANGELOG.md | 40 +++ CONTEXT.md | 4 +- REPORT.md | 300 +++--------------- controller/internal/web/i18n_parity_test.go | 3 + controller/internal/web/lang_visitor_test.go | 27 ++ controller/internal/web/server.go | 39 +++ .../internal/web/storage_handlers_test.go | 5 +- controller/internal/web/templates/claim.html | 4 +- .../templates/launcher_share_password.html | 2 +- .../web/templates/launcher_shared.html | 2 +- controller/internal/web/templates/login.html | 4 +- .../internal/web/templates/recovery.html | 4 +- controller/internal/web/templates/style.css | 12 +- .../i18n_parity/claim_reset_code.html | 16 +- .../i18n_parity/claim_reset_nocode.html | 16 +- .../i18n_parity/claim_setup_code.html | 16 +- .../i18n_parity/claim_setup_nocode.html | 16 +- .../i18n_parity/launcher_share_password.html | 2 +- .../i18n_parity/launcher_shared_apps.html | 2 +- .../i18n_parity/launcher_shared_empty.html | 2 +- .../web/testdata/i18n_parity/login.html | 16 +- .../i18n_parity/recovery_locked_can.html | 16 +- .../i18n_parity/recovery_locked_cannot.html | 16 +- .../i18n_parity/recovery_locked_confirm.html | 16 +- .../i18n_parity/recovery_unlocked_apps.html | 16 +- .../i18n_parity/recovery_unlocked_empty.html | 16 +- .../recovery_unlocked_unavailable.html | 16 +- .../recovery_unlocked_untagged.html | 16 +- 28 files changed, 277 insertions(+), 367 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a72ec69..9a4ec69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +## v0.255.0 — The globe on the sign-in-flow pages: styled, and inside the card (2026-09-18, fixes v0.254.0) + +**MinAgent: 0.131.0** (unchanged). **No hub release needed.** Nothing on the wire moved. One Hungarian +block changes, on the pages outside the dashboard chrome only — **zero dashboard fixtures were +re-captured**. + +Two defects in v0.254.0's globe, both visible on a real browser and neither catchable by any test that +existed: + +**1. The stylesheet was requested with no cache-buster, so the globe rendered UNSTYLED.** `layout.html` +has always used `/static/style.css?v={{.Version}}`; the shells did not. A browser that had the file +cached from before v0.254.0 kept serving a copy with no `.lang-globe` rules in it, and the globe came +out as a bare `
` — a stray triangle and two plain words, floating at the edge of the window. +**It was not three shells but FIVE**: `login`, `claim`, `recovery`, **and both guest share pages**. The +two share pages have no globe, but they have the same stale-stylesheet problem for any CSS change, and +their visitor is the likeliest of all to be holding an old copy. All five fixed. + +**And `.Version` was not in the data of three of them** — which is exactly how the next shell would +have been missed. It is now set in `executeTemplateLang`, the single choke point every shell renders +through, so a new shell cannot forget it. + +**2. The globe floated outside the card.** `.shell-lang` was pinned to the corner of the VIEWPORT, so +it read as part of the browser rather than part of the page. It now sits **inside the card, centred +under the footer**, with the menu opening upward — which is the shared `.lang-globe-menu` rule, so the +block adds no positioning of its own and the dashboard and the shells cannot drift apart. + +**A third defect, found by an existing test while fixing the first two.** Putting `?v={{.Version}}` +on the guest share pages would have printed the controller version onto a page **a stranger holding a +capability URL can open** — `TestShareGuest_HeadersTilesNoAdminChrome` refuses exactly that, and +refused it. Those two pages now take an **opaque per-build tag** (`AssetTag`, four bytes of a hash of +the version): it busts a cache just as well and tells a visitor nothing about which build is running. +The fill lives in one function, `addShellAssetData`, called by `executeTemplateLang` **and** by the +parity harness — because a fixture rendered through a different data path is a picture of a page +nobody serves, which this release's predecessor got wrong twice. + +`TestGlobeOnAnonymousShells` gained both halves — the stylesheet carries a non-empty `?v=`, and the +globe is inside the card and below the footer. Each was red-proofed by restoring the defect's exact +shape. The test server gained a version, because an empty `?v=` busts a cache exactly once and never +again, and baking that into a fixture would have hidden it. + ## v0.254.0 — The saved notes follow the language, and the switch becomes a globe (2026-09-18, R-557 slice 2 release C — SLICE 2 CLOSED) **MinAgent: 0.131.0** (unchanged). **No hub release needed.** Nothing on the wire moved. diff --git a/CONTEXT.md b/CONTEXT.md index 7159edc..32b41d6 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,9 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-09-18 (v0.254.0 — localisation slice 2 CLOSED: saved notes follow the language, the switch is a globe) +Last updated: 2026-09-18 (v0.255.0 — the globe on the sign-in-flow pages: styled, and inside the card) + +> **2026-09-18 — v0.255.0 (fixes the v0.254.0 globe).** The sign-in-flow pages requested `style.css` with **no `?v=`**, so a browser holding a pre-0.254.0 copy served CSS with no `.lang-globe` rules and the globe rendered as a bare unstyled `
` — **invisible to every test, because they all read markup and the fault was in which CSS file the browser fetched**. It was FIVE shells, not three (both guest share pages too), and `.Version` was missing from three of their data maps — now set once in `executeTemplateLang`. The globe also moved INSIDE the card, centred under the footer, menu opening upward via the shared rule. 15 shell fixtures re-captured, **zero dashboard ones**. R-579. > **2026-09-18 — FLEET FLOOR RAISED to 0.254.0** (operator asked), `min_agent` 0.131.0 declared — above the vouched golden 0.246.0, so the declaration carries it (R-472). Hub: `managed floor SERVED … from declared`; demo-felhom went 0.253.0 -> 0.254.0 **by itself in ~40 s**, healthy, four other containers up, own log `settle-gate: GO — at/above floor 0.254.0`, and its sign-in page shows **one globe, zero old text links**. **Peti Proxmox (DOWN 65 d, 0.115.0) and Tester 1 (DOWN 1 d, 0.245.0) did NOT get it** and take it unattended when they return — untested on this version. Evidence: `felhom.eu/documentation/audits/i18n-slice2-2026-09-18/floor-raise-0.254.0.md`. diff --git a/REPORT.md b/REPORT.md index a9c6a3a..207a12c 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,274 +1,70 @@ -# REPORT — localisation slice 2, COMPLETE (controller v0.252.0 + v0.253.0 + v0.254.0, R-557) +# REPORT — the globe on the sign-in-flow pages, fixed (controller v0.255.0) -**2026-09-18 · base commit `736f54b49610` (v0.251.0) · MinAgent 0.131.0, unchanged · no hub release.** -**Three releases: A (the sentences shown now), B (the 179 error messages), C (the saved notes + the globe).** -Architecture read first and named: `felhom.eu/documentation/architecture/10-localisation.md` §1 -(parity), §2 (mechanism), §5 (gates), §9 (the R-553 signals), §10 (this slice); `07-backup-architecture.md` -for what the hub reads from a box; `05-hub-architecture.md` for what the hub composes itself. - ---- - -## 1. Claims in the task that live source disproved — named first - -| the task said | source says | -|---|---| -| "`cloudflare/countries.go` is used by `report/types.go` (on the wire)" | **No.** `report/builder.go` puts country **CODES** on the wire (`AllowedCountries []string`); no country NAME leaves the box. `CountryName()` has no caller at all. So the names are display-only and were free to translate — which is what this release did. | -| "the hub's dispatcher composes its own customer mail from event kinds (grep of `dispatcher.go` for `.Message`: nothing)" | **Wrong in substance.** `.Message` finds nothing because the message arrives as a PARAMETER, not a field. `FormatCustomerEmail` (hub `notify/templates.go` L167-210) uses `customerMessages[eventType]` when it has one and **falls back to the controller's message** when it does not — and appends it as „- Üzenet: %s" whenever the two differ. Several event types deliberately carry no entry so the controller's sentence IS the mail (hub `api/handler.go`, the notes at L2034/L2045/L2066). The conclusion (do not translate them) was right; the reason was not. | -| "5 flash readers / 8 writers" | 8 writers, **8 read sites** — the three named plus `auth.go:100` (login), `server.go:498` (claim) and the `flash_error` twins. All eight converted. | -| per-file counts (`handlers.go` 121, `offbox_handlers.go` 72, `storage_handlers.go` 48, `handler_debug.go` 40, `offbox_reconstitute.go` 41, `notify/notifier.go` 30, `api/router.go` 19) | measured at `736f54b49610`: **120 / 70 / 45 / 39 / 38 / 31 / 18**. Totals: **1 120** Hungarian literals, not 1 141. | -| "`scripts/i18n_inventory.py`" (as a controller script) | it lives in **`felhom.eu/scripts/`**. Its Go-literal walker was copied into the new gate rather than imported — a controller clone need not sit beside felhom.eu, and a gate that can fail to import its scanner can silently skip. | - -## 2. What shipped - -**226 Go literals converted**, plus 237 country names now available in English. 426 keys, each -recorded in `scripts/i18n_go_keys.json` against the literal it replaced. - -| file | before | after | converted | of the rest, `fmt.Errorf` (release B) | -|---|---|---|---|---| -| `internal/web/handlers.go` | 120 | 44 | 76 | 0 | -| `internal/web/offbox_handlers.go` | 70 | 22 | 48 | 0 | -| `internal/web/storage_handlers.go` | 45 | 19 | 26 | 16 | -| `internal/api/router.go` | 18 | 0 | 18 | 0 | -| `internal/web/share_handlers.go` | 22 | 5 | 17 | 0 | -| `internal/web/sharing_handlers.go` | 18 | 3 | 15 | 1 | -| `internal/web/alerts.go` | 13 | 0 | 13 | 0 | -| `internal/web/tier2_config_handler.go` | 8 | 3 | 5 | 0 | -| `internal/api/geo.go` | 5 | 0 | 5 | 0 | -| `internal/web/backup_handlers.go` | 12 | 9 | 3 | 0 | -| `internal/cloudflare/countries.go` | 113 | 113 | 0 (the TABLE stays — it validates codes) | 0 | - -Mechanism: `i18n.Bundle.Msgf`; `web.(*Server).msg/msgLang/msgN`; `api.(*Router).msg/msgLang/langFor/countryName`; -`flashQuery`/`flashText`/`flashFrom`; `Alert.MessageKey`/`MessageArgs`/`LinkTextKey` + `Alert.rendered(lang)`; -`TitleArgs` for the four app-named titles (R-566, closed). - -**Word order without a second syntax.** The task asked for a named-parameter (`{{.Name}}`) form for -multi-parameter messages. It is not here, deliberately: English reorders with Go's own explicit -argument indexes (`%[2]s`), which `fmt` already understands. The Hungarian value then stays the format -string the code always had, byte for byte — which is exactly what the parity gate compares, so the -measurement needs no exception. `TestBundleParametersMatchAcrossLanguages` counts an indexed verb as -one verb, so a reordered English still has to use the same verbs on the same values. - -## 3. The wire — surveyed, frozen, not translated (A.1) - -Everything a HUB reads keeps its bytes. Filed against **R-558** (slice 3 owns it): - -| producer | where it lands | why it stays | -|---|---|---| -| `internal/monitor/healthcheck.go` — 5 storage sentences | report `health.warnings` / `health.issues` | the hub stores and shows them to the operator | -| `internal/notify/notifier.go` — 31 event messages | event `message` → `FormatCustomerEmail` | **the household reads these in an e-mail**; several types have no `customerMessages` entry precisely so this sentence is the mail | -| `internal/cloudflare/countries.go` — the table | nothing (codes only) | it validates codes; the DISPLAY name is what follows the language | - -Pinned by `TestStorageWarningWireTextIsFrozen`, `TestStorageWarningFormatsAreFrozen`, -`TestEventMessageWireTextIsFrozen`, `TestEventMessagesCarryNoBundleKey`. Both goldens were captured -from the producers at the base commit, before any change, and both were seen to fail. - -The **persisted** text found by the same survey is listed in §6 for release C. - -## 4. Tests and their red-proofs - -Every one was run against a planted break and watched to convict; the planted change was then -reverted and the suite re-run green. Full log: `audits/i18n-slice2-2026-09-18/redproofs.txt`. - -| test / gate | what was planted | what convicted | -|---|---|---| -| `i18n_go_parity.py` | one byte added to a hu.json value | `CHANGED`, naming the key and the base literal | -| `i18n_go_parity.py` | a key citing text no literal has | `INVENTED`, naming the text | -| `i18n_go_parity.py` | a joined key with reworded fragments | `INVENTED`, naming the fragment | -| `test_gate_decoys.py` go-parity ×3 | reworded / invented / unlisted | all three rejected | -| `TestEventMessageWireTextIsFrozen` | „telepítve" → „telepitve" | got/want printed | -| `TestStorageWarning*IsFrozen` | a wire warning translated | both tests | -| `TestFlashOnAServerWithNoBundleField` | `s.bundle()` without the `i18n.Shared` fallback | the page showed `flash.share.enabled` | -| `TestFlashKeyRoundTrip` | — | legacy prose, unknown key, `