Public Access
Three triggers: once when the vault opens, straight after any local change, and every minute while it stays open. The Sync button stays, because someone just handed a credential wants to know now rather than within the minute, but nothing depends on it being pressed any more. A background pass is deliberately not the button's code path. Routing it through RunAsync would raise the busy flag every minute — disabling Connect and Save for the duration — and repaint the status line over whatever the user was reading. So it is quiet: the status changes only when a pass actually moved an item or produced something needing attention, and a pass is skipped outright while a command is running rather than queueing behind it. Both guards are covered; removing either fails a test. A shared semaphore serialises every pass, taken with a zero timeout rather than awaited — a pass arriving while another runs has nothing to add by waiting, and queueing them would turn a slow server into a backlog of identical work. Failures are swallowed, which is right in exactly this one place: a laptop closed all afternoon would otherwise replace the status line with a socket error once a minute. It is quiet rather than hidden — the account bar already shows when there is no connection, and pressing Sync reports the real reason. What earns that is the outbox: a test proves a change left queued by a failed pass is still sent by the next sync, so quiet never means lost. Two existing tests asserted the opposite behaviour — that a save queued and pushed nothing until Sync was pressed — and were rewritten rather than deleted; the local-first guarantee they were really protecting is that the list updates with no server, which the offline test still covers. Two things the tests caught in my own work. ReloadAsync had to be split out of LoadAsync because rebuilding the list repainted the status line unconditionally, which made "the background pass is quiet" false on the one path that mattered. And the yields-to-a-command test was vacuous as first written: saving pushes, so there was no pending change left and the assertion held with the guard deleted. It now fails the automatic push first to arrange a real queue.