OfflineOutbox class

Append-only event log of pending Supabase writes that the device couldn't (or won't) flush online immediately.

Each enqueued row represents one logical write intent — e.g. "insert this conversation_messages row" or "upsert this user_topic_progress row". The SyncWorker drains the log when connectivity returns, replaying each event against the live Supabase client.

The schema is deliberately generic (a single outbox_events table with a payload JSON blob) rather than mirroring every Supabase table. Reasons:

  1. Phase 3 only needs the write path to survive offline; reads remain online-first (the chat notifier keeps messages in memory during a session). Denormalized per-table mirrors land later if and when read-path offline support is required.
  2. Adding a new table to the outbox means a new tableName/payload shape, not a schema migration on the local DB.
  3. The original plan used Drift; the current riverpod_lint pin made its analyzer-10 requirement incompatible, so this is the raw sqlite3 equivalent. See pubspec.yaml for the resolution history.

Web has no native sqlite — local_vector_store_stub.dart set the precedent; the matching offline_outbox_stub.dart returns no-op instances and the Riverpod provider yields null under kIsWeb.

Constructors

OfflineOutbox()

Properties

hashCode → int
The hash code for this object.
no setterinherited
isReady → bool
no setter
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

delete(int id) → void
Permanently remove an event. Used by the "Needs attention" UI when the user explicitly discards a row, and for periodic cleanup of synced rows older than a retention window (not yet implemented).
dispose() → void
enqueue({required String localId, required String tableName, required String operation, required Map<String, dynamic> payload, DateTime? clientUpdatedAt}) → Future<int>
Append a write intent. Returns the autoincrement row id. clientUpdatedAt is the wall-clock at the time of the user action, used by SyncWorker for last-write-wins conflict resolution.
failedEvents({int limit = 50}) → List<OutboxEvent>
Just the failed rows — convenience for the "Needs attention" list, which doesn't want to show pending/syncing rows because those resolve on their own.
initialize() → Future<void>
markFailed(int id, String error) → void
Mark an event as failed; SyncWorker will retry with backoff next cycle. After enough attempts the row stays in failed and is surfaced in the "Needs attention" UI.
markPending(int id) → void
Flip a failed row back to pending so the next SyncWorker drain attempts it again. Used by the "Needs attention" UI's retry button. Resets last_error so a stale message doesn't keep showing while the row is back in flight.
markSynced(int id, {String? serverId}) → void
Mark an event as successfully replayed. Records the server-assigned id (if any) so future re-emits (e.g. an update after an insert) can reference the right row.
markSyncing(int id) → void
Mark an event as in-flight to a Supabase write. Prevents the SyncWorker from picking it up again if it crashes mid-call.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
pendingAndFailed({int limit = 100}) → List<OutboxEvent>
Oldest-first list of events eligible for a sync attempt now: all pending/syncing rows, plus failed rows that are (a) under maxAttempts and (b) past their exponential-backoff window. Rows past the attempt cap stay failed and are surfaced in the "Needs attention" UI (where the user can markPending to retry) rather than retried automatically.
pendingCount() → int
Count of unsynced rows. Used by Settings to surface a "N pending" hint and by SyncWorker to short-circuit when empty.
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited

Static Methods

failedRowIsDue(OutboxEvent e, DateTime nowUtc) → bool
Whether a failed row is due for another automatic attempt: not past maxAttempts, and past an exponential-backoff window since its last attempt (30s, 60s, 120s, … capped at 1h).

Constants

maxAttempts → const int
Max automatic retry attempts before a failed row is left alone for manual retry (a poison event must not be re-hammered forever).