docs: the app page (controller v0.315.0) — design, the guide in the copy model, R-1066/R-1067
gates / gates (push) Successful in 6m35s

02-controller-module-map.md gains "The app page" under web/: the URL map, why the default tab is
RENDERED at the bare URL and not redirected to, why the tabs are links and not panels, the
302-never-301 rule for the old /stacks/{name}/... GETs and the untouched POSTs, the after-start
registry (and why its actions are Go, never catalog data), and the guide's checked_version gate.
Each marked [DESIGN]. There was no document covering the app page at all, which is why the six-page
sprawl and the "Vissza goes to the list" behaviour were never called wrong — R-1066, filed and
closed by the same change.

10-localisation.md §7 gains the guide block, including the one exception it has to state out loud:
guide sections and FAQ entries are matched BY INDEX, the only lists in the copy model that are,
because a section's title is the thing being translated and so there is no key to match on. Safe
only because the copy gate requires equal counts AND the fallback stays field by field.

R-1067 (P4, BLOCKED): the catalog still carries pi_compatible: and nothing reads it. Left on
purpose — a box on 0.314.0 would show "csak x86" on every app — and gated on the fleet floor
reaching 0.315.0.

STATUS.md: one line asking the operator to look at the new app page on demo-hp, and the controller
version line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
This commit is contained in:
2026-10-15 18:38:21 +02:00
parent 06e7cf8c3f
commit 5d4538cc59
5 changed files with 129 additions and 2 deletions
@@ -397,6 +397,86 @@ by its words — the earlier Hungarian-substring test would have moved the warni
page the day the sentence was translated (R-553, pinned by `TestR553_DiskWarningPlacementSurvivesWordingChange`).
A new inline warning needs its own kind, not a text match.
#### The app page — one page, four tabs (v0.315.0)
**[DESIGN]** One app is ONE page. Until v0.314.0 it was six — details, install/settings, logs,
export, the per-app backup panel, and a details card for moving data — and every „Vissza" on them
went to the app LIST, so there was no way back to the app you were looking at.
| URL | Tab | Holds |
|---|---|---|
| `/apps/{slug}` | — | the default tab |
| `/apps/{slug}/settings` | „Beállítások" | „Működés" (Start / Restart / Stop), the cards that want something from the household, the install-time and optional settings |
| `/apps/{slug}/data` | „Adatok és mentés" | the data folders, move to another drive, the nightly copy, the 2nd-copy setting, export |
| `/apps/{slug}/guide` | „Útmutató" | tagline, screenshots, use cases, first steps, the catalog's guide, the FAQ, the docs link |
| `/apps/{slug}/logs` | „Napló" | the log |
| `/apps/{slug}/install` | — | the install wizard — a page, not a tab |
**[DESIGN] The default tab is RENDERED at the bare URL, never redirected to.** `/apps/{slug}` serves
the settings tab's content for an installed app and the guide for a not-installed one, with the URL
unchanged, so the address an alert, an e-mail, a bookmark or the app list carries stays the app's one
address. A redirect would make every such link bounce and would put a tab name into addresses nobody
chose.
**[DESIGN] The tabs are links, not panels.** A link in an alert or an e-mail has to land ON a tab,
and the log tab's poller must not run while the household reads the settings; one page with hidden
panels gives neither. Each tab is an `<a>` with `aria-current`, and the log poller is inside the log
tab's template.
**[DESIGN] A not-installed app has one view and one URL.** No tab bar — a bar of one tab is
furniture — and every tab suffix answers 302 to the base.
**[DESIGN] The old URLs are a bridge, and only for reads.** `/stacks/{name}/{deploy,logs,export,backup}`
GET answer **302** to the tab that now holds them, with the query string carried through;
`/deploy` goes to the settings tab for an installed app and to the install wizard for one that is
not. **302 and never 301:** a 301 lives in the browser's cache beyond anyone's reach, and these
paths are a bridge for one release, not an address. `name → slug` is resolved through the stack
list, never assumed equal — the two are equal for every catalog app today, which is exactly why
assuming it would break on the first app where they differ, on a page nobody tests.
**Every POST on those paths is untouched.** The backup form, the app-email toggle and the
`auto-field/reveal` endpoint keep their URLs and their handlers; the redirect cases are matched on
`GET`. Redirecting a POST would turn a save into a page fetch and lose the household's input.
#### After-start fields — the catalog asks, Go answers (v0.315.0, D2)
**[DESIGN]** Some values can only be given once the app runs: Plex's claim code is minted on plex.tv
and handed to a server that is already up (R-1028/R-1060). A catalog field marked `after_start: true`
is skipped by the install wizard and gets a card on the settings tab.
**[DESIGN] The catalog declares THAT a value is asked, and what to call it. It never declares what
happens to the value.** The action is Go, in a registry keyed by `slug + "/" + ENV_VAR`
(`web/after_start.go`), with `Needed(stack)` and `Apply(…, value)`. The household's text is therefore
an argument to a function in the controller's own source, and never a fragment of a command, a URL
or a container name that a catalog file chose. Adding a template cannot add an action.
**[DESIGN] Both halves are required, and each is checked on its own.** A field the catalog declares
with no registered action renders no card, answers its POST with 404, and logs one WARN naming the
env var KEY — a field that silently does nothing is the kind of absence nobody notices. An action
registered for a field **this box's catalog does not declare** is equally refused: the registry alone
is not an invitation. The second check looks redundant and is not — it was deletable with the whole
suite green until a test was written for it, and without it a POST for an undeclared field reached
Plex and opened the setup gate.
The value is a credential and is never logged. `/apps/plex/plex-claim` remains and runs the same
registry entry, so a page open across the update still works.
#### The catalog's user guide (v0.315.0)
**[DESIGN]** `app_info.guide` carries `sections` (title, optional `intro`, `steps`) and `faq`
(`q`, `a`), rendered on the guide tab. The copy model is `10-localisation.md` §7; the guide block is
described there.
**[DESIGN] `checked_version` is the render gate, and it is not copy.** It names the image tag the
guide's author actually clicked through. A guide is rendered only when it is set; otherwise the tab
shows its other content and says a guide is still to come. A guide walking a household through a
screen two releases old is worse than no guide, and „someone wrote this against a version" is the
only cheap evidence that it was ever true. Being outside the copy model is deliberate: a translator
does not get to vouch for a version, so an overlay can neither set nor clear it.
**[DESIGN] Guide text is DATA.** Plain text through `html/template` — no markdown, no raw HTML — so
a step containing a tag is shown as the characters its author typed.
### `scripts/`
| File | Class | Reason | Risk |
|---|---|---|---|