Files
felhom-controller/REPORT-app-page-2026-10-15.md
T
admin a86c864b06
gates / gates (push) Successful in 50s
REPORT-app-page-2026-10-15.md: the app-page arc, in a file a second session cannot overwrite
REPORT.md is per-session and overwritten, and tonight two sessions released this repo within an
hour — the second (v0.317.0, the storage work) took it. This sibling holds the app-page arc:
Part 0's measurement and its caveat, what shipped, every red-proof, the three incidents, what is
deployed and what must never be, the browser check, and what is still owed.

The convention is felhom.eu/CLAUDE.md's, for exactly this case; this is the first use of it in
this repo. Whether REPORT.md should stay a single overwritten file in a repo two sessions can
release is a question for the operator, and is left as one rather than answered by a change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
2026-10-15 21:48:19 +02:00

14 KiB
Raw Blame History

REPORT — the app page in one column, and one image per version number (2026-10-15)

A non-overwritten sibling of REPORT.md. Two sessions released this repo on the same evening; REPORT.md is per-session and overwritten, and it now holds v0.317.0's (the storage work). This file holds the app-page arc so it stops being clobberable — the convention felhom.eu/CLAUDE.md prescribes for parallel sessions, applied here.

1. Baselines

Repo main at start Version at start
felhom-controller 8339206 v0.315.0
felhom.eu 340804f9 docs only

Architecture read before any claim: 02-controller-module-map.md „The app page", 09-update-architecture.md, the felhom-ui-design skill, CLAUDE.md + .claude/rules/*. Edited on the Windows workstation; built and tested on DooPlex in an isolated clone; the browser work is on the workstation, which is why CC runs here.

2. PART 0 — the diagnosis, before any fix

Verdict: (a) a cache. Evidence: felhom.eu/documentation/audits/app-page-2026-10-15b/P0/VERDICT.md.

(b) is ruled out. Exactly one guest ran 0.315.0 — demo-hp 9201 — on digest 8887343011d6, identical to the registry's 0.315.0 at the time of measuring. The global floor was 0.314.0 (read from the hub's /configuration), so no box could have taken 0.315.0 by itself. demo-hp 9202 and felhom-pve (N100) 9201 were both on 0.314.0; Tester 1 (c11-appliance) was unreachable and, by the floor, cannot have had it.

The mechanism, in four measured facts:

  1. layout.html asked for /static/style.css?v={{.Version}} → ?v=0.315.0 for all three different 0.315.0 builds (e66d466, fe0228c, db52f5a); the first had no .app-tabs rules.
  2. The origin sends no validator: on the box, ETag present = 0, Last-Modified present = 0. A cached copy cannot be revalidated — only expire.
  3. Cloudflare caches that URL (cf-cache-status: HIT, age: 99 → 100 over three requests) 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 past its TTL.

The caveat, stated rather than glossed: origin, edge and box now agree byte-for-byte (sha256 c1774f63…) and all carry the rules. The stale object itself is no longer observable — that first probe forced its revalidation, which is what EXPIRED records — so no HIT with a bad body can be produced. What is shown is every precondition, the proof an aged entry existed, and the elimination of the only other candidate. No cache was purged by hand.

3. What shipped

Area What
Push-once build.sh refuses --push/--multiarch for a tag already in the registry, before building. A registry it cannot reach is a refusal too. Local builds untouched.
Clean tree build.sh also refuses a tree that is dirty, not at origin/main, or not a git checkout. Added after it was needed — see §6.
Asset tag assetTag() hashes version + GitCommit + BuildTime; all nine shells use it (seven asked for ?v={{.Version}}). Opaque 8 hex, because the stranger-facing share pages use the same value. Cache-Control unchanged — the new address is the fix.
Layout The approved one-column mockup: 820px column, hairline sections, underlined tabs, no hero box, 200px label grid. Two boxes only: blue „the app is waiting for you", amber „this is risky".
Banner The auto-update SUCCESS banner is meta text in the hero. Every banner that needs a decision is untouched above the tabs (R-571).
Leftovers „2. mentés beállítása — " and „ — Exportálás" removed; „Cím és kulcsok" with one address row replaces the duplicate disabled subdomain field; the „already installed, read-only" banner is now the hint under the fields; the install wizard gets the same back link, hero and column.

4. Commits on main

9cc7ffc push-once + per-build asset tag · 7f3e58f the one-column layout · 48d8674 v0.316.0 CHANGELOG + the rule-file fact · 006ceab v0.316.1 (the tag reaches the dashboard) · f908eee settings tab de-boxed · 9e68e52 two data-tab leftovers · 8c7aa10 v0.316.3 CHANGELOG · f9151f0 the padding fix · 4261a38 FELHOM_REPO_DIR + the build names its tree · c6a5671 v0.316.4 the clean-tree gate · e7b64af workspace/ gitignored. felhom.eu: b6638ac4 (design docs + the audit), 22cd7c51 (R-1080 closed, R-1081 filed).

5. Tests and red-proofs — every break seen failing, then reverted

# Break Result
B1 the registry check deleted 5 of 6 cases fail; a present tag builds AND pushes
B2 an unreachable registry treated as absent the two unanswered-registry cases fail
B3 the clean-tree gate deleted all four of its cases fail
C1 assetTag from the version only TestAssetTag_ChangesPerBuild fails on both same-version cases
C2 one shell back on ?v={{.Version}} TestAssetTag_EveryShellUsesIt fails naming layout.html
C3 addShellAssetData removed from executeTemplate TestAssetTag_ReachesTheRenderedPage: empty buster on four pages
D1 the success banner restored TestAppPage_AutoUpdateSuccessIsMetaNotBanner fails
D2 one tab loses its column TestAppPage_OneColumnLayout fails naming the tab
D3 a section drawn as a card again same test fails on two tabs
E an informational section drawn as a blue callout TestAppPage_CalloutsOnlyForAskAndWarn fails on both counts

New: scripts/test_build_push_once.sh (ten cases, driving the real build.sh with a fake docker on PATH — nothing is ever pushed), TestAssetTag_{ChangesPerBuild,EveryShellUsesIt,ReachesTheRenderedPage}, TestAppPage_{OneColumnLayout,AutoUpdateSuccessIsMetaNotBanner,CalloutsOnlyForAskAndWarn}.

Full gate: go build ./... && go vet ./... && go test ./... → exit 0, 37 packages ok, zero failures, read from go test's own exit code, not a pipeline's. All controller gates OK; 53 decoys behaved.

6. THREE INCIDENTS, one shape

All three are the same defect: a guard that is not on the path the work actually takes. Two of them I caused while fixing the first.

(i) I re-pushed 0.315.0 a fourth time. The push-once guard went into controller/build.sh in the repo; the documented release command runs $FELHOM_ROOT/build/felhom-controller/build.sh, which was a separate copy taken 2026-07-18 with no guard. The first thing I did with the new refusal — run it against a real existing tag to prove it fires — therefore rebuilt and re-pushed 0.315.0 with v0.316.0's code under it (8887343011d6 → b809201841f7). Root cause fixed: the build directory's script is a symlink to the repo's, the stale copy kept as build.sh.stale-copy-2026-07-18, and the refusal then proven from the documented path. R-1081.

(ii) The asset tag shipped dead in 0.316.0. Every shell asked for ?v={{.AssetTag}} and the template test passed, but addShellAssetData was only ever called by executeTemplateLang — the stranger-facing pages. The dashboard path had never needed it, because layout.html used to ask for {{.Version}}, which baseData supplies. The first live load rendered /static/style.css?v= — empty, which is a constant, and a constant busts nothing. Found in a browser. Fixed in 0.316.1; TestAssetTag_ReachesTheRenderedPage now asserts the rendered value, not the markup.

(iii) Building 0.316.3 published another session's unreleased work. The symlink from (i) points into the shared clone's working copy, which predated the FELHOM_REPO_DIR override — so the override was silently ignored and the build copied the shared tree, which held 31 uncommitted files of the physical-disk storage work. The banner said Commit: 0225255 with no Repo: line; the evidence was on screen and the image was already pushed. Nobody deployed it — that was luck, not a control. The clean-tree rule had existed only as a sentence in a runbook. It is now two questions the build asks and refuses on, plus a refusal for a source that is not a git checkout at all; it immediately caught its own next build (the scratch workspace/ beside the script, now gitignored).

0.316.3 must never be deployed. It is neither re-pushed nor deleted: a version number is pushed once, and that applies to a number I spoiled.

7. Deployed, and what happened to 0.316.4

demo-hp guest 9201 runs 0.317.0, healthy. That is the other session's release, built from main at a commit that contains all of this work — verified: f9151f0 (the padding fix) and c6a5671 (the clean-tree gate) are both ancestors of it.

0.316.4 was built but never published, and was then dropped. Its first build attempt was refused by its own new clean-tree gate (the workspace/ directory); by the time that was fixed, 0.317.0 was already in the registry. Publishing 0.316.4 would have put a lower number in the registry carrying nothing 0.317.0 does not already have. The household's button offers the highest tag, so it would have been invisible anyway.

The floor stays 0.314.0 — raising it needs all three test boxes and a green box walk, and this evening two sessions released the same component.

tag state
0.316.0 / .1 / .2 published, superseded
0.316.3 published, CONTAMINATED — never deploy
0.316.4 never published (superseded)
0.317.0 published, deployed on demo-hp, carries this work

8. The browser check, through the PUBLIC address

Chrome on the workstation, https://felhom.enkisfelhom.hu, normal loads — no hard refresh:

  • The stylesheet URL is a real 8-hex per-build tag and the loaded sheet carries the .app-tabs rules. This is the fix working: before it, the same page needed a hand-forced refetch.
  • .app-tab-active computes border-bottom: 2px rgb(46,168,245) (--blue-bright); column max-width: 820px; hero border-top-width: 0px.
  • Across settings / data / guide / logs / plex / grocy / install: 1 column each, 1 aria-current on an installed app and 0 on a not-installed one, 0 .app-info-card, 0 success banners.
  • Data tab: exactly 1 amber callout (the same-disk warning). Settings, guide, grocy: none — correct, nothing is asked and nothing is risky.
  • After the operator's report: .app-page padding 0px 0px 32px, .content padding 32px, measured gap 32px from the sidebar (was 80) and 36px from the top (was 64+).
  • Console clean on a fresh load, on 0.316.2 and again on 0.317.0.

Screenshots: felhom.eu/documentation/audits/app-page-2026-10-15b/browser/ — 01-settings-boxes-still-there, 02-settings-one-column-0.316.2, 03-data-tab-amber-callout, 04-guide-tab-padding-fixed-0.317.0.

Scenario E's blue callout was NOT exercised live. Plex's setup gate is open (the operator's real claim, R-1060), so Needed() is false and the card is correctly hidden. Showing it live would mean un-claiming the operator's real Plex server; not done. The branch is covered by TestAppPage_CalloutsOnlyForAskAndWarn against a harness Plex with a closed gate.

9. Parity fixtures

Recaptured (the pages this task restyles): app_settings_* ×21, app_data_* ×9, app_guide_* ×4, app_logs, deploy_new{,_blocked,_memory_blocked}. All other fixtures are byte-identical — and now permanently independent of build identity: the harness normalises the asset tag on both sides of the comparison, as it already did for the per-session CSRF token, so a build can no longer churn 148 files. The same normalisation was applied at the second comparison site in i18n_wiring_test.go, which had the same assumption.

10. NOT yet validated

  1. The operator's own look at AdventureLog and Plex on demo-hp through the public address. The layout judgement is theirs. (The padding complaint from the first look is fixed and live.)
  2. A blue callout in a real browser (§8).
  3. A catalog guide rendered from a real template — no template carries app_info.guide yet.

11. Teardown — three layers

  • Machine: nothing provisioned, no app installed, no throwaway Plex. Probe scripts, fetched pages and the password file removed from guest 9201, demo-hp and DooPlex /tmp; the dashboard password went file→file, was never printed, and no copy reached Windows or git.
  • Host: both scratch clones and the felhom.eu sibling symlink deleted. The shared DooPlex checkout was never written to by me — which is how the other session's work was distinguishable from mine. build.sh in the build directory is now a symlink to the repo's (deliberate), with the stale July copy kept beside it.
  • Hub: nothing created; no floor change.

12. Observations

  • FILED: R-1081 — the registry's 0.315.0, and now also 0.316.3, hold content that is not what their numbers say. Both caused by this session; both fixed at the root; no box affected.
  • NOT-A-FINDING (fixed in session): the asset tag shipped dead — incident (ii).
  • NOT-A-FINDING (fixed in session): the clean-tree rule had no mechanism — incident (iii).
  • NOT-A-FINDING (fixed in session): app_row_dedup_gate convicted my .app-row, correctly — that class already means „a row in the app list". Renamed .app-field-row; an over-broad rename briefly took the list's own rule with it and was restored.
  • NOT-A-FINDING (fixed in session): the one-column padding doubled against .content's own 2rem. The mockup's numbers were the total the page should show, not an amount to add.
  • NOT-A-FINDING: build.sh's workspace/ dirtied any clone it was run from; gitignored.
  • Register: 169 before → 170 after; 1 opened (R-1081), 1 opened-and-closed (R-1080).
  • System poster: no fact in felhom-system-poster.facts.md changed. Neither file edited.
  • Instruction files: .claude/rules/unprompted-work.md item 6 in all four versioned copies gains „a version number is pushed once", stated as a fact about what the build now enforces, not as a new rule. The workspace-root copy is unversioned and not on this machine.
  • Worth the operator's attention: two sessions released this repo within an hour, and the second overwrote the first's REPORT.md. This file is the workaround; whether REPORT.md should stay a single overwritten file in a repo two sessions can release is a question for the operator, not a change I should make unasked.