From a86c864b06ac17d46943e6db3ea0fd28971687c8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 15 Oct 2026 21:48:19 +0200 Subject: [PATCH] REPORT-app-page-2026-10-15.md: the app-page arc, in a file a second session cannot overwrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y --- REPORT-app-page-2026-10-15.md | 218 ++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 REPORT-app-page-2026-10-15.md diff --git a/REPORT-app-page-2026-10-15.md b/REPORT-app-page-2026-10-15.md new file mode 100644 index 0000000..8ed51a3 --- /dev/null +++ b/REPORT-app-page-2026-10-15.md @@ -0,0 +1,218 @@ +# 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.