v0.316.0 shipped it dead. Every shell asked for `?v={{.AssetTag}}`, TestAssetTag_EveryShellUsesIt
passed, and the first live load through the public address rendered `/static/style.css?v=` —
EMPTY. addShellAssetData was called only by executeTemplateLang (the sign-in, claim, recovery,
guest-share and catch-all pages). The dashboard path had never needed it, because layout.html used
to ask for {{.Version}}, which baseData supplies; moving it to {{.AssetTag}} left nothing to
supply the value.
An empty buster is a CONSTANT. It would never have changed again — worse than the per-version one
it replaced, and invisible to a test that only checks the markup. executeTemplate now calls the
same function executeTemplateLang does, which is literally what the v0.255.0 comment above that
call asked for: "setting it at the single choke point every shell renders through means a new
shell cannot miss it". There were two choke points.
TestAssetTag_ReachesTheRenderedPage renders six real URLs through the production router and asserts
the buster is non-empty, 8 hex, and not a version. Red-proved by removing the call again: four
pages fail naming the empty value.
A PATCH VERSION, not a second push of 0.316.0 — the rule this release added, applied to its own
mistake rather than around it.
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
forwarded.yml: felhom-forwarded is now a chain [felhom-https-only (redirectScheme https, 301 when the trusted tunnel
says X-Forwarded-Proto: http), felhom-forwarded-headers (the clean-up + HSTS 86400, no subdomains, no preload)] plus a
priority-1 catch-all router for unknown names over http. traefik.yml unchanged; hot-loaded.
Tests red-proofed. Evidence: felhom.eu documentation/audits/https-only-2026-10-14/.
Security review of 8d862e4: compose's shared runner logs a failed command's stdout and puts its
stderr in the error, and that error became the event's `problem`. The after_unclean_boot commands now
run through a quiet runner (nothing logged, stderr discarded) and `problem` keeps the exit code only.
TestAUB_AFailingCommandLeaksNoOutput, red-proofed.
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
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 demo-hp 2026-10-10 (adventurelog x4, bookstack x4). The synthesised run skips IsUndo files; only undo copies
-> no synthesised run. Two tests, both red-proved. v0.306.0 (R-570) was built but never delivered; 0.307.0 carries it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Fleet condition read on each box's settings.json 2026-10-10 (offbox.last_success ~02:16Z on 0.304.0).
Red-proof: the new no-kind row of TestR553_StaleNoteDecisionSurvivesWordingChange failed against the fallback.
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
22 Go-literal messages (escrow handler, share password page, export
upload, network-storage uid/remove/attach failures) are bundle keys with
te-form Hungarian and English; a detached attach failure renders in the
reader's language. The NAS refusal says „Válassz". Setup wizard,
recovery-info.txt (R-554) and the SMART/fill-watch wire texts are left.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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