Biztonsagi mentes was still a card — it sits directly in the tab, not inside a section, so the
.app-section scoping rule did not reach it. It is a section now.
The 2nd-copy heading still read '2. mentes beallitasa — <app>', because the locale key carries the
app name: it was a PAGE title, and a tab already says which app it is. New key
app_page.second_backup ('2. mentes' / 'Second backup'). This is the leftover the task named.
Found by looking at the rendered page, not the markup.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
The fields on Beallitasok are the install wizard's markup, which carries that page's card around
the whole form and one around every generated value. On a TAB those are sections, so the boxes come
off; the input itself is still a field, which is what tells the household it holds a value. Scoped
to .app-section so the install page — same column, its own form — is untouched.
Found in a browser by asking the page which elements still paint a background, rather than reading
the markup and guessing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
v0.315.0 worked and looked, in the operator's words, messy: boxes of different widths stacked down
the page, with the same card shape around things you act on and things you only read. The mockup
approved on 2026-10-15 replaces it, and each of its three rules has a reason:
ONE COLUMN OF ONE WIDTH (820px). Differing widths read as a page assembled by accident.
SECTIONS ARE HAIRLINES, NOT CARDS. A card says "this is a thing you act on"; most of an app page
is a thing you READ, so a card there is a promise the content cannot keep.
A BOX MEANS SOMETHING. Exactly two remain: blue when the app is waiting for the household,
amber when something is risky. The exception-colour principle applied to shape as well.
What moved:
- the hero loses its box: a 64px logo tile, the name at 26px, the tagline, then hairline chips
(state, lifecycle, update) and meta text (the address, the last automatic update);
- the tabs are links with a 2px blue underline under the open one — no pills, no fills;
- sections are `padding: 28px 0` with a bottom hairline; rows are a 200px label column so every
value on the page starts at the same x, and stack under 720px;
- the after-start callout is now the FIRST thing on Beallitasok. A household opening a Plex that
wants its claim code should meet the code box, not a Restart button;
- "2. mentes beallitasa — <app>" and "<app> — Exportalas" are gone: they were the headings of
pages that no longer exist, on a tab that already says which app this is;
- the settings tab gains "Cim es kulcsok" with one address row, and the disabled subdomain input
that said the same thing is skipped for an installed app (the install wizard still asks);
- the "this app is already installed, the settings are read-only" BANNER becomes the hint under
the fields it is about;
- the install wizard gets the same back link, hero and column, so pressing Telepites does not
land the household on a page that looks like a different product.
THE AUTO-UPDATE SUCCESS BANNER IS GONE. It was a full-width blue box above the tabs, level with
"the update failed" and "your files are missing". A success that asked nothing of the household is
a date, and it is now meta text in the hero. Every banner that DOES need a decision — a failed
update, a hold, lost files, a missing drive, an interrupted install — is untouched, above the tabs,
exactly where R-571 puts it.
Tests: TestAppPage_OneColumnLayout (one column, one aria-current, no card-shaped section, no
success banner), TestAppPage_AutoUpdateSuccessIsMetaNotBanner, TestAppPage_CalloutsOnlyForAskAndWarn
(one blue callout on a waiting app and it comes first; one amber on a same-disk backup; none on the
good case). Four red-proofs recorded in REPORT.
app_row_dedup_gate convicted `.app-row`, correctly: that class already means "a row in the app
list". The app page's label/value row is `.app-field-row`. Restoring the list's own rule after an
over-broad rename was part of the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
MEASURED FIRST (audits/app-page-2026-10-15b/P0/VERDICT.md). The tab bar rendered unstyled through
the public address for one reason, and it was not the box: exactly one guest runs 0.315.0 and it
runs the registry's current digest (8887343011d6, identical to the registry's). The floor is still
0.314.0, so nothing else could have taken it.
What it was, in four measured facts:
1. the stylesheet URL is /static/style.css?v={{.Version}} -> ?v=0.315.0 for all THREE different
0.315.0 builds (e66d466, fe0228c, db52f5a), the first of which had no .app-tabs rules at all;
2. the origin sends NO validator at all (ETag present = 0, Last-Modified present = 0), so a
cached copy cannot be revalidated - it can only expire;
3. Cloudflare caches that URL (cf-cache-status HIT, age climbing) and rewrites the browser TTL
from the origin's 3600 to 14400 - four hours;
4. the session's first request returned cf-cache-status EXPIRED, which only happens when the edge
holds an entry that has aged out.
Origin, edge and box now agree byte-for-byte. The stale object itself is gone - that first probe
forced its revalidation - and the REPORT says so rather than claiming a HIT nobody can reproduce.
THE FIX IS IN TWO PARTS, NEITHER OF WHICH IS PURGING A CACHE:
build.sh refuses to push a version that already exists, before building, for --push and
--multiarch. A registry that cannot be asked is a refusal too: proving the tag is new is the point,
and "I could not ask" is not that proof. Local builds are untouched - reusing a version while
iterating is normal. scripts/test_build_push_once.sh drives the real build.sh with a fake docker on
PATH over six cases; red-proved twice (check deleted -> a present tag builds and pushes; an
unreachable registry treated as absent -> two cases fail).
assetTag() now hashes version + GitCommit + BuildTime, and EVERY shell's stylesheet link uses it
(seven of the nine asked for ?v={{.Version}}; only the two stranger-facing share pages already had
it). It stays opaque and 8 hex, because the share pages are reached by a capability URL and naming
the build to whoever holds one is a gift to somebody hunting a known fault. Cache-Control is
unchanged: the new address is the fix, not a shorter cache.
The parity harness normalises the asset tag on BOTH sides of the comparison, as it already does for
the per-session CSRF token, so all 148 fixtures stay byte-identical on disk and stop depending on
which build rendered them. The same normalisation is applied at the second comparison site in
i18n_wiring_test.go, which had the same assumption.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
FOUND IN A BROWSER. The log tab's body was lifted out of the old logs.html by line range and the
range stopped one line short, so the closing </script> never came with it. Everything downstream
agreed the page was fine:
- html/template parsed it,
- the page rendered and looked right,
- TestAppPage_EachTabRendersItsOwnContent found `setInterval(window.fetchLogs` exactly where it
expected it, because the TEXT was there.
Chrome refused the whole block with "SyntaxError: Unexpected token '<'" at the `</div>` that
followed, so the live log never refreshed. A marker proves a string is present; it does not prove
the browser will execute it.
TestAppPage_ScriptTagsAreBalanced now counts <script> against </script> on all seven app-page URLs
and checks the last block is closed. Red-proved by removing the tag again: it fails naming the logs
tab, 3 opened and 2 closed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
FOUND IN A BROWSER, and findable no other way. Every endpoint-level marker passed and the page was
unusable: the tab bar rendered as four bare underlined links run together, and the app logo filled a
third of the screen. The shell had been written with new class names — app-hero, app-hero-logo,
app-tabs — and only the first two of those had any CSS at all, under the names the design system
already uses.
- the shell now reuses .app-info-hero / .app-info-logo / .app-info-hero-text, which carry the
80px logo box, the hairline border and the 2px radius. Inventing a parallel name is what left
the logo unsized; REUSE.md's first rule, learned again.
- .app-tabs / .app-tab / .app-tab-active are genuinely new and are now styled to design system
v2: 2px radius, hairline --line border, no shadow, --text-2 for an inactive tab and
--blue-bright only for the one the household is on (blue marks where you are, not a deviation),
the active tab sitting on the bar's own line, and a narrower gutter under 640px.
- .after-start-card gets a blue left hairline — it is a request, not a fault, so no amber.
- .app-faq gets its dt/dd rules.
TestAppPage_DeployedDefaultsToSettings sliced the header at `class="app-hero"`; it now slices at the
reused class and fails loudly if either marker is gone, instead of slicing on -1. The 36 app-page
parity fixtures are recaptured — and no other fixture moved, which is the evidence that the shell
change reaches only the app page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
An app was six pages — details, install/settings, logs, export, the per-app backup panel and a
details card for moving data — and every "Vissza" on them went to the app LIST, so there was no way
back to the app you were looking at. It is now ONE page with four tab URLs under /apps/{slug}/,
one shared header and one set of banners.
settings Mukodes (Start/Restart/Stop, Stop confirms) + the cards that want something from the
household + the install-time and optional settings. The default for an installed app,
RENDERED at the bare URL and never redirected to.
data the data folders, move to another drive, the nightly copy, the 2nd-copy setting, export
guide tagline, screenshots, use cases, first steps, the catalog's guide, the FAQ, the docs
link — and the WHOLE page for an app that is not installed
logs the log, and the only tab whose poller runs
The tabs are links, not panels: a link in an alert has to land ON a tab, and the log poller must not
run on the other three. Start/Restart/Stop left the page header, where they read as menu buttons.
The old /stacks/{name}/{deploy,logs,export,backup} GETs answer 302 — never 301, which a browser
caches beyond anyone's reach — to the tab that now holds them, query string carried through, with
name->slug resolved through the stack list rather than assumed equal. Every POST on those paths is
untouched: the backup form, the app-email toggle and the reveal endpoints keep their URLs.
A value asked AFTER the app starts is now generic (D2). The catalog marks a field `after_start: true`
and supplies its label; the ACTION is Go, in a registry keyed by slug + env var. The catalog can
never declare what runs — a declared field with no registered action renders nothing, POSTs 404 and
logs one WARN with the env var KEY, never the value. A red-proof found the second half of that guard
unpinned: deleting the "the catalog must have DECLARED this field" check left the suite green, and a
POST for an undeclared field then reached Plex and opened the setup gate.
TestAfterStart_RegistryAloneIsNotAnInvitation pins it.
New catalog block app_info.guide (sections + faq + checked_version), parsed, localised field by
field and rendered with the household's own address filled in. It renders only when checked_version
names the image tag its author clicked through. No template carries one yet.
The Pi and "csak x86" badges are gone; PiCompatible and the catalog key stay this release, or a box
on 0.314.0 would read "csak x86" on every app (R-1067).
template_id_gate.py now follows {{template}} includes, with a scope decoy proving the walk did not
become vacuous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
New template block after_unclean_boot (decision 236): once per controller start that followed an
UNCLEAN host boot (crashboot's own verdict, after the 15-minute crash grace; a normal or unknown boot
never runs it), each app with the block rebuilds ONLY its zero-byte files (scan: regular files, no
symlinks, its own filesystem; ids appended to the app's own command in batches; one retry, then
reported) and its own lost-file check is read. The household sees lost files (name, date taken) on
the app page until dismissed; the operator gets app_repaired_after_crash with counts only. Skips,
with the reason, an unproven image, an unknown key, an app not running. MinAgent 0.155.0 unchanged.
Tests red-proofed (14 guards). One new parity fixture (the new card only).
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
POST /api/server/poweroff (dashboard login, POST only, the typed word KIKAPCSOLÁS / TURN OFF)
→ agent POST /host/poweroff (MinAgent 0.155.0; hidden for an older agent). The controller refuses
during its own backup/restore/app update; the agent's reasons are shown as household sentences.
„Kiszolgáló újraindítása" becomes „A Felhom újraindítása" and says it restarts the Felhom part;
/api/server/reboot is POST only (R-983). Tests red-proofed; parity fixtures regenerated on purpose.
Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before
release. A box whose first ingestion failed now serves one bilingual waiting page and retries every
minute instead of the wizard. Own commit so the lead can hold it back.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Operator rulings 2026-10-12, 09 §3 decisions 196, 204-207, 223, 227. MinAgent 0.131.0 unchanged.
R-255's sweep found one leak, fixed here: an optional-config secret_input was rendered into its
settings page; it is now revealed on demand. Hungarian parity fixtures re-captured once for R-562.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Seen on scratch 9202 with the R-906 build: once the real banner warnings showed (instead of an empty
overflow line), the link ran 90 px past a 390 px screen and rendered browser-default blue. Phone block:
.alert-banner wraps, .alert-message breaks long paths, .alert-link on its own line; .alert-link color: inherit.
Pinned in TestR907_PageHeaderWrapsOnAPhone (red against d5f2e47's CSS). No Hungarian string changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
- Apps card: title row (logo top-aligned, name, address cut with an ellipsis and full in title) over a wrapping
.stack-tags row; the tags no longer share a space-between row with the name.
- Phone (768px block): .page-header wraps; the share button drops to its own line, text whole.
- GetBannerAlerts(page, lang): the layout's filter before the cap; baseData and /monitoring use it.
- Tests: TestStackCardHeaderLayout, TestR907_PageHeaderWrapsOnAPhone, TestR906_* (all red against 05e12921).
- No Hungarian string changed. The three stacks parity fixtures are re-captured in the next commit
(this commit alone fails TestI18nParity on them; both are pushed together).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
The format wizard rendered the agent's initialize list as-is, and the
agent deliberately allows re-initialising Felhom's own drives, so a
registered data drive was offered for formatting. The controller proxy
now drops every candidate that backs a registered storage path (joined
through the guest mount table) from both lists; an unreadable mount
table empties initialize.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
The command form ran only when the lock was SET, so a database switch closed by
`command` stayed closed through the household's 15-minute window. New twin
fields `open_command` + `open_success` (same service/user/args_env and the same
argv-safe expansion and success-marker rules as `command`/`success`).
- liftNativeLock (the window, via OpenSignupWindow → goNativeLock(false)): marks
the gate record native_lock "opening" BEFORE anything opens, lifts the env,
runs open_command; only full success records "lifted". A failed open closes
the switch again at once and records after_setup {ok: false, step: open}; the
app page shows its own line (app_info.signup_native_open_failed).
- The close: reconcileSignupBlocks (every 20 s and at controller start) runs
`command` for any non-"applied" state once no window runs. A failed close
after a window is logged ERROR ("may still be OPEN past the household's
window") and retried every nativeLockOpenRetry (2 min) instead of 30.
- After a successful app update, verifyAndConclude → markNativeLockForReapply
sets native_lock "" so the loop closes the switch again.
- A template with `command` but no `open_command` keeps today's window (env
only) and logs once per app that its own switch cannot be reopened.
Tests: internal/stacks/after_setup_r717_test.go (7), web render + parity case.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
A held app's page now says, inside the hold panel, that the app's own log from
before it was stopped was kept, with a Napló link to the logs page that serves
it (0b93e1a). Shown only for an update hold with a non-empty kept log
(Manager.HoldLogKept). New copy through the bundle (hu + en), parity case
app_info_hold_log captured when born.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Where the app has its own switch (after_setup.env), both presses restart
it; the cards now say so (app_info.restarts_once, hu+en). Two parity
cases born with the line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Item 1: the always-visible 'Debug' menu item reads 'Hibakeresés' in
Hungarian (key layout.hibakereses; English stays 'Debug'). Item 4: four
formal forms on the storage pages (Adjon hozzá, Csatlakoztasson,
Válassza ki x2, Önnek) are now te-form. The parity fixtures change by
exactly these bytes (a deliberate copy change, not a regeneration);
HU_FORMAL_CEILING 17 -> 14. Item 2 was already fixed (slice 1).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Since v0.292.0 (R-841) cloudflared's health check asks /ready, so a lost
connection reads unhealthy; the cloudflared row now also carries the tag
"Kivulrol nem erheto el" with a plain title, in hu+en. New parity case
dashboard_tunnel_offline (born as a key).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
POST /backup/offbox/clear (reveal-then-confirm) forgets the target and deletes ssh_key + known_hosts;
nothing on the target is touched. repo_password is kept whenever anything could depend on it (hub
sealed package — the R-241 rule; escrowed; a successful run; snapshots) and deleted only otherwise.
Refused for the Felhom tier (rclone-pinned), while the single-flight is held, and while an
abandonment countdown runs. i18n parity fixtures of backups_remote gain the additive block only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
- internal/family: the family list (bcrypt, generated 4x4 passwords shown once) + 30-day sessions in family.json
(0600, atomic); a reset (generation), a removal or a logout ends sessions at the next request.
- internal/stacks/family_gate.go: family_gate / family_gate_except / min_controller in .felhom.yml; the door is written
BEFORE the first start (install and a removed app's restore), a life record in app.yaml, reconciled by the gate loop;
priority below the install hold, setup gate and sign-up block; every exception anchored ^/prefix(/|$) (finding F1).
- internal/web/family_gate.go: forwardAuth /__felhom_gate/family (app cookie felhom_famgate, host-only, names a store
session); /__family/start|login|logout on the dashboard host (session cookie felhom_family, Path=/__family);
sign-in counted per visitor (clientIP) AND per name, short windows; the household's dashboard session vouches.
RequireAuth never reads a family cookie. The "Család" card on the security page: add / new password / remove.
Red-proofs RP-F1..RP-F7 (felhom.eu audits/family-gate-2026-10-02/A/).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Image retention: after a done/undone guarded Update and at remove, an app's images older than its running
and previous one are deleted — never an image any container, installed compose or installed/previous record
names (box-wide keep set read at delete time); exact id, never forced or pruned; paused while any update runs;
a one-time sweep of catalog app images at the first start. Install hold: an after_install app is installed
behind the setup gate's door and opens when after_install succeeds or the household says it changed the login.
Tests TestImageRetention_* and TestInstallHold_* with red-proofs; parity fixture for the held card.
MinAgent: 0.131.0 (unchanged).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
The unit's data files are stamped with the versions that wrote them; the capture keeps the
definition the data belongs to; a restore never starts data under another version's
definition (unit restores refuse a mismatch; the off-site restore writes the snapshot's
definition); every tier's time is its data's; the conversion-copy release needs a dump on
the new engine. File-browser sync single-flight + no empty kept folder (R-695); the kept
view joins the folder's owning group, language switch resyncs (R-691); a restore-generated
login is not shown as the password (R-694). Red-proofs in
felhom.eu/documentation/audits/version-travel-2026-09-26/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
An install over an app's kept drive folder (appdata/<app> non-empty) asks the household:
"use my kept data" (a load from the newest copy of THIS drive's install, own unit or
second-drive mirror, then the template's after_load) or "start fresh" (the folder is
renamed into <drive>/kept/<app>/<date>/ with the removed app's unit; nothing deleted).
The install API answers 409 kept_data_choice until one is chosen; DeployStack refuses
too. New page Megorzott adatok / Kept data (/kept-data): Load / Look / Delete (typed
confirmation, the only deletion of kept data). FileBrowser gets a read-only source.
The drive-full warning names the kept folders. <drive>/kept is protected and outside
every backup leg.
R-690: the removed-app restore (R-487) never found a unit on a DATA drive — it asked
GetStackComposePath (true for every catalog app) and restored nextcloud with no env.
Now isStackDeployed; pinned with a production-shaped provider.
Red-proofs: audits/night-2026-09-26/E/redproofs/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
R-650: internal/dockerexec — every docker exec routed through it; under
go test a real docker is refused (opt-in FELHOM_TEST_REAL_DOCKER=1; a stub
under the temp dir is allowed). api/stacks/web tests run under a silent
stub (TestMain). TestR650_NoBareDockerExec pins it repo-wide.
R-640: a dump without its engine's completion marker is refused before
the first mutation (unit + off-site restore) and again before any load.
R-499: the Tier-2 page's system-disk sentence has four true branches.
R-518: the backup button states the measured ~8 min stop.
R-626: measured on 9202, not reproduced.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
app_update_undone / app_update_held events (09 decision 15), on by
default and seeded once on existing boxes; R-606 update sentences as
key+args rendered per reader; R-646 startup applied-meta backfill for
apps current with the catalog; R-620 a disabled notifier WARNs once per
event type. Needs hub v0.120.0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
The guarded update gains a folder copy of the app's named volumes, taken
after the pull where the app stops anyway (decision 19, chosen by the
2026-09-23 bake-off). On a failed health check the box undoes: every copy
validated by its finished-marker first, volumes refilled, definition and pin
from the job's own pre-update copies, the old version checked with the OLD
.felhom.yml probe. It holds only if the undo fails, and the hold sentence
says so and what state the data is in. Bind-mounted folders are never
touched.
- R-637 built; R-638/R-640/R-641 do not arise with a folder copy; R-639
(pre-update copies incl. .felhom.yml kept until the undo is over).
- journal phases copying/undoing with power-cut recovery.
- app.yaml last_update_undone + one line on the app page (hu/en).
- R-642: start/restart never answer "completed".
- Removal deletes kept undo copies.
MinAgent unchanged (0.131.0). Nine red-proofs in REPORT.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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 <details> — 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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
- backups_remote.html: the status stat carries data-status="{{.Offbox.LastStatus}}"; the poll reads
v.dataset.status === 'running' instead of v.textContent.indexOf('Fut'). The English page could never
see a running backup before this (slice 1 shipped the English page with that word left Hungarian).
- The running label becomes {{T "backups_remote.fut"}} — hu „Fut…" unchanged, en „Running…".
- 12 backups_remote parity fixtures re-captured. Proven (audit switch/fixture diff): each differs from
its predecessor ONLY by the data-status attribute and that one poll line; the other 94 are
byte-identical. The displayed Hungarian is unchanged.
- Tests: TestR563_PollStartsFromAttribute (hu and en), TestR563_AttributeFollowsTheStatus.
Red-proofed twice: poll back on textContent → both languages fail; word back inline → the English
page shows Hungarian.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
- 11 templates converted (storage, storage_network, storage_init, storage_attach, sharing, login,
claim, launcher_shared, launcher_share_password, catchall, debug); every key translated.
Six ASCII-only Hungarian JS fragments the extractor missed were found by eye and converted by hand
(", majd a(z)", "FIGYELEM:", "jelenlegi:", "mp", "p", " db").
- login, claim, both guest share pages and the catch-all render through executeTemplateLang (the
household language; no session CSRF, no escrow reminder). renderLogin now takes the request.
- Page titles: TitleKey for storage, network storage, the two drive wizards and sharing.
TestHandlerTitleKeysMatchHungarianTitle pins handler literal == hu.json value for every TitleKey.
- Tests: TestDirectRenderHandlersFollowLanguage (real routes, en + hu),
TestI18nDirectRenderPagesHaveNoAdminChrome (escrow reminder due; none on the guest page).
- Fixture correction: the release C cases had invented page titles; the cases now carry the handlers'
real titles (and the wizard pages their real page name), and those 12 fixtures were RE-CAPTURED from
the unconverted templates at 0555091. The diff to the old fixtures is the <title> line (and the nav
highlight on the two wizard pages) only.
- i18n gate: HU_FORMAL_CEILING 12 -> 16 — four more „ön" forms in the converted copy, unchanged by rule (R-516).
Hungarian: byte-identical (TestI18nParity against fixtures from unconverted templates).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Seven backup pages + the restore-progress JS converted against fixtures captured unconverted
(f8ebc47); recovery renders through executeTemplateLang; English retrieval claims registered;
the Fut comparison left unconverted (R-563).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS