v0.147.0 — feedback slice 1: pressing a button says something

The systemic complaint, twice in one evening: you press a button and nothing
happens. No progress, no ETA, no named result. Three worst offenders, fixed on
the two patterns already here (deploy 3-step panel, storage-init status poll).
No new framework — that is a ROADMAP item; three targeted cards ship tonight.

4a — a verification restore names its result. The flash said the app had been
restored "to a verification folder on the drive"; which folder, on which drive,
was invisible, so the customer could not go and look at what they had just asked
for. Full path now. The restore page gained a listing of existing verification
copies (app, size, date, path) — nothing anywhere showed these, so they piled up
and the only way to find them was SSH — each with a double-confirmed delete.

That delete is the only one this release adds, so it names a STACK, never a
path: the Manager resolves the name inside a backups/offsite-restore root it
computed itself and refuses anything landing outside. Red-proofed — neutralise
the name guard and stack:"" resolves to the offsite-restore ROOT and takes every
copy with it. Refusals are asserted as non-effects.

4b — Megosztás enable shows what it is waiting for. Enabling ran ReconcileSamba
synchronously inside the POST handler; on a golden without felhom-samba baked
that is compose pulling ~100MB, i.e. minutes of an apparently-hung form post
followed by "Beállítás mentve." whether or not anything came up. Detached +
polled now, distinguishing "képfájl letöltése" from "indítás" — decided BEFORE
the work starts, since afterwards the image is always present. Success is
probed, not inferred (compose up -d exits 0 on a crash-loop). The password form
starts the same job: with UserSet false reconcile deploys nothing, so on a fresh
box that is where the pull actually happens.

4c — "Távoli mentés most" streams real progress. restic was already reporting
bytes and percent; the runner seam used CombinedOutput() and discarded them. The
manual run now passes --json and scans stdout line-by-line: total bytes, percent,
current app. Manual only — the nightly stays silent, pinned by a test that fails
if it ever passes --json. The poll now arms unconditionally, closing a race the
manual trigger always ran: the redirect rendered before the goroutine wrote
LastStatus=running, so the poll never armed and the page sat static during the
very run just started. Red-proofed twice.

Also closes the golden/controller infra-image drift at the source: infra.Images()
derives from the existing pins and --print-infra-images exposes it, so the golden
bake can stop carrying its own copy. That copy had already drifted — felhom-samba
was never added, so the golden baked 3 of 4, which is why enabling Megosztás
pulled at runtime in the first place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
2026-07-19 09:30:30 +02:00
parent 7f0b41c3e7
commit b5d78d1e0f
22 changed files with 1527 additions and 21 deletions
+68
View File
@@ -1,5 +1,73 @@
## Changelog
### v0.147.0 — feedback slice 1: pressing a button says something (2026-07-19)
Green: `go build ./... && go vet ./... && go test ./...` all pass (23 packages);
`template_id_gate` + `emoji_gate` + `native_confirm_gate` + `offbox_rename_gate` +
`app_row_dedup_gate` + `mojibake_gate` all PASS. (`docker_run_volume_path_gate` fails on
`internal/appexport/estimate.go:179`**pre-existing on HEAD, untouched by this release**; verified
by stashing this work and re-running.)
**The systemic complaint, twice in one evening: you press a button and nothing happens.** No
progress, no ETA, no named result. This slice fixes the three worst offenders using the two patterns
already in the codebase (the deploy 3-step panel and the storage-init status poll). It deliberately
does **not** introduce a feedback framework — that is a ROADMAP item ("unified async-job feedback"),
because three targeted cards are worth shipping tonight and a framework is not.
- **4a — a verification restore now names its result.** The completion flash said the app had been
restored „ellenőrző mappába a meghajtón" — *which* folder, on *which* drive, was invisible, so the
customer could not go and look at the thing they had just asked for. It now carries the **full
path**. The restore page gained a **„Meglévő ellenőrző másolatok"** listing (app · size · date ·
path) — until now nothing anywhere showed what these restores had accumulated, so they piled up
and the only way to find them was SSH — each with a double-confirmed **„Másolat törlése"**.
- That delete is the **only** delete this release adds, so it names a STACK, never a path: the
Manager resolves the name inside a `backups/offsite-restore` root it computed itself and refuses
anything landing outside. Red-proofed — neutralise the name guard and `stack: ""` resolves to the
offsite-restore ROOT and takes every copy with it. Every refusal is asserted as a **non-effect**
(the neighbouring copy and the live data are still on disk afterwards).
- `backups/offsite-restore` was open-coded in three places; it now has one home
(`offsiteRestoreRootFor`), and a test pins the path in the flash to the path in the listing so
the customer can never be told about a directory the page cannot show or remove.
- **4b — Megosztás enable shows what it is waiting for.** Enabling sharing ran `ReconcileSamba()`
**synchronously inside the POST handler**. On a box whose golden had not baked `felhom-samba` that
is `compose up -d` pulling ~100MB from a private registry: minutes of an apparently-hung form post,
then „Beállítás mentve." whether or not anything had come up. Now detached + polled, with a card
that distinguishes **„képfájl letöltése"** (image genuinely absent — the multi-minute case) from
**„indítás"** (already baked — seconds). The distinction is decided *before* the work starts,
because afterwards the image is always present and the card could never truthfully say „letöltés".
- Success is **probed, not inferred**: `compose up -d` exits 0 on a crash-loop, so the terminal
state is container liveness. `nil` from reconcile also covers "deliberately deployed nothing
because there is no household password yet", which now gets its own message instead of a card
spinning forever.
- The **password** form starts the same job — with `UserSet` false reconcile deploys nothing, so on
a fresh box *that*, not the enable toggle, is where the pull actually happens.
- **4c — „Távoli mentés most" streams real progress.** restic was already reporting bytes and
percentages; the runner seam used `CombinedOutput()` and threw them away. The manual run now passes
`--json`, scans stdout line-by-line, and the page shows **total bytes, percent and the app
currently being pushed**. Before the scan finishes it says „a mentendő adatok felmérése…" rather
than pinning a bar at 0%, which reads as stuck.
- **Manual only.** The nightly run stays silent and its output format is untouched — pinned by a
test that fails if the scheduled path ever passes `--json` or publishes progress.
- The poll now **arms unconditionally**. It used to start only if the page already rendered „Fut…",
which loses a race the manual trigger always runs: the POST redirects and the page renders before
the detached goroutine writes `LastStatus=running`, so the poll never armed and the customer
watched a static page during the very run they had just started.
- Red-proofed twice, both confirmed: break the parser → the percent assertion fails; drop the
wiring → the `--json` assertion fails. The `--json` stream is tail-bounded (40 lines) so a large
backup does not buffer megabytes of status spam for error diagnosis.
- **Golden/controller infra-image drift closed at the source (supports the agent-side change).**
`infra.Images()` derives the list from the existing pins, `--print-infra-images` prints it, and the
golden bake now asks the controller binary it is about to bake instead of carrying its own copy.
The copy had already drifted: `felhom-samba` was never added to it, so the golden baked 3 of 4 —
which is *why* enabling Megosztás pulled at runtime. A test parses the const block out of the
source and fails if a pin is added without reaching `Images()`.
**Live-validated** on demo guest 9201 through the real UI. **No floor change** — Viktor decides floor
timing.
### v0.146.0 — nav polish: styled scrollbars + collapsible sidebar groups (2026-07-18)
UI-only; no behavioural or backup/restore surface touched. Green: