diff --git a/CONTEXT.md b/CONTEXT.md
index 80cbcc5..56dfee8 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -7,7 +7,26 @@
>
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
-Last updated: 2026-10-15 (v0.315.0 — one app page with four tabs, the catalog guide, no Pi badge)
+Last updated: 2026-10-15 (v0.316.x — the app page in one column; a version number is pushed once)
+
+> **2026-10-15 (late evening) — v0.316.0/.1/.2 built and on demo-hp; .3 committed, NOT built.**
+> **Measured before fixing:** the tabs rendered unstyled through the public address because of a
+> CACHE, not the box — the stylesheet URL was `?v={{.Version}}`, identical across three different
+> 0.315.0 builds; the origin sends NO ETag and NO Last-Modified, so a copy can only expire; and
+> Cloudflare caches it while rewriting the browser TTL to four hours. Evidence and its caveat:
+> `audits/app-page-2026-10-15b/P0/VERDICT.md`.
+> **Two mechanisms now:** `build.sh` refuses a second push of a version (a registry it cannot reach
+> is a refusal too), and every shell's stylesheet address is a per-build tag.
+> **The app page is the approved one-column mockup**: one width, hairline sections, underlined tabs,
+> and a box only for „the app is waiting for you" (blue) or „this is risky" (amber). The
+> auto-update SUCCESS banner is now meta text.
+> **THE LESSON OF THE EVENING, and it cost a bad push:** I put the guard in `controller/build.sh`,
+> but the release runbook runs `build/felhom-controller/build.sh` — a SEPARATE COPY from
+> 2026-07-18. Proving the refusal against a real tag therefore re-pushed 0.315.0 a fourth time
+> (R-1081). The build directory's script is a symlink now. A guard in a script nobody runs is not a
+> guard. The same shape bit twice more in one evening: the asset tag shipped dead because only one
+> of TWO render paths set it, and both times a BROWSER found what the suite could not.
+> Not yet validated: the operator's own look; 0.316.3's two cosmetic fixes.
> **2026-10-15 (evening) — v0.315.0, ONE APP PAGE WITH FOUR TABS.** An app was six pages and every „Vissza" went to the
> app LIST; it is now `/apps/{slug}/{settings,data,guide,logs}` plus `/apps/{slug}/install`, one shared header and one
diff --git a/REPORT.md b/REPORT.md
index ee50266..ae8f7e4 100644
--- a/REPORT.md
+++ b/REPORT.md
@@ -1,280 +1,177 @@
-# REPORT — v0.315.0: one app page with four tabs (2026-10-15)
+# REPORT — v0.316.x: the app page as one column; one image per version number (2026-10-15)
## 1. Baselines
| Repo | `main` at start | Version | → |
|---|---|---|---|
-| felhom-controller | `85f65c7` | v0.314.0 | **v0.315.0** |
-| felhom.eu | `06e7cf8c` (the task said `3263bb2`; it had moved — two other sessions are pushing) | docs only | — |
-| app-catalog-felhom.eu | `e67aad4` | read only | — |
+| felhom-controller | `8339206` | v0.315.0 | **v0.316.0 → .1 → .2 built; .3 committed, NOT built (§6)** |
+| felhom.eu | `340804f9` (docs only) | — | — |
-Architecture read before any claim: `02-controller-module-map.md` (`web/`, the R-571 alert-placement
-rule), `10-localisation.md` §7, `15-media-library.md` (the Plex claim path), and the controller's
-`CLAUDE.md`, `REUSE.md` and `.claude/rules/{ui-hungarian,gates,unprompted-work}.md`.
+Architecture read first: `02-controller-module-map.md` „The app page", `09-update-architecture.md`,
+the `felhom-ui-design` skill, and the controller's `CLAUDE.md` + `.claude/rules/*`.
-**Where the work ran.** Edited on the Windows workstation; **built and tested on DooPlex** in an
-isolated scratch clone (`/mnt/5_hdd/felhom.eu/scratch/cc-apptabs`) so the shared DooPlex checkout —
-which two other sessions are using tonight — was never dirtied. The sync normalises line endings to
-LF and restores any file it did not mean to touch, because this checkout has `core.autocrlf=true`
-while six files (`.gitignore`, `Dockerfile`, `Makefile`, the three infra templates) are stored in git
-WITH CRLF; a blanket normalisation rewrote all six on the first attempt.
+Built and tested on DooPlex in an isolated scratch clone; edited on the Windows workstation. The
+browser work is on the workstation, which is the reason CC runs here.
-## 2. Files created / modified
+## 2. PART 0 — the diagnosis, before any fix
-**Created (11)**
-`controller/internal/web/app_page.go` · `after_start.go` · `app_page_test.go` · `guide_render_test.go` ·
-`controller/internal/stacks/guide_test.go` ·
-`templates/app_shell.html` · `app_settings.html` · `app_data.html` · `app_guide.html` · `app_logs.html` · `app_fields.html`
+**Verdict: (a) a cache.** Full evidence: `felhom.eu/documentation/audits/app-page-2026-10-15b/P0/VERDICT.md`.
-**Deleted (4 templates)** `templates/app_info.html` · `logs.html` · `app_export.html` · `tier2_config.html`
-— with the three page handlers that rendered them (`logsHandler`, `exportPageHandler`,
-`tier2ConfigPageHandler`). The POST handlers beside them (`tier2ConfigSaveHandler`,
-`appEmailToggleHandler`) stay, with their URLs.
+**(b) is ruled out.** Exactly one guest ran 0.315.0 — demo-hp 9201 — on digest
+`sha256:8887343011d6…`, identical to the registry's 0.315.0 at the time of measuring. The **global
+floor is 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 are both on 0.314.0; Tester 1 (`c11-appliance`) was
+unreachable and, by the floor, cannot be on 0.315.0.
-**Modified (main ones)** `web/server.go` (routes + the GET-only redirects) · `web/handlers.go`
-(`appDetailHandler` → `appCommonData`; `deployHandler` → `deployPageData` + a thin renderer) ·
-`web/media_events.go` (`applyPlexClaim` extracted) · `web/tier2_config_handler.go` (redirect targets) ·
-`templates/deploy.html` (install-only; 1197 → 544 lines) · `stacks.html`, `dashboard.html`,
-`backups_apps.html`, `app_import.html` (links) · `stacks/metadata.go`, `metadata_i18n.go`,
-`appinfo_address.go`, `manager.go` (the guide) · both locale files · `scripts/template_id_gate.py`,
-`scripts/test_gate_decoys.py`, `scripts/i18n_go_keys.json`.
+**The mechanism, in four measured facts:**
-## 3. Commits on `main`
+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**: measured 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 `max-age=3600` to `max-age=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.
+
+**What is true now, and the caveat stated rather than glossed:** origin, edge and box agree
+byte-for-byte (`sha256 c1774f63…` both sides) and all carry the rules; the page itself is never
+cached (`DYNAMIC`). The specific stale object is **no longer observable** — the first probe forced
+its revalidation, which is what `EXPIRED` records — so I cannot show a `HIT` whose body lacks
+`.app-tabs`. 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. Boxes on 0.315.0, with digests
+
+| Box | tag | digest | now |
+|---|---|---|---|
+| demo-hp 9201 | 0.315.0 | `8887343011d6` | **0.316.2** |
+| demo-hp 9202 | 0.314.0 | — | unchanged |
+| felhom-pve (N100) 9201 | 0.314.0 | — | unchanged |
+| Tester 1 (`c11-appliance`) | unreachable | — | cannot be on 0.315.0 (floor 0.314.0) |
+
+## 4. Files changed, commits on `main`
| Commit | What |
|---|---|
-| `e66d466` | v0.315.0 — the slice: tabs, routes, the after-start registry, the guide, the Pi removal, the fixtures. One commit, because the template split, the routes and the fixtures cannot be green apart |
-| `fe0228c` | style the tab bar; reuse the hero classes that already have rules (**found in a browser**) |
-| `db52f5a` | close the log tab's `` from `app_logs.html` again | `TestAppPage_ScriptTagsAreBalanced` FAILS: "logs: 3 `