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
4.2 KiB
Live validation — controller v0.254.0 on demo-hp guest 9201 (2026-09-18, release C)
Method: endpoint-level, stated as such. No browser on DooPlex. Nothing was installed, created,
formatted or deleted, and the deploy endpoint was not touched at all — the new rule
(.claude/rules/live-probes.md) written after yesterday's mistake. The password was passed as a file
and deleted from host and guest afterwards.
1. A visitor's language is their own (no session) — before and after
| probe | 0.253.0 | 0.254.0 |
|---|---|---|
globe on /login |
0 | 1 |
POST /lang lang=en back=/login |
302 (no such route → the login redirect) | 303, felhom_lang=en set |
/login with that cookie |
<html lang="hu"> |
<html lang="en">, and "Forgot password" present |
settings.json language after it |
"hu" |
"hu" — unchanged. An anonymous request cannot write what the household owns |
POST /lang lang=xx |
302 | 400, no cookie |
POST /lang back=//evil.example/x |
the login redirect | / — a protocol-relative URL is another origin |
globe on /claim |
0 | 1 |
2. A signed-in household reads their own setting, never the cookie
/launcher with a session and the felhom_lang=en cookie → <html lang="hu">. The cookie is
not read once there is a session; that is the row that keeps a household from inheriting a language a
previous visitor picked in the same browser.
3. The dashboard globe, end to end
POST /settings/language lang=en (session CSRF) → 302 → settings.json "language": "en" →
/launcher, /backups, /backups/remote all <html lang="en"> → switched back → "hu".
The footer renders in order — version, globe, sign-out — and the old two-text-link switch is gone
(lang-switch-btn count 1 → 0):
<div class="sidebar-footer">
<span class="version">0.254.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" …globe…/></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language">…<button … class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language">…<button … class="lang-globe-item">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
4. The saved note, and the §16 consequence, seen live
With the box switched to English, the stored tier-2 note stayed as it was written:
"last_warning": "A belső SSD-n csak a konfiguráció, adatbázis és a köte…". That is the operator's
choice working, not a bug: a saved note is written in the box's language at write time and shown
verbatim afterwards, so a household that switches sees the previous run's note in the old language
until the next run rewrites it.
5. What could NOT be proven live, and why
The /recovery globe. recoveryPageHandler redirects to /backups/remote unless
recoveryOffer() is true — the page exists only while a box is in a lost-machine situation, and
demo-hp is not. It 302s with or without a session. So its globe is proven by the render tests
(TestGlobeOnAnonymousShells, TestI18nDirectRenderPagesFollowLanguage) and by its 7 parity
fixtures, not live. Said plainly rather than left to be assumed from the other rows.
That page is also where release C's own defect was: it is an AUTHENTICATED route, so its globe must
write the household's SETTING; the first draft gave it the anonymous form, which sets a cookie
langFor ignores once there is a session — the button would have done nothing. Found by this probe
reporting no globe on /recovery and then reading the route table.
The <details> menu opening in a browser. claude-in-chrome is not available on DooPlex. The
markup is asserted; whether it looks and behaves right on screen is a manual click-through.
6. State left behind
Controller 0.254.0; saved language hu; the visitor cookie was set only inside the probe's
own curl jar and is gone with it. 23 standing containers up, unchanged. Nothing installed, nothing
removed, no drive touched, no floor raised, no golden baked. Provisioned nothing.