User intent (likes, hides, playlist adds, Lidarr requests, cancels)
now persists across network loss. Controllers write their optimistic
local state to drift first, then try the REST call; on failure
the call is enqueued in cached_mutations rather than rolled back.
MutationReplayer drains the queue on connectivity transitions and
a 1-minute periodic tick.
**Infrastructure (schema 8):**
* CachedMutations table — id / kind / payload (JSON) / createdAt
/ lastAttemptAt / attempts. Drop-after-5-attempts semantics: a
permanently-failing mutation eventually drops, and next sync
reconciles drift to the server's authoritative state.
* MutationQueue.enqueue / pendingCount
* MutationReplayer.start + .drain — start fires from app.dart's
postFrameCallback alongside SyncController / Prefetcher / etc.
* Kind registry: like.add / like.remove / quarantine.flag /
quarantine.unflag / playlist.append / request.create /
request.cancel — each with a Ref+payload handler that re-fires
the corresponding REST call.
**Wired surfaces:**
* LikesController.toggle — optimistic drift like/unlike stays
across REST failure; queues the call. Drops the old rollback.
* MyQuarantineController.flag / .unflag — same pattern. Hide/unhide
visibly persists offline; replays when back online.
* addToPlaylistActionProvider — now does an optimistic
cached_playlist_tracks write (position = max + 1) so the
playlist detail screen shows the new track instantly. Queues
appendTracks on REST failure.
* DiscoverScreen._request — queues request.create on DioException.
No drift state for the request itself (myRequestsProvider is
still REST-only) so the row won't show on /requests until replay
succeeds — acceptable for v1.
* MyRequestsController.cancel — optimistic in-memory remove no
longer restores on failure; queues request.cancel instead.
**Test update:**
quarantine_provider_test "flag rolls back on server failure"
renamed and rewritten to assert the new offline behavior:
optimistic drift row persists, mutation is enqueued for replay.
**Out of scope (v2):**
* Playlist create / rename / delete (no Flutter UI exposes these yet)
* Lidarr request optimistic local row (would need a cached_requests
drift table)
* UI "syncing N pending changes" indicator (operator preference:
silent unless we find a concrete need)