v0.254.0 — the saved notes follow the language, and the switch becomes a globe (R-557 slice 2 release C; SLICE 2 CLOSED)
gates / gates (push) Successful in 23s

The notes a background run SAVES — last night's backup line, the last error, the proof
result, the restore outcome — are written in the BOX's language at the moment they are
written. A household that switches sees the previous run's note in the old language until
the next run rewrites it: the operator's §16 option 1, stated rather than hidden.
EndRestoreOp no longer receives a Hungarian literal from anywhere.

The language switch is a globe. Two text links wrapped in the sidebar footer and asked the
reader to recognise "Magyar"/"English" as links; a globe is the one symbol every web user
already reads as "language", so nobody has to read Hungarian to escape Hungarian. It is
<details>/<summary> — a menu with no script, drawn inline because the icon sprite lives
only in layout.html and the visitor pages have their own shell.

Those visitor pages get the same globe, and a visitor's choice stays theirs: a display-only
felhom_lang cookie that langFor reads ONLY when there is no session. A signed-in household
can never inherit a language a previous visitor picked in the same browser. POST /lang is
CSRF-exempt for a narrow reason written at the exemption — its only achievable effect is the
language of the page the victim's own browser shows them — and safeBackPath refuses
//evil.example as well as https://, because "starts with /" alone is not the test. §16 taken:
a successful claim carries the cookie into the household's setting.

TWO PARITY EXCEPTIONS, MEASURED: 106 fixtures compared with a real diff — exactly two change
shapes (the dashboard footer, the globe in the shells) and 5 byte-identical, which are the
three pages that must not change.

I INTRODUCED A DEADLOCK AND THE SUITE CAUGHT IT BY HANGING. 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 the settings
lock. Fixed by resolving the language before the callback, and guarded by a test that names
the file and line in a second instead of hanging for 25 minutes.

MinAgent: 0.131.0 (unchanged). No hub release needed.

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:19:31 +02:00
parent eb6aa58aa7
commit 48f3336956
142 changed files with 2199 additions and 318 deletions
@@ -31,7 +31,7 @@ import (
// whether the app has data (R-361 destroyed apps' canonical .sql files for four months).
func TestUnitRestoreOutcome_VolumesAndDatabaseNamed(t *testing.T) {
msg := unitRestoreOutcomeMsg("kimai", backup.UnitRestoreResult{
msg := noteServer(t).unitRestoreOutcomeMsg("kimai", backup.UnitRestoreResult{
VolumesReplayed: 2, DBsReplayed: 1, ManifestVolumes: 2, ManifestDBs: 1,
})
for _, want := range []string{"2 adatkötet", "az adatbázis"} {
@@ -48,7 +48,7 @@ func TestUnitRestoreOutcome_VolumesAndDatabaseNamed(t *testing.T) {
}
func TestUnitRestoreOutcome_BackupHeldOnlySettings(t *testing.T) {
msg := unitRestoreOutcomeMsg("opengist", backup.UnitRestoreResult{})
msg := noteServer(t).unitRestoreOutcomeMsg("opengist", backup.UnitRestoreResult{})
for _, want := range []string{"csak a beállításokat tartalmazta", "NEM álltak vissza"} {
if !strings.Contains(msg, want) {
t.Errorf("a restore that returned no data must say so plainly; missing %q in %q", want, msg)
@@ -64,7 +64,7 @@ func TestUnitRestoreOutcome_BackupHeldOnlySettings(t *testing.T) {
}
func TestUnitRestoreOutcome_ManifestListedDataThatDidNotReturn(t *testing.T) {
msg := unitRestoreOutcomeMsg("paperless-ngx", backup.UnitRestoreResult{
msg := noteServer(t).unitRestoreOutcomeMsg("paperless-ngx", backup.UnitRestoreResult{
VolumesReplayed: 0, DBsReplayed: 0, ManifestVolumes: 2, ManifestDBs: 1,
})
for _, want := range []string{"2 adatkötetet", "1 adatbázis-mentést", "változatlanok maradtak"} {
@@ -79,7 +79,7 @@ func TestUnitRestoreOutcome_ManifestListedDataThatDidNotReturn(t *testing.T) {
}
func TestUnitRestoreOutcome_DatabaseOnly(t *testing.T) {
msg := unitRestoreOutcomeMsg("bookstack", backup.UnitRestoreResult{
msg := noteServer(t).unitRestoreOutcomeMsg("bookstack", backup.UnitRestoreResult{
VolumesReplayed: 0, DBsReplayed: 1, ManifestVolumes: 0, ManifestDBs: 1,
})
if !strings.Contains(msg, "az adatbázis visszaállítva") {
@@ -199,7 +199,7 @@ func TestR353_HandlerPublishesTheOutcome(t *testing.T) {
// entire dataset. **An unknown drawn as a zero is the R-88 failure direction**, and it is exactly what
// this whole change exists to remove — so it must not be re-introduced by the fix itself.
func TestUnitRestoreOutcome_NoUnitFallbackSaysUnknownNotEmpty(t *testing.T) {
msg := unitRestoreOutcomeMsg("legacyapp", backup.UnitRestoreResult{CountsUnknown: true})
msg := noteServer(t).unitRestoreOutcomeMsg("legacyapp", backup.UnitRestoreResult{CountsUnknown: true})
if strings.Contains(msg, "csak a beállításokat tartalmazta") {
t.Fatal("FALSE CLAIM: told the customer the backup held no data when the counts were never " +