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:
adminandClaude Opus 5 committed 2026-10-15 18:38:21 +02:00
1 parent 06e7cf8c3f
commit 5d4538cc59
5 files changed
+129 -2

No files matched your search

@@ -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 |
|---|---|---|---|
@@ -248,6 +248,45 @@ reached only through `LocalizeStacks` / `LocalizeStackPtr` / `Stack.MetaFor`.
still looks right.
- **`For` never writes through.** The metadata lives in the stack manager's cache and is shared by
concurrent requests; an in-place merge would put one household's language on another's page.
**[DESIGN] The guide block (v0.315.0).** `app_info.guide` joins the copy model with one exception it
has to state out loud:
```yaml
app_info:
guide:
checked_version: "1.2.3" # NOT copy — the render gate; see below
sections:
- title: "Első bekapcsolás"
intro: "…" # optional
steps: ["…", "…"]
faq:
- q: "…"
a: "…"
i18n:
en:
app_info:
guide:
sections: [...] # same shape; checked_version is NOT overlaid
faq: [...]
```
- **`checked_version` is not copy and is not overlaid.** It names the image tag the guide's author
clicked through, and the guide renders only when it is set (`02-controller-module-map.md`, „The
catalog's user guide"). A translator does not vouch for a version, so an overlay can neither set
nor clear it.
- **Sections and FAQ entries are matched BY INDEX — the only lists here that are.** The rule above
says every list is matched by its own key, and a guide section has no key to match on: its title
is the very thing being translated, so a `match_title` would have to be kept in step with the
Hungarian by hand, which is the drift the key rule exists to prevent. Index matching is safe here
for two reasons that must both hold: the catalog's copy gate requires a translated guide to carry
the same number of entries in the same running order, and **the fallback is still field by
field**, so a misaligned entry shows the Hungarian one and never a sentence nobody wrote.
- **`steps` replaces whole**, like the other prose lists, and for the same reason.
- An English list **longer** than the Hungarian one is ignored past the Hungarian count, with one
WARN naming the app and the counts (`Metadata.GuideOverlayExtras`, logged by the page that renders
the guide — `For` runs per request and holds no logger). A silently dropped translation is exactly
the kind of absence nobody notices.
- **Two copy producers have no request and therefore no language** — the integration rows
(`internal/integrations`) and the initial-credentials note (`internal/stacks/initialcreds.go`).
Both are re-taken from the localised metadata in the handler.