docs: localisation slice 2 CLOSED (controller v0.254.0) — R-577, R-578, and a probe rule
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
10-localisation.md §10.3: the saved notes follow the box language at write time, with the one-night consequence stated rather than hidden; the globe, and the table of WHO reads which page and where its globe posts — getting that wrong makes the button do nothing, which it did on /recovery until the live probe found it. Decision 6 superseded a second time; decision 8 (a claim carries the visitor's language) recorded. Decision 5 of §11's anonymous-surface line: changing what a VISITOR reads is within what an anonymous request may do; changing anything the household owns is not, and POST /lang can do only the first. R-578 — the deadlock, and why it is a row rather than a fixed bug: UpdateOffboxStatus holds the settings write lock while running its callback, boxLang() wants the read lock, sync.RWMutex is not reentrant. On a real box an off-site run would have hung FOREVER holding that lock. The symptom was a test suite going from 8 minutes to a 25-minute timeout. Fixed and guarded, but the guard covers one package and three helper names; the class needs a gate. R-577 — a guest share visitor still has no way to pick a language, and the household's setting is the wrong default for a stranger. Deliberately left, pinned by a test, and the operator's to decide because it is a promise the share feature makes. .claude/rules/live-probes.md, unconditional: never send a deploy request for an app that is not installed, not even expecting a refusal — the endpoint accepts first and validates later. Two sessions made that mistake in two days, the second WITH a prompt line forbidding it. A prompt is read once; a rule file is loaded every session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -279,7 +279,7 @@ session). Each slice ends with the parity gate green for every page it touched.
|
||||
|---|---|---|---|---|
|
||||
| 0 (done) | — | mechanism, 3 pages + layout, setting, report field, gates | Hungarian unchanged by measurement; English reachable | spent |
|
||||
| 1 (done, v0.248.0–v0.250.0) | R-556 | the other 31 dashboard templates (~1 500 strings, 600 of them JS), parity fixtures per page; switch shown to everyone; English retrieval stems; the extractor's ASCII word list reviewed per page | the whole dashboard in English with Hungarian byte-identical | 12–16 h, three releases |
|
||||
| 2 | R-557 (after R-553) | Go-side customer strings; flash-in-URL → keys; country names; alert texts; the 179 error messages | a page's server messages follow the language | 16–20 h, three releases · **A and B shipped 2026-09-18; C remains** |
|
||||
| 2 (done, v0.252.0–v0.254.0) | R-557 | Go-side customer strings; flash-in-URL → keys; country names; alert texts; the 179 error messages; the saved notes; the globe | a page's server messages follow the language | **CLOSED 2026-09-18** |
|
||||
| 3 | R-558 | hub: per-customer language at creation; `configgen` renders it; `customerMessages` (39), the 5 lifecycle mails and the self-bind page in English; dispatcher reads the reported language | a household's e-mails arrive in its language | 6–8 h, one hub + one controller release |
|
||||
| 4 | R-559 | console banner (34 lines, console-font limits) and the download page | an English household meets English from the first boot screen (in scope — ruling 1b) | 4–6 h + an ISO train |
|
||||
| 5 | R-560 | catalog: §7 format, controller reads it, 835 strings / ~5 000 words, catalog copy gates per language | an app card, its settings and its first steps in English | 12–16 h |
|
||||
@@ -394,6 +394,68 @@ with both controls.
|
||||
string with no error to carry a key and no language where it is built, so it renders Hungarian on an
|
||||
English page. The copy is in the bundle; only the render is fixed. Named in the code, filed as a row.
|
||||
|
||||
### 10.3 Slice 2 release C — the saved notes, and a globe (v0.254.0). SLICE 2 CLOSED.
|
||||
|
||||
**[DESIGN] A note a background run SAVES is written in the BOX's language at write time** (operator
|
||||
ruling, §16 option 1). ~70 producers. **The consequence, recorded because it is the cost of the
|
||||
choice:** a household that switches language sees the previous run's note in the old language until
|
||||
the next run rewrites it — usually the next night. The alternative (store a code, render live) needs a
|
||||
dozen new persisted fields and a legacy path for each: the R-570 shape a dozen times over.
|
||||
`EndRestoreOp` now receives no Hungarian literal from anywhere.
|
||||
|
||||
**[FACT] A deadlock, introduced and caught by the suite hanging (R-578).** `UpdateOffboxStatus` holds
|
||||
the settings WRITE lock while running its callback; `boxLang()` reads the language through the READ
|
||||
lock; `sync.RWMutex` is not reentrant. A note rendered inside that callback deadlocks **holding the
|
||||
settings lock**, which wedges everything else on the box that touches `settings.json`. The only
|
||||
symptom was `go test` going from 8 minutes to a 25-minute timeout. **The general rule this establishes:
|
||||
a helper that takes a lock must never be called from inside a callback that holds one** — and a hang
|
||||
is the worst symptom to diagnose, which is why R-578 asks for a gate rather than one test in one package.
|
||||
|
||||
**[DESIGN] Decision 6, superseded a second time: the switch is a GLOBE, and a visitor's language is
|
||||
their own.** One answerable sentence: *how does someone who cannot read Hungarian find the way out of
|
||||
Hungarian?* Options: (1) keep two text links („Magyar"/„English") in the sidebar footer — cost: they
|
||||
wrap at the sidebar's width, and finding them means recognising two words as links; (2) one globe, the
|
||||
symbol every web user already reads as "language". **Chosen (2)**, `<details>`/`<summary>` so the menu
|
||||
needs no script and a screen reader announces it. Language names inside are shown in their own
|
||||
language and are never translated. Drawn inline rather than in the icon sprite, because the sprite
|
||||
lives only in `layout.html` and the pages outside the dashboard chrome have their own shell.
|
||||
|
||||
**[DESIGN] Who the page is FOR decides where its globe posts, and getting that wrong makes the button
|
||||
do nothing.**
|
||||
|
||||
| page | reader | globe posts to | their choice lives in |
|
||||
|---|---|---|---|
|
||||
| every dashboard page | the household, signed in | `/settings/language` (session CSRF) | `settings.json` |
|
||||
| `/recovery` — an AUTHENTICATED route | the household, signed in | `/settings/language` | `settings.json` |
|
||||
| `/login`, `/claim` | a visitor, no session | `/lang` (no CSRF) | the `felhom_lang` cookie, their browser |
|
||||
| the two guest share pages, the catch-all | a stranger / nobody | **no globe** | — (R-577, the operator's) |
|
||||
|
||||
`langFor`'s order is fixed: `?lang=` → **the household's setting when a session exists** → the cookie
|
||||
when there is none → the setting → `hu`. **A signed-in household never reads the cookie**, so they
|
||||
cannot inherit a language a previous visitor picked in the same browser. The recovery row above was
|
||||
measured live before it was right: an anonymous form there sets a cookie that `langFor` then ignores,
|
||||
and the button appears to do nothing.
|
||||
|
||||
**[DESIGN] `POST /lang` is CSRF-exempt, for a reason narrow enough to check.** The only achievable
|
||||
effect of a forged request is to change the language of the page the victim's own browser shows them.
|
||||
It writes one display-only cookie, reads nothing, touches no setting, and `safeBackPath` refuses a
|
||||
protocol-relative `//host` as well as an absolute URL — *"starts with `/`"* alone is not the test,
|
||||
because a browser reads `//evil.example` as another origin. **If that handler ever gains a second
|
||||
effect it needs CSRF that day.** That is the anonymous surface `04-control-plane-authorization.md`
|
||||
governs: changing what a visitor reads is within it, changing anything the household owns is not.
|
||||
|
||||
**[DESIGN] §16, the operator's default, taken: a successful CLAIM carries the visitor's language into
|
||||
the household's setting.** Someone who switched the claim page to English and then claimed the box
|
||||
chose English. Only on success, and only there — the one moment an anonymous visitor becomes the
|
||||
household.
|
||||
|
||||
**[FACT] The parity exceptions, measured rather than asserted.** 106 fixtures, a real (LCS) diff
|
||||
against the fixtures as they stood at v0.253.0: **3 change shapes** (the footer; the recovery globe;
|
||||
the login/claim globe) and **5 byte-identical**, which are exactly the pages that must not change.
|
||||
`audits/i18n-slice2-2026-09-18/C/parity-exception-diff.txt`. **A second measurement error worth
|
||||
keeping: the first attempt compared LINE BY INDEX, and an insertion shifts every line below it — it
|
||||
reported 60 520 changed lines and measured nothing. A line-index compare is not a diff.**
|
||||
|
||||
---
|
||||
|
||||
## 11. Operator decisions
|
||||
@@ -414,6 +476,11 @@ These are rulings, not proposals. Anything specced against a different assumptio
|
||||
5. **Mechanism (b2)** — §2.1.
|
||||
6. ~~**Switch hidden while only three pages are English** — §3.~~ **Superseded 2026-09-17** by slice 1
|
||||
release C (v0.250.0): every template converted, switch shown to every household (§3).
|
||||
**Superseded again 2026-09-18** by slice 2 release C (v0.254.0): the switch is a GLOBE, it is on the
|
||||
sign-in and claim pages too, and a visitor's choice lives in their own browser (§10.3).
|
||||
|
||||
8. **A successful claim carries the visitor's language into the household's setting** (§16 default,
|
||||
taken 2026-09-18). Only on success; every other anonymous request leaves `settings.json` alone.
|
||||
|
||||
### 2026-09-17 (evening) — the two open questions, ruled
|
||||
|
||||
|
||||
Reference in New Issue
Block a user