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

219 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 — <app>" and „<app> — 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.