feat(offline): tier 2 phase 2 — extend cache to tasks/projects/milestones/events/conversations

Phase 1 cached only notes. Phase 2 brings the read-through pattern to every
domain the app reads in bulk:

- Drift schema v2: 5 new cached_* tables + onUpgrade migration. Calendar
  events use range-scoped read/write (replaceEventsInRange) so disjoint
  month fetches don't clobber each other; milestones are per-project.
- Wrap TasksRepository, ProjectsRepository, MilestonesRepository, and
  ChatRepository (conversation list only) with NetworkException fallback.
  Writes hit the API then sync the cache.
- New EventsRepository wrapping EventsApi; calendar_provider and
  event_form_sheet repointed at the repository.
- OfflineBanner now surfaces getLatestSync() — most-recent timestamp
  across all domains — so the hint reads sensibly regardless of screen.

flutter analyze clean; existing tests pass. Phase 3 (offline write queue)
and Phase 4 (read-only UI indicators) still to come.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 21:22:06 -04:00
parent 6ef658558a
commit fe10067761
12 changed files with 5376 additions and 104 deletions
+24 -4
View File
@@ -18,6 +18,7 @@ import '../data/api/tasks_api.dart';
import '../data/local/database.dart';
import '../data/repositories/auth_repository.dart';
import '../data/repositories/chat_repository.dart';
import '../data/repositories/events_repository.dart';
import '../data/repositories/knowledge_repository.dart';
import '../data/repositories/voice_repository.dart';
import '../data/repositories/milestones_repository.dart';
@@ -82,15 +83,24 @@ final notesRepositoryProvider = Provider<NotesRepository>((ref) {
});
final tasksRepositoryProvider = Provider<TasksRepository>((ref) {
return TasksRepository(ref.watch(tasksApiProvider));
return TasksRepository(
ref.watch(tasksApiProvider),
ref.watch(fabledDatabaseProvider),
);
});
final chatRepositoryProvider = Provider<ChatRepository>((ref) {
return ChatRepository(ref.watch(chatApiProvider));
return ChatRepository(
ref.watch(chatApiProvider),
ref.watch(fabledDatabaseProvider),
);
});
final projectsRepositoryProvider = Provider<ProjectsRepository>((ref) {
return ProjectsRepository(ref.watch(projectsApiProvider));
return ProjectsRepository(
ref.watch(projectsApiProvider),
ref.watch(fabledDatabaseProvider),
);
});
final milestonesApiProvider = Provider<MilestonesApi>((ref) {
@@ -98,7 +108,10 @@ final milestonesApiProvider = Provider<MilestonesApi>((ref) {
});
final milestonesRepositoryProvider = Provider<MilestonesRepository>((ref) {
return MilestonesRepository(ref.watch(milestonesApiProvider));
return MilestonesRepository(
ref.watch(milestonesApiProvider),
ref.watch(fabledDatabaseProvider),
);
});
final knowledgeApiProvider = Provider<KnowledgeApi>((ref) {
@@ -128,3 +141,10 @@ final voiceRepositoryProvider = Provider<VoiceRepository>((ref) {
final eventsApiProvider = Provider<EventsApi>((ref) {
return EventsApi(ref.watch(dioProvider));
});
final eventsRepositoryProvider = Provider<EventsRepository>((ref) {
return EventsRepository(
ref.watch(eventsApiProvider),
ref.watch(fabledDatabaseProvider),
);
});