Enhance offline resilience, caching, and SQLite persistence#447
Draft
kevintran-git wants to merge 11 commits into
Draft
Enhance offline resilience, caching, and SQLite persistence#447kevintran-git wants to merge 11 commits into
kevintran-git wants to merge 11 commits into
Conversation
Add network-resilience and cold-start improvements: track server reachability in AuthState, run background /health probes, retry on connectivity changes, and expose a non-blocking server reachability banner. Instrument cold-start with a StartupTimeline and record key markers. Add granular local caches and persistence: per-conversation cache, per-file-info cache, and composer drafts in OptimizedStorageService; hydrate/cache conversations in providers and warm caches on conversation load. Use cached FileInfo for sends to avoid extra network round-trips and warm the cache on upload/response. Optimize send flow: parallelize new-conversation creation with attachment metadata fetches, include outboundTaskId metadata for queued sends, and wire message delivery status via a new messageDeliveryStatus provider. Surface per-message delivery badges in the UI with retry support. Composer & UI tweaks: persist/hydrate drafts in ModernChatInput (debounced saves, cross-chat switching), show startup/chat-first-paint markers, add server reconnect banner to ChatPage, and replace drawer loading spinner with skeleton placeholders. Clean up storage on reset to remove new caches and drafts.
Introduce SQLite-backed storage for conversations and messages: add AppDatabase (schema, indices, in-memory open for tests) and ConversationStore (CRUD, upsert, append/update/delete message, previews). Migrate persistence flow: bump migrator target to v2 and add logic to move per-conversation Hive blobs into SQLite (best-effort, deletes migrated keys). Wire the new DB into app startup and Riverpod providers; update OptimizedStorageService to delegate conversation operations to ConversationStore. Add unit tests for ConversationStore and PersistenceMigrator (sqflite_common_ffi test setup). Add sqflite and test ffi dependency and minor Android build.gradle tweak to fallback to debug signing if no keystore. Debug/logging preserved for migration and DB operations.
Release builds without keystore.properties were silently producing unsigned APKs that wouldn't install. Use the debug signing config as the fallback so local release builds still work end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two additions to the local-first persistence layer: 1. ConversationStore.upsertMessageEnsuringConversation — tolerant message upsert that scaffolds a header row if the conversation doesn't yet exist. Lets the chat send/streaming path persist a single message without first round-tripping the entire conversation. Wrapped in a transaction to avoid races between parallel sends. 2. SQLite schema v2 with an FTS5 mirror of message bodies. Triggers keep messages_fts in sync with messages so search results follow inserts/updates/deletes automatically. searchConversations() unions title-LIKE matches with FTS5 prefix matches, dedupes by id, and sorts pinned + recency. User input is sanitized so FTS operators can't be injected; falls back to title-only on FTS errors. Existing v1 installs get backfilled on first open. OptimizedStorageService gains pass-throughs (persistMessageEnsuring- Conversation, persistUpdatedMessage, persistMessageDeletion, searchConversationsLocal) so callers in feature code don't reach into the store directly. All wrapped in try/catch — persistence failure logs but never breaks the calling flow. 15 new conversation_store tests cover upsert + FTS5 paths (round-trip, prefix match, dedupe, archived exclusion, syntax sanitization, trigger sync on update/delete). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… deletes Builds on the new persistence APIs to deliver four user-visible local-first behaviors in the chat surface. Phase 3b — granular SQLite writes during send: Every meaningful state transition in the send pipeline now persists to SQLite as it happens. The user message lands as soon as the conversation has a real id; the assistant message is throttled to ~2s during streaming via the existing buffer-sync timer, with a guaranteed final write at completion. Error states persist too so a failed send survives a restart with its error populated. Reviewer mode and temporary chats are skipped via _shouldPersistGranular. All persists are unawaited and try/catch-guarded — disk never blocks the UI or breaks the stream state machine. Phase 4a — stuck-streaming recovery: refreshActiveConversationFromServer drops stale transport state and pulls the server's authoritative copy, recovering messages whose generation completed server-side after the client disconnected. Triggered by an always-visible app bar refresh button, the existing pull-to-refresh, and a foreground-resume observer (chatLifecycle- Provider) that fires only when there's a stuck isStreaming placeholder. _manualRefreshInFlight guards against double-taps. Phase 4b — local-first drawer search: localSearchProvider hits the SQLite FTS5 index in tens of ms; the drawer renders those results immediately and merges in the server result asynchronously by id, preferring the local payload. Spinner only when both sources are loading and we have nothing to display; "no results" only after the server has settled, so cached hits never get masked by a transient false negative. Phase 4c — message deletion: deleteMessages performs an optimistic local removal (chat list, active conversation, drawer summary, SQLite rows) then best-effort syncs the new authoritative message list to the server. Server failure logs but does not roll back — the next refresh reconciles. Wired up to the existing multi-select delete UI in chat_page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Provides an escape hatch to drop cached conversations, file metadata, and unsent drafts without signing out. Sits above Sign Out in the profile page since both are destructive account actions; clearing invalidates the conversations providers so the drawer reloads immediately. Covers the case where a user wants to free disk or recover from a stale cache without losing their session — useful before LRU eviction ships. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Local-first chat: instant cold start, offline send queue, granular SQLite writes during streaming, FTS5 search, app-bar refresh, and manual cache clear all land here. Worth a minor bump from 2.6.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Android's system SQLite omits the FTS5 module, causing a crash at startup because the db schema is created before runApp() is called. sqflite_common_ffi + sqlite3_flutter_libs bundle a modern SQLite (3.52.0) compiled with FTS5, replacing the system library on Android. Also: - Bump schema from v2→v3; upgrade path drops any partial FTS5 artifacts from failed v2 installs (DROP TABLE IF EXISTS) and recreates the FTS table cleanly. - _toFtsMatchExpression now returns '' when all tokens are stripped, with an early-return in searchConversations instead of passing an invalid sentinel to MATCH. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Owner
|
@kevintran-git this is an interesting PR but massive. Could you please open an issue describing the problem it will solve first? I will look into implementing it myself as it requires extensive testing. Thank you for the effort! |
Improve local-first resilience across auth, storage, task queue and send path: - Auth: avoid full-screen error on transient failures if a valid token exists; toggle a serverReachable flag instead of demoting to AuthStatus.error for mid-session 401/403 and retry exhaustion. Keep state when possible so UI remains usable. - Persistence: add ConversationStore.replaceAllConversations and make upsert preserve messages marked metadata.localPending (protect queued sends from passive syncs). Use insert-or-update for conversation headers to avoid FK CASCADE wiping protected messages. - Hive: add cachedUserSystemPrompt key and OptimizedStorageService helpers (replaceLocalConversations, get/setCachedUserSystemPrompt) for synchronous read/warm cache of user system prompt used in hot send path. - Chat send: make sends idempotent on retries by reusing original message IDs when outboundTaskId is present; mark queued messages with localPending and persist them early to avoid vanishing UI; prefer cached system prompt synchronously and refresh in background; classify send errors into auth/permanent/transient and map behavior (auth → trigger auth flow, permanent → surface error and stop retries, transient → keep for TaskQueue retry). Clear localPending after successful dispatch and re-persist. - Task queue & tasks: add OutboundTask.failureError and failedPermanently helpers; on connectivity restore cancel backoffs and revive tasks that exhausted retries while offline (but leave permanent failures alone); refine load/save filters and retention so transient failures can be revived. - Tests: add unit tests for ConversationStore localPending preservation and replaceAllConversations, and tests for OutboundTask.failedPermanently. These changes aim to make the app more robust to intermittent network/server issues, avoid surprising UX (vanishing messages or blocking full-screen errors), and ensure queued sends survive reconnects and restarts.
Replace the previous Hive/task-queue outbox with a SQLite-backed outbox and worker. Bump DB schema to v4 and add send_status/send_attempt/send_next_at/send_error columns + index and migration. Add MessageSendStatus enum, PendingMessage model and numerous ConversationStore APIs (upsert/insertMessageAsSending, markSending, markSent, scheduleRetry, markPermanentFailed, cancelPending, getSendStatus, pendingMessages, pendingMessageIdsByConversation) to manage per-row outbound state. Introduce MessageOutbox provider to drive delivery, backoff and connectivity handling; wire it into chat send flows so sends are persisted as 'sending' rows and retried from SQLite. Update chat providers and send logic to reuse pending rows on retry, merge pending outbox rows into server snapshots, and flip rows to 'sent' or schedule retries on errors. Adjust UI: delivery badge now reads from DB status and triggers markSending + outbox.kick on manual retry; chat page and user message bubble now dispatch directly (no Hive enqueue). Add OWUI chats import in profile and small chats drawer date-grouping. Update tests accordingly and remove an obsolete outbound_task_test.
Allow local-first new chats and reconcile them with server-assigned ids after createConversation completes. Added ConversationStore.renameConversation to atomically remap a conversation primary key and its child messages in a single SQLite transaction. Persist local placeholders to SQLite up-front so streaming writes have a stable parent, and run /api/v1/chats/new concurrently instead of gating the stream. Also added a best-effort _reconcileNewChatId flow that waits for the server conversation, renames SQLite rows, swaps the active conversation provider if still active, upserts the sidebar entry, refreshes caches, and pushes the final message list to the server via syncConversationMessages. Errors are swallowed as this is a best-effort cleanup. Introduced a cachedUserSystemPromptUpdatedAt Hive key plus storage helpers: setCachedUserSystemPrompt now writes/deletes an updated-at timestamp and getCachedUserSystemPromptUpdatedAt() returns a DateTime. Chat send logic now uses the cached timestamp (5-minute freshness) to skip or background-refresh /user/settings to avoid extra per-send HTTP work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces significant improvements to authentication state management and database persistence. The main highlights are a more resilient, "local-first" authentication flow that allows the UI to render cached user data before network checks complete, and the introduction of a new SQLite database layer with full-text search support and robust migration logic. These changes improve startup speed, offline usability, and data migration reliability.
Authentication state management:
lib/core/auth/auth_state_manager.dart[1] [2] [3] [4] [5] [6] [7] [8]serverReachableflag toAuthState, with new logic to update this flag based on background/healthprobes and network connectivity changes, enabling the UI to reactively display reconnect banners. (lib/core/auth/auth_state_manager.dart[1] [2] [3] [4] [5] [6] [7]_probeReachabilityAndValidate,probeServerReachability) for clearer logic and easier manual retry from the UI. (lib/core/auth/auth_state_manager.dartlib/core/auth/auth_state_manager.dartR738-R789)lib/core/auth/auth_state_manager.dartlib/core/auth/auth_state_manager.dartR190-R201)Database and persistence:
AppDatabaseclass using SQLite (viasqfliteand FFI), with schema and migration logic supporting full-text search (FTS5) on message content for offline search. This replaces previous per-conversation Hive blobs, improving scalability and searchability. (lib/core/persistence/database/app_database.dartlib/core/persistence/database/app_database.dartR1-R206)PersistenceMigratorto handle migrations from SharedPreferences to Hive (v1), and from Hive to SQLite (v2), with a new target version and improved testability. (lib/core/persistence/persistence_migrator.dartlib/core/persistence/persistence_migrator.dartR3-R35)Build configuration:
android/app/build.gradle.ktsandroid/app/build.gradle.ktsL56-R59)