7df7b5ff85
Offline writes now queue + apply optimistically instead of throwing. On reconnect, the queue drains automatically; conflicts and rejections surface as one-time SnackBars so the user knows their edit didn't land. - Drift schema v3: `pending_writes` table (verb + payload + baseline + tries + last_error). Negative ids serve as cache placeholders for offline-created rows until replay assigns the server id. - Each write repository (notes, tasks, projects, milestones, events) now catches NetworkException, applies the change to cache (with a fresh `nextTempId()` for creates), and enqueues the API call. - Edits to a still-queued offline-created row coalesce into the original create payload — only one server call per row, in order. - Deletes of still-queued offline-created rows drop the queue entry and the cache row; no server call ever happens. - New WriteQueue service drains the queue oldest-first. Server-wins on `updated_at` baseline check (notes/tasks/projects); last-writer-wins for milestones/events (no getOne available). 4xx → drop + surface as `rejected`; conflict → drop + `overwritten`; 404 on update → drop + `missing`; 5xx/network → keep + retry next online cycle. - Replay fires on AuthStatus → authenticated transitions (cold-start, came-back-online, login). - OfflineBanner shows "Retry (N)" with the pending-writes count. - Distinct ServerException added so 4xx no longer masquerade as NetworkException — the queue can drop them instead of looping. flutter analyze clean; 21 tests pass (3 new for QueueFailure messaging). Phase 4 (read-only UI indicators on cached/temp rows) still ahead. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
29 lines
810 B
Dart
29 lines
810 B
Dart
class AppException implements Exception {
|
|
final String message;
|
|
const AppException(this.message);
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
class NetworkException extends AppException {
|
|
const NetworkException(super.message);
|
|
}
|
|
|
|
class AuthException extends AppException {
|
|
const AuthException(super.message);
|
|
}
|
|
|
|
class NotFoundException extends AppException {
|
|
const NotFoundException(super.message);
|
|
}
|
|
|
|
/// 4xx/5xx response received from the server (excluding 401/404 which have
|
|
/// dedicated subclasses). Distinguished from `NetworkException` (connection
|
|
/// failure) so the offline write queue can drop non-retryable failures
|
|
/// instead of looping forever on a 422.
|
|
class ServerException extends AppException {
|
|
final int statusCode;
|
|
const ServerException(super.message, this.statusCode);
|
|
}
|