## Changelog ### v0.147.1 — 4c follow-up: the progress bar must move on an INCREMENTAL run (2026-07-19) Found by watching the v0.147.0 card during a real manual run on the demo box, which is the only way this was ever going to surface. **The observation.** A 430MB immich push reported `0%` for 40+ seconds and then completed. The parser was not broken — restic was genuinely reporting no transferred bytes. On an incremental run where nothing changed, restic transfers nothing: `bytes_done` is `omitempty` on restic's side, so it is not even present in the JSON, and `percent_done` stays 0 for the whole run. Confirmed against the real schema by capturing `backup --dry-run --json` output from restic 0.14.0 in the controller image (the version comment in `offbox_progress.go` now quotes those captured lines verbatim). **Why it mattered.** A byte-only progress bar is indistinguishable from a hang in the COMMON case — the incremental run — which is precisely the silence 4c set out to remove. Shipping it would have replaced "no feedback" with "feedback that says 0% and looks stuck". - `files_done` / `total_files` are now parsed and published alongside the byte counters. They move on an incremental run even when bytes do not. - The card prefers bytes when bytes are moving; otherwise it drives the bar from files and says „N / M fájl ellenőrizve"; only before restic knows a total does it say „a mentendő adatok felmérése…". - `parseResticStatus` now returns a struct rather than four positional values, and a new test pins the real incremental-run line shape (bytes absent, files climbing) so a future refactor cannot quietly drop the file counters and restore the stuck bar. ### 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: `go build ./... && go vet ./... && go test ./...` all pass; `template_id_gate` + `emoji_gate` + `native_confirm_gate` + `offbox_rename_gate` + `mojibake_gate` + `app_row_dedup_gate` all PASS. - **Scrollbars (`style.css`).** The platform default is a light, chunky bar that reads as a bright stripe against the navy and competes with the content it is scrolling. Now thin and hairline coloured: `scrollbar-width: thin` + `scrollbar-color` for Firefox, `::-webkit-scrollbar` (8px, thumb `--line`, hover `--text-3`, `--radius`) for WebKit/Blink — **both** declared, because neither alone covers the browsers customers actually use. The two surfaces that really scroll take their own panel background as the track (`.sidebar` → `--bg-2`, `html` → `--bg-0`) so the gutter never shows through as a lighter channel. Tokens only, no raw hexes. - **Collapsible nav groups (`layout.html` + `style.css`, vanilla JS — no framework).** Tárhely, Biztonsági mentés and Megosztás are now accordions with a chevron indicator; **exactly one is open at a time**, and clicking the open one closes it. Groups without sub-items (Vezérlőpult, Alkalmazások, Rendszermonitor, Debug) are untouched plain links. Hungarian labels unchanged. - **The header is a real `