REPORT + CONTEXT for v0.316.x: the diagnosis, the two mechanisms, and the bad push I caused
gates / gates (push) Failing after 48s
gates / gates (push) Failing after 48s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
This commit is contained in:
+20
-1
@@ -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
|
||||
|
||||
@@ -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 `<script>`; `TestAppPage_ScriptTagsAreBalanced` (**found in a browser**) |
|
||||
| `9cc7ffc` | build.sh refuses a repeated push; `assetTag` per build; every shell uses it; harness normalises it |
|
||||
| `7f3e58f` | the one-column layout (the approved mockup) |
|
||||
| `48d8674` | v0.316.0 CHANGELOG + the push-once fact in the rule file |
|
||||
| `006ceab` | **v0.316.1** — the asset tag reaches the dashboard pages |
|
||||
| `f908eee` | the settings tab loses the install page's boxes |
|
||||
| `9e68e52` | two more data-tab leftovers |
|
||||
| `8c7aa10` | **v0.316.3** CHANGELOG |
|
||||
| felhom.eu `b6638ac4`, `22cd7c51` | the design docs, the audit, R-1080 closed, R-1081 filed |
|
||||
|
||||
Baseline `85f65c7` -> `db52f5a`. felhom.eu `06e7cf8c` -> `5d4538cc` (docs).
|
||||
Main files: `controller/build.sh`, `scripts/test_build_push_once.sh`, `internal/web/server.go`,
|
||||
`asset_tag_test.go`, `app_layout_test.go`, `templates/{style.css,app_shell,app_settings,app_data,app_guide,app_logs,app_fields,deploy}.html`,
|
||||
both locale files, `i18n_parity_test.go`, `i18n_wiring_test.go`.
|
||||
|
||||
## 4. Tests and red-proofs
|
||||
|
||||
**New test groups (9 functions across 3 files), all through the production router where routing is
|
||||
the subject.**
|
||||
|
||||
| Test | Asserts |
|
||||
|---|---|
|
||||
| `TestAppPage_DeployedDefaultsToSettings` | `/apps/{slug}` renders the settings tab (`data-tab="settings"` + `aria-selected="true"`), all four tabs present, Mukodes offers restart+stop and not start, **the header contains no `stackAction(`**, no Pi/x86 text |
|
||||
| `TestAppPage_NotDeployedShowsGuideOnly` | no tab bar, `data-install-cta`, every tab suffix → 302 `/apps/grocy`; unknown app 404; unknown suffix 404 |
|
||||
| `TestAppPage_OldURLsRedirectGETOnly` | the five old GETs → 302 with the right Location, query carried, `?raw=1` still text/plain, unknown name 404, **POST to `/backup` and `/app-email` reaches its handler** |
|
||||
| `TestAfterStart_PlexThroughRouter` | POST `/apps/plex/after-start/PLEX_CLAIM` → the real registry → a fake Plex, by ADDRESS; the gate moves; the card disappears |
|
||||
| `TestAfterStart_NoActionNoCard` | an unregistered field: no card, POST 404, **zero calls to the fake** |
|
||||
| `TestAfterStart_RegistryAloneIsNotAnInvitation` | a REGISTERED action whose field the catalog does not declare: 404, zero calls, the gate untouched |
|
||||
| `TestAfterStart_CardHiddenWhenNotNeeded` | registered + `Needed()==false` → no card |
|
||||
| `TestAfterStart_PlexClaimOldRouteStillWorks` | `/apps/plex/plex-claim` runs the same entry |
|
||||
| `TestInstallPage_BackGoesToApp` | Vissza and Mégsem href `= /apps/grocy`; the post-install links point at the tabs; unknown app 404 |
|
||||
| `TestAppPage_EachTabRendersItsOwnContent` | one marker per tab, and **the log poller on the log tab only** |
|
||||
| `TestGuide_RenderRules` | checked renders / empty does not / absent says so / `<script>` escaped / DOMAIN filled |
|
||||
| `TestGuide_EnglishFallsBackFieldByField` | English FAQ + Hungarian sections; a blank English intro falls back; `checked_version` survives; the Hungarian page is unchanged |
|
||||
| `TestGuide_*` (stacks, 8 funcs) | parse, the render gate, the overlay field by field, extras dropped+counted, `WithAddress` over every string, `Clone` not shared, and the catalog snapshot carries no guide yet |
|
||||
|
||||
**Red-proofs — each break seen failing, then reverted.**
|
||||
## 5. Tests and red-proofs
|
||||
|
||||
| # | Break | Result |
|
||||
|---|---|---|
|
||||
| A | default tab → guide for a deployed app | `TestAppPage_DeployedDefaultsToSettings` FAILS |
|
||||
| B | drop the not-deployed tab redirect | `TestAppPage_NotDeployedShowsGuideOnly` FAILS |
|
||||
| C | drop `&& r.Method == http.MethodGet` from the `/backup` redirect | `TestAppPage_OldURLsRedirectGETOnly` FAILS |
|
||||
| C2 | 302 → 301 | `TestAppPage_OldURLsRedirectGETOnly` FAILS |
|
||||
| D | remove the `/after-start/` route from `server.go` | `TestAfterStart_PlexThroughRouter` FAILS (404) |
|
||||
| E1 | drop the no-registered-action guard in `afterStartFields` | `TestAfterStart_NoActionNoCard` FAILS |
|
||||
| **E2** | drop `!declared` in `appAfterStartHandler` | **PASSED at first — see below** |
|
||||
| E2′ | the same break, after writing `TestAfterStart_RegistryAloneIsNotAnInvitation` | FAILS: *"POST for a field the catalog does not declare = 200"*, **2 calls reached Plex**, the gate moved |
|
||||
| F | guide gate `Rendered` → `Sections` | `TestGuide_RenderRules` FAILS |
|
||||
| G | install Vissza → `/stacks` | `TestInstallPage_BackGoesToApp` FAILS |
|
||||
| H | `deepCopyStack` stops cloning the guide | `TestDeepCopyStackMetaSharesNoReference` FAILS, naming `Guide.Sections` and `Guide.FAQ` |
|
||||
| I | remove `</script>` from `app_logs.html` again | `TestAppPage_ScriptTagsAreBalanced` FAILS: "logs: 3 `<script>` opened, 2 closed" |
|
||||
| 1–3 | `Rendered()` without `checked_version`; `overlayGuide` replacing the FAQ whole; `WithAddress` skipping the guide | the three matching `TestGuide_*` FAIL |
|
||||
| B1 | the whole 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 |
|
||||
| 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` fails: empty buster on four pages |
|
||||
| D1 | the auto-update 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 |
|
||||
|
||||
**E2 is the finding of this session.** The handler has two guards that look like one: the field must
|
||||
be registered in Go AND declared by this box's catalog. Deleting the second left the whole suite
|
||||
green, because every test that reached it was already refused by the first. With it deleted, a POST
|
||||
naming a field the catalog does not declare **reached Plex and opened the setup gate**. The missing
|
||||
test was written, the break repeated, and it now convicts. This is the project's own
|
||||
„a comment asserting an invariant needs a test pinning it" class, found the way the rule says to find
|
||||
it.
|
||||
New: `TestBuildScript_RefusesExistingTag` (six cases, real script, fake docker),
|
||||
`TestAssetTag_{ChangesPerBuild,EveryShellUsesIt,ReachesTheRenderedPage}`,
|
||||
`TestAppPage_{OneColumnLayout,AutoUpdateSuccessIsMetaNotBanner,CalloutsOnlyForAskAndWarn}`.
|
||||
|
||||
**Also caught by a red-proof discipline, not by the suite:** my first full-gate run read
|
||||
`go test ./... | grep -v ok | head -25`, whose exit code is `head`'s. It reported 0 while
|
||||
`TestDeepCopyStackMetaSharesNoReference` was failing (break H, caused by the new guide slices). Re-run
|
||||
capturing `go test`'s own `$?`: **`GO_TEST_RC=0`, 37 packages `ok`, zero `FAIL` lines.** The
|
||||
„exit codes that lie" trap, hit and corrected in-session.
|
||||
Full gate: `go build ./... && go vet ./... && go test ./...` → **exit 0, 37 packages ok, zero
|
||||
failures**, measured with `go test`'s own exit code (not a pipeline's). All controller gates OK; 53
|
||||
decoys behaved.
|
||||
|
||||
**TWO DEFECTS THE SUITE COULD NOT SEE, both found in a browser.** This is the case for Claude in
|
||||
Chrome running on the workstation, and it is worth stating plainly: every endpoint-level marker
|
||||
passed on both.
|
||||
## 6. Deployed — and what is NOT
|
||||
|
||||
1. **The tab bar had no CSS and the logo was unsized.** The shell was written with new class names -
|
||||
`app-hero`, `app-hero-logo`, `app-tabs` - and the first two duplicate names the design system
|
||||
already has rules for (`app-info-hero`, `app-info-logo`), while the third had none at all. The
|
||||
page answered 200, carried every `data-*` marker, and showed four bare underlined links and a
|
||||
logo filling a third of the screen. Fixed in `fe0228c`: the shell reuses the existing hero
|
||||
classes, and the tab bar is styled to design system v2 - measured live in the browser as
|
||||
`display: flex`, `border-bottom 1px rgb(34,52,79)` (`--line`), active `rgb(46,168,245)`
|
||||
(`--blue-bright`) on `rgb(15,27,46)` (`--bg-1`), radius 2px.
|
||||
2. **The log tab's `<script>` was never closed, so the live poller was dead.** The body was lifted
|
||||
out of `logs.html` by line range and the range stopped one line short. `html/template` parsed it,
|
||||
the page rendered, and `TestAppPage_EachTabRendersItsOwnContent` found
|
||||
`setInterval(window.fetchLogs` exactly where it expected it - **because the text was there.**
|
||||
Chrome refused the block with "SyntaxError: Unexpected token '<'" and the log never refreshed.
|
||||
Fixed in `db52f5a`, with `TestAppPage_ScriptTagsAreBalanced` red-proved.
|
||||
**demo-hp guest 9201 runs `0.316.2`, healthy.** Tester 1 and demo-felhom untouched. The **floor stays
|
||||
0.314.0** — raising it needs all three test boxes and a green box walk.
|
||||
|
||||
**The lesson, in the project's own terms:** a marker proves a string is present; it does not
|
||||
prove the browser will execute it. The positive observable is
|
||||
`typeof window.fetchLogs === "function"`, which is what the live check now reads.
|
||||
**v0.316.3 is committed and pushed to git but NOT built.** Between the 0.316.2 build and this one,
|
||||
another session's work in progress appeared in the shared DooPlex build clone (15 modified files,
|
||||
the physical-disk storage work, R-1074..R-1079). The clean-tree gate forbids building over it and I
|
||||
will not stash or revert another session's work. Every build I did make printed `porcelain=0` first.
|
||||
|
||||
## 5. Test count
|
||||
What 0.316.3 adds, and therefore what the operator will NOT see tonight: on the data tab,
|
||||
„Biztonsági mentés" is still drawn as a card, and the 2nd-copy heading still reads „2. mentés
|
||||
beállítása — AdventureLog". Both are cosmetic; everything else is live.
|
||||
|
||||
`internal/web` + `internal/stacks` test functions: **+22** (13 new functions). Parity CASES: 145 -> 148.
|
||||
## 7. The browser check, through the PUBLIC address
|
||||
|
||||
## 6. Deployed versions
|
||||
Chrome on the workstation, `https://felhom.enkisfelhom.hu`, **normal loads — no hard refresh**:
|
||||
|
||||
| Box | Before | Now |
|
||||
|---|---|---|
|
||||
| demo-hp guest 9201 | 0.314.0 | **0.315.0**, `Up ... (healthy)` |
|
||||
| Tester 1, demo-felhom | 0.314.0 | unchanged - not touched |
|
||||
- The stylesheet URL is `?v=20540d81` (an 8-hex per-build tag, not a version, not empty), 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`; the column is
|
||||
`max-width: 820px`; the hero has `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: **0 blue,
|
||||
0 amber** — correct, nothing is asked and nothing is risky.
|
||||
- **Console clean on a fresh load.**
|
||||
|
||||
Image `gitea.dooplex.hu/admin/felhom-controller:0.315.0`, built three times from a clean tree
|
||||
(`git status --porcelain` empty and HEAD == origin/main each time) as the two browser findings were
|
||||
fixed. **The floor is NOT raised**: per `.claude/rules/unprompted-work.md` item 6 that waits until it
|
||||
has run on Tester 1, demo-hp and demo-felhom through the normal update path with a green box walk on
|
||||
all three. demo-hp is one of the three, not the gate.
|
||||
Screenshots: `felhom.eu/documentation/audits/app-page-2026-10-15b/browser/`.
|
||||
|
||||
**For anyone rebuilding the same version:** the stylesheet's cache key is `?v={{.Version}}` with
|
||||
`max-age=3600`, so a second build of 0.315.0 serves new CSS under a key the browser already holds. It
|
||||
cost twenty minutes here. It cannot reach a household - a released version is built once - so it is
|
||||
recorded, not filed.
|
||||
**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.
|
||||
|
||||
## 7. NOT yet live-validated
|
||||
## 8. Fixtures replaced, by name
|
||||
|
||||
1. **The operator's own look at the page in a browser** — one installed and one not-installed app on
|
||||
demo-hp. Asked for in `STATUS.md`. Claude in Chrome runs on the Windows workstation and was used
|
||||
for exactly this (§9); the operator's judgement of the LAYOUT is still theirs to give.
|
||||
2. **A real Plex claim through the new card.** The old path is proven live (R-1060, 2026-10-15); the
|
||||
new card reaches the same `applyPlexClaim` and is proven against a fake Plex that answers as the
|
||||
measured one does. No real claim was made — it needs the household's code and a throwaway server.
|
||||
3. **A catalog guide rendered from a real template.** No template carries `app_info.guide` yet; the
|
||||
render is proven against fixtures and a unit test.
|
||||
Recaptured (the pages this task restyles, per the standing exception): `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 so against 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.
|
||||
|
||||
## 8. Evidence
|
||||
## 9. NOT yet validated
|
||||
|
||||
`/mnt/5_hdd/felhom.eu/evidence/app-page-2026-10-15/` on DooPlex, copied off the boxes at the end of
|
||||
each phase and before the scratch clone was deleted: `gates/` (the runner all green, 53 decoys,
|
||||
`go test ./...` with its own exit code), `live/` (every page fetched in the endpoint walk, cookie
|
||||
jars stripped) and `browser/` (four screenshots for the operator).
|
||||
1. **The operator's look** at AdventureLog and Plex on demo-hp through the public address — asked
|
||||
for in STATUS. The layout judgement is theirs.
|
||||
2. **v0.316.3's two cosmetic fixes** (§6) — committed, unbuilt, so unseen.
|
||||
3. **A blue callout in a real browser** (§7).
|
||||
|
||||
## 9. Live validation - demo-hp guest 9201, 0.315.0
|
||||
## 10. Teardown — three layers
|
||||
|
||||
The OMV work another session is running tonight is on demo-hp's guests **9310/9311**; 9201 was free.
|
||||
N100 was left alone (the media-stack session has it).
|
||||
|
||||
**Endpoint-level, authenticated, through the endpoints the UI invokes.** Every assertion is on an
|
||||
ASCII `data-*` marker with a positive and a negative control - an accented Hungarian pattern through
|
||||
`ssh -> pct exec -> bash` returns a false `0` that reads exactly like the markup being gone.
|
||||
|
||||
| Checked | Result |
|
||||
|---|---|
|
||||
| all five tab URLs on an installed app | 200 each |
|
||||
| content per tab | run card / data tab / guide / poller each on exactly one tab; the bar on all four |
|
||||
| the selected tab per URL | `/apps/x`->settings, `/settings`->settings, `/data`->data, `/guide`->guide, `/logs`->logs; `aria-current="page"` once |
|
||||
| the header | **0** `stackAction(` before the hero; the Mukodes card offers restart+stop for a running app |
|
||||
| a not-installed app | no tab bar, no run card, one Telepites; all four tab suffixes **302 -> `/apps/grocy`** |
|
||||
| the five old URLs | 302 each, to `/logs`, `/data`, `/data`, `/settings`, `/install`; **0 of them 301**; `?raw=1` carried through; the tab answers `text/plain` |
|
||||
| POST `/stacks/adventurelog/backup` | **303 -> `/apps/adventurelog/data?flash=flash.tier2.saved`** - the save ran and came back to the tab holding the form |
|
||||
| after-start POSTs (with a CSRF token) | unknown field 404, unknown app 404, **a registered env var on the wrong app 404**; without a token 403, which is what proves the 404s are the handler's |
|
||||
| Pi / x86 badges | 0 across every page fetched; `meta-badge` (22) and `Alkalmaz` (42) as positive controls |
|
||||
| the guide | `data-guide-missing` on the guide tab, no rendered guide - no catalog template carries one yet |
|
||||
| the install wizard | Vissza and Megsem both `href="/apps/grocy"` |
|
||||
| negative controls | unknown app, unknown tab suffix and unknown stack name all 404 |
|
||||
|
||||
**Plex's card is absent, and the reason was read rather than assumed:** its template declares
|
||||
`after_start` and `PLEX_CLAIM`, and its gate is `state: open` since 2026-10-15T08:45:53Z - the
|
||||
operator's real claim (R-1060). `Needed()` is therefore false, which is the
|
||||
`TestAfterStart_CardHiddenWhenNotNeeded` branch. A positive control would mean un-claiming the
|
||||
operator's real Plex server; not done.
|
||||
|
||||
**The data tab's Tier-2 section, with both controls:** `adventurelog` and `bookstack` keep their data
|
||||
on the system disk and get the honest `data-system-backup="same_disk"` sentence and no target picker
|
||||
(the `app_data_not_hdd_same_disk` branch); `paperless-ngx`, `plex` and `calibre-web` are HDD apps and
|
||||
each gets the real save form. A missing form on one app was a branch, not a defect - checked before
|
||||
it was reported.
|
||||
|
||||
**In a real browser** (Claude in Chrome; four screenshots in `evidence/.../browser/`): the settings,
|
||||
data and logs tabs and the not-installed view all render correctly; the tab bar's computed style
|
||||
matches the design tokens; `window.fetchLogs` is a function on the log tab and refreshing the log
|
||||
works; **the console is clean on a fresh load**. "Elso lepesek" on Grocy's page shows the household's
|
||||
own address (`kamra.enkisfelhom.hu`), so R-498 travels through the new page.
|
||||
|
||||
## 9b. Teardown - three layers, stated
|
||||
|
||||
- **Machine:** nothing provisioned. The probe scripts, the fetched pages and the password file are
|
||||
removed from guest 9201 and from demo-hp (0 leftovers, verified). The password went file->file, was
|
||||
never printed, and no copy reached Windows or git.
|
||||
- **Host:** the DooPlex scratch clone (`scratch/cc-apptabs`) and the `felhom.eu` sibling symlink the
|
||||
shared gates need are deleted; the shared checkout two other sessions are using was never dirtied.
|
||||
Evidence was copied off first.
|
||||
- **Hub:** nothing created. No customer, no floor change.
|
||||
|
||||
## 10. Parity fixtures (the §9 exception), by name
|
||||
|
||||
**Removed (33).** `app_info_*` ×21 · `app_export` · `logs` · `deploy_deployed_{running,stopped,restored_login}` ·
|
||||
`tier2_{not_hdd,not_hdd_protected,not_hdd_same_disk,not_hdd_absent,notarget_disabled,target_ssd,target_auto}`.
|
||||
|
||||
**Added (36).** `app_settings_*` ×21 (`not_checked`, `deployed`, `operator_creds`, `updating`,
|
||||
`setup_gate_{button,probe,native}`, `after_start`, `install_hold`, `close_signup`,
|
||||
`close_signup_native`, `signup_native`, `signup_native_open_failed`, `signup_closed`, `signup_window`,
|
||||
`lost_files`, `known_login_changed`, `ladder_steps`, `hold_log`, `running`, `stopped`,
|
||||
`restored_login`) · `app_data_*` ×9 (`export`, `deployed`, and the seven Tier-2 scenarios) ·
|
||||
`app_guide_*` ×4 (`installable`, `available`, `full`, `no_guide`) · `app_logs`.
|
||||
|
||||
**Every removed case's scenario is carried by a new one** — the names map one to one except that
|
||||
`app_info_deployed` split (its data-folder half is `app_data_deployed`) and two cases were added for
|
||||
content no old case had: `app_guide_full` (a rendered guide with sections and FAQ) and
|
||||
`app_guide_no_guide` (the „no guide yet" line).
|
||||
|
||||
**Changed, and NOT covered by the task's exception list (14).** The task's §9(b) names the pages it
|
||||
expected to change. These fourteen also changed, and each is required by the task's own §2.6 and
|
||||
Part 3 — a link change and a badge removal ARE byte changes:
|
||||
|
||||
- `deploy_new`, `deploy_new_blocked`, `deploy_new_memory_blocked` — the Pi badge removed (Part 3), the
|
||||
Vissza/Mégsem retargeted (§2.5). *(In §9(b)'s `deploy_*` set.)*
|
||||
- `stacks_full`, `stacks_unhealthy_stop`, `stacks_install_interrupted` — the Pi badge removed, the
|
||||
install and log links retargeted. *(In §9(b)'s `stacks_*` set.)*
|
||||
- `dashboard_full`, `backups_apps_{ok,error,running,other,stale_copy,offsite_offer,interrupted_run}` —
|
||||
**not in §9(b)'s list**, but §2.6 requires exactly these links to be updated. Stated here rather
|
||||
than quietly regenerated.
|
||||
|
||||
**Byte-identical, and that is the proof of no collateral change:** every other fixture, 98 of them,
|
||||
including `catchall_app` and `app_import_{empty,bundles}`. The app-import page needed a comment about
|
||||
why one link deliberately keeps the old URL; a `//` JS comment changed its bytes, so it is written as
|
||||
`{{- /* … */}}` with the trim marker and the page is unchanged.
|
||||
- **Machine:** nothing provisioned, no app installed, no throwaway Plex. Probe scripts and fetched
|
||||
pages removed from DooPlex `/tmp`.
|
||||
- **Host:** the scratch clone and the `felhom.eu` sibling symlink are deleted. The shared DooPlex
|
||||
checkout was never written to by me — which is how I could tell the other session's work from
|
||||
mine. `build.sh` in the build directory is now a symlink to the repo's (deliberate, §11), with the
|
||||
stale copy kept beside it.
|
||||
- **Hub:** nothing created; no floor change.
|
||||
|
||||
## 11. Observations
|
||||
|
||||
- **NOT-A-FINDING:** `page.title.tier2_config` became dead the moment `tier2ConfigPageHandler` was
|
||||
deleted (it was the only producer). Removed from both locale files, from
|
||||
`scripts/i18n_go_keys.json` and from `i18n_wiring_test.go`'s two lists in the same commit. Caused by
|
||||
this change, so cleaning it is part of it, not scope creep.
|
||||
- **NOT-A-FINDING (fixed without a row, the size rule):** `scripts/template_id_gate.py` was
|
||||
file-scoped, so the shared `app_deploy_fields` partial made it convict ids that are demonstrably on
|
||||
the page. It now follows `{{template}}` includes transitively. The hazard that buys is a VACUOUS
|
||||
check, so it ships with a scope decoy — a reference planted in `deploy.html` to an id defined only
|
||||
in `app_data.html`, a file `deploy.html` does not include — which the gate must still convict. It
|
||||
does. 53 decoys green.
|
||||
- **NOT-A-FINDING:** the i18n gate caught „Please" in the new English string (the copy style forbids
|
||||
pleading); reworded before the commit.
|
||||
- **FILED: R-1067** — the catalog still carries `pi_compatible:` and nothing reads it. Deliberately
|
||||
left: a box on 0.314.0 would show „csak x86" on every app. Gated on the fleet floor reaching
|
||||
0.315.0. (A row and not a session fix: it is another repo and it is blocked on the floor.)
|
||||
- **FILED + CLOSED: R-1066** — no architecture document covered the app page. Closed by this task's
|
||||
`02-controller-module-map.md` „The app page" and `10-localisation.md` §7 additions.
|
||||
- **Capability map:** no row covers „the app page", so none moved. Said rather than assumed.
|
||||
- **Register size: 156 before → 157 after; 1 opened (R-1067), 1 opened-and-closed (R-1066).**
|
||||
- **System poster:** no fact in `felhom-system-poster.facts.md` changed (no machine, role, traffic
|
||||
path, backup tier, time, retention, key or known gap). Neither file edited; nothing added to STATUS.
|
||||
- **Behaviour change worth naming:** the initial-credentials and default-login cards used to render
|
||||
only when `HasAppInfo` was true — incidental (the FileBrowser branch set `HasAppInfo = true` by hand
|
||||
just to make its credentials show). On the settings tab they are no longer gated on catalog copy
|
||||
existing. A login is not catalog prose.
|
||||
- **FILED: R-1081** — the registry's `0.315.0` no longer holds the 0.315.0 release. **I did this**,
|
||||
and the cause is worth stating plainly: I put the guard in `controller/build.sh` in the repo, but
|
||||
the documented release command runs `$FELHOM_ROOT/build/felhom-controller/build.sh`, which was a
|
||||
**separate copy taken 2026-07-18**. The very 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 a fourth
|
||||
time, with v0.316.0's code under it. A guard added to a script the runbook does not execute is the
|
||||
seam-built-but-never-wired class, and I shipped one while fixing another. Root cause fixed in the
|
||||
session (symlink), then the refusal proven from the documented path. No box is affected.
|
||||
- **NOT-A-FINDING (fixed in session):** the asset tag shipped dead in 0.316.0 — every shell asked for
|
||||
it, the template test passed, and the dashboard path never set it, so the first live load rendered
|
||||
`?v=` (empty, i.e. a constant). Found in a browser; `TestAssetTag_ReachesTheRenderedPage` now
|
||||
asserts the rendered value, not the markup.
|
||||
- **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:** `page.title.tier2_config`-style leftovers — the 2nd-copy heading's locale key
|
||||
carries „— {{.DisplayName}}" because it was a page title. New key `app_page.second_backup`.
|
||||
- **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;
|
||||
nothing added to STATUS about it.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user