# CONTEXT.md — Project Memory > This file serves as persistent project memory across Claude Code sessions. > It replaces the auto-generated "Memory" from the claude.ai Project. > **Update this file at the end of each working session** with current state, > recent decisions, and anything the next session needs to know. > > Ask Claude Code: "Please update CONTEXT.md with what we did today" Last updated: 2026-07-21 (v0.155.0 — R-48 follow-up: the wizard read the wrong running-flag) > **2026-07-21 — v0.155.0.** v0.154.0's wizard sourced "is an op running" from `Manager.IsRunning()` > — the CONCURRENCY single-flight, acquired inside the goroutine, and **`RestoreOffboxScratch` never > acquires it**. So the execution step was unreachable for „Ellenőrzés" and the full-restore > preparation: live buttons while a restore downloaded, with the progress banner contradicting the > phase strip on the same screen. Found by the operator on the first live click-through. > > **DECISION: display reads `RestoreStatus()` (the `opRunning` flag), never `IsRunning()`**, through > the named `restoreOpInFlight` seam, and the handler reads the status ONCE per render so the strip, > the suppression and the running-op name cannot diverge. The lesson generalises: `opstatus.go` is the > DISPLAY surface and says so in its own header — the concurrency flag is not a substitute. > > **The test lesson:** a table test over a pure function proves the function, not the caller. Scenario > E passed throughout because it injected `OpRunning=true` directly. The new test drives a real > `Manager` through `BeginRestoreOp` and asserts the render. > > **DECISION: „Eredmény" earns its place.** The strip's highlight is now `Phase`, derived separately > from `Step`: a finished restore is back on the intent step while the strip reads „Eredmény" and an > outcome card shows the result — window-bounded (10 min) and app-bound. > **2026-07-21 — v0.154.0 (R-48).** Collapses the offsite restore controls to a single > „Visszaállítás…" entry per app row plus a per-app wizard at `GET /backups/restore/app?name=`. > The defect it closes is the CAUSE of the round-2 incident: the list rendered up to five inline > forms per row, two of which — the missing-only merge and the true reconstitution — were sibling > buttons whose difference is whether the data comes back. The rule it establishes: *two adjacent > controls whose difference is "your data comes back" vs "your data cannot come back" must never be > distinguishable only by layout.* > > **DECISION: the wizard is server-rendered on the EXISTING endpoints.** No new mutation endpoint, > no JSON state API, no client router. Every card is a real form POST to > `/backup/offbox/{restore,place,reconstitute}` with the same field names and gates, and the server > renders the next step — so it works with JavaScript disabled. `TestRestoreWizard_NoNewMutationEndpoints` > makes that structural: adding a form that posts somewhere new fails the suite by design. > > **DECISION: R-45 stays its own item.** The wizard polls the two existing status surfaces as-is; the > generalized job registry (and with it a real per-phase progress feed) is not built here. > > **DECISION: the step is derived, never requested.** `deriveWizardStep` is pure over (op running, > size-gate flash, scratch ready). Precedence is load-bearing — a running op outranks a stale > `?full_prep=` in the URL, or a commit button reappears mid-restore. While ANY op runs every > mutation form is suppressed server-side rather than offered and then refused with a 409. > > Latent bug found and fixed on the way: `offboxRedirectTo` hardcoded `"?"` when appending its flash, > which would have buried the flash inside `?name=`. **No agent coupling — MinAgent stays > 0.90.0.** 9 new tests + the Group-B red-proof; full suite green. > > **NOT live-validated at commit time by design:** v0.154.0 is published but deliberately NOT > hand-deployed — the operator's hub floor save (0.153.0 → 0.154.0) pulls it via the self-update > path, and that swap IS the R-23(a) single-fire validation (STOP-1). > **2026-07-20 — v0.153.0 (R-47).** Closes the H4 race on **BOTH** restore paths. The replay needs a > running DB container, so both paths started the WHOLE stack first — giving the application a window > to rebuild the schema objects the dump was about to create. Measured at 8 s on 2026-07-19 > (`DIAG-immich-restore-round2-2026-07-19`): immich-server rebuilt `clip_index` two seconds before > the dump's `CREATE INDEX`, the replay aborted `already exists` under `ON_ERROR_STOP=1`, and immich > then reported schema drift. The photos came back **by accident** — `pg_dump` emits COPY before > CREATE INDEX, so the abort landed after the rows; a collision earlier in the script would have left > a genuinely half-restored database, reported identically. > > **DECISION: the DB-only bring-up is done by compose SERVICE scoping**, not by container tricks — > `StartStackServices(name, []string{svc})` → `compose up -d `. Every catalog template's > dependency direction is app→db, so naming the DB starts the DB and nothing else. `docker start > ` was never an option: `StopStack` is `compose down`, so the containers no longer exist. > `RestartStack`/`RedeployFromEnv` are traps here — both end in a full `up -d`. > > **DECISION: fail-closed.** A `.sql` dump with no identifiable DB service refuses BEFORE the first > mutation, on both paths (one Hungarian string, shared). The alternative would be to start everything > and replay into the race. It should be structurally unreachable — `dbTypeForImage` is now shared by > `DiscoverDatabases` and `DBServiceNames`, and a dump can only exist because discovery matched the > container's image, which IS the compose `image:` value — so this is the belt for template drift. > > Enablers: `RedeployFromEnv` split into `PersistUnitRedeployConfig` (persist, starts nothing) + the > unchanged tail; `StackDataProvider.RecreateStackFromUnit` renamed to > `RecreateStackDefinitionFromUnit` because the old name promised less than the method did — the > hidden `up -d` inside it is what carried the defect on the local path. `StartStackServices` REFUSES > an empty list (argument-less `up -d` is a full start). **No agent coupling — MinAgent stays 0.90.0.** > 19 new tests, 3 red-proofs, 23/23 green. **NOT live-validated yet:** STOP-1 supervised reconstitute, > golden 0.153.0 bake (P3 registry-reachability probe from the vacation site is load-bearing), Viktor's > two hub saves, and his C6 customer-restore UI run. > **2026-07-20 — v0.152.0 + felhom-samba 1.1.0 (Megosztás on a Mac).** Closes **S-3**. **A capture > on the box overturned the earlier guess:** macOS DOES send a correct NBNS query for `<20>` and > nmbd DOES answer it correctly in 140 µs (flags `0x8580`, RCODE=0, right address) — macOS simply > never acts on it. NetBIOS there feeds legacy browsing, not `smb://` URL resolution, so **the bare > `smb://` can never work from a Mac** and nmbd was never the broken part (it is what serves > Windows). felhom-samba 1.1.0 adds **avahi + dbus**, templating `avahi-daemon.conf` and the > `_smb._tcp` service file from `FELHOM_SERVER_NAME` so a rename re-advertises; both daemons are > non-fatal on failure. v0.151.0's card had offered `smb://` for Mac — the one dead form — now > `smb://.local`; Windows keeps flat `\\`. Spiked live by hand and confirmed from the > operator's Mac BEFORE publishing the image (the operator's call, and it chose the design too). > **STILL OPEN: Finder-sidebar discovery is NOT shipped** — the record is published and answers > browse queries, but was never observed working; likely a Finder Settings → Sidebar toggle, but > unverified. **Windows was not retested.** Two test bugs fixed en route, neither a production > defect: `TestRenderSambaCompose` pinned a literal image tag, and `TestFabUpload_GCAndIdleTimeout` > asserted an async unlink synchronously (it passed alone, failed in the full package once the new > render tests made `web` heavier). 23/23 green twice; 2 red-proofs. > **2026-07-20 — v0.151.0 (Megosztás).** Closes **S-1/S-2/S-4-core/S-5** of > `felhom.eu/documentation/audits/DIAG-sharing-2026-07-20.md`; **S-3 (no mDNS/Bonjour) stays OPEN**, > awaiting Viktor's `smbutil lookup FELHOM` + `dns-sd -B _smb._tcp` from the Mac. **The `/sharing` > page had been reload-looping at ~1.2 s for every customer with sharing enabled since v0.147.0** — > `/sharing/status` coerced `idle`→`running` on the JOB phase channel, and the client answers a > terminal `running` with a one-shot `location.reload()`, so the first poll of every steady-state > page load re-armed it. The rule this leaves behind, now recorded against R-45 too: **a phase a > client answers with a one-shot action is an EDGE — never synthesise it from a level, and serve it > exactly once.** Both halves are server-side; `sharing.html`'s `