fix: the recovery page's globe must write the HOUSEHOLD's setting, not a cookie nothing reads
gates / gates (push) Successful in 24s

/recovery is in the AUTHENTICATED route table — its reader is the household, not a visitor. The
first draft of v0.254.0 gave it the anonymous form, which sets the felhom_lang cookie that
langFor deliberately ignores once there is a session: the button would have appeared to work
and done nothing. Found by the live probe on demo-hp reporting no globe on /recovery (it 302s
to /login without a session) and then reading the route table.

executeTemplateLang now branches on hasSession: household form with its session CSRF, or the
visitor form without. The parity harness and TestI18nDirectRenderPagesFollowLanguage carry the
same branch, so the fixture is the form the real page serves — the trap this release already
walked into once with the shells.

A per-session CSRF token cannot be a fixture value, so it is blanked on both sides of every
parity comparison, exactly as relative ages already were. What stays pinned is that the field is
THERE and WHICH form it sits in — the half that says whether the globe writes the household's
setting or the visitor's cookie.

Evidence regenerated: 3 change shapes across 106 fixtures, 5 byte-identical (both guest share
pages and the catch-all — the three that must not change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-18 14:28:24 +02:00
parent 48f3336956
commit 0b1486df8f
104 changed files with 257 additions and 211 deletions
+11 -2
View File
@@ -306,10 +306,19 @@ func TestI18nDirectRenderPagesFollowLanguage(t *testing.T) {
// The SAME request path the parity harness renders with. Since v0.254.0 the page carries a
// language globe whose `back` is the path the visitor is on, so a different path here would
// differ from the fixture in one attribute and say nothing about the language.
if err := s.executeTemplateLang(&buf, httptest.NewRequest(http.MethodGet, "/i18n-fixture", nil), p.tmpl, c.data()); err != nil {
//
// And the same SESSION state: /recovery is an authenticated route, so its reader is the
// household and its globe writes their setting; /login and /claim are met with no session.
// Rendering recovery session-less here would compare the real page against a form it never
// serves — the mistake this release already made once, in the parity harness.
req := httptest.NewRequest(http.MethodGet, "/i18n-fixture", nil)
if i18nSessionTemplates[p.tmpl] {
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: newTestSession(s)})
}
if err := s.executeTemplateLang(&buf, req, p.tmpl, c.data()); err != nil {
t.Fatalf("%s [%s]: %v", p.tmpl, lang, err)
}
got := relativeAgeRe.ReplaceAllString(buf.String(), "# $1")
got := normaliseI18nRender(buf.String())
if lang == "hu" {
want, err := os.ReadFile(filepath.Join("testdata", "i18n_parity", p.caseName+".html"))
if err != nil {