Incremental context for the Ralph Loop. Each iteration appends an entry below.
What: App shell with SwiftData persistence, Inbox CRUD, item detail editing.
Files created:
- Models:
DoitItem,Tag,RepeatRule,Heading,Project(full schema, P1 stubs included) - Views:
InboxView,DoitItemRow,ItemDetailView - ViewModels:
InboxViewModel,ItemDetailViewModel - Services:
PersistenceService(with App Group variant for future widget/extension) - App:
DoitAppentry point,AppConstants - Extensions:
Date+Helpers
Architecture: MVVM with @Observable, SwiftData @Model, @Query for list fetches, @Bindable for detail editing.
Next: Phase 2 — Tags, This Evening, Today View, Sidebar navigation.
What: Sidebar navigation, Today view, This Evening sections, full tag system.
Files created:
Views/Sidebar/SidebarView.swift—NavigationSplitViewwith Inbox, Today, and tag filter linksViews/Today/TodayView.swift— smart list: overdue, today, and evening sectionsViews/Tags/TagListView.swift— tag CRUD with color pickerViews/Tags/TagFilterView.swift— filtered item list by tagViewModels/TagViewModel.swift— tag create/delete/toggle on items
Files updated:
DoitApp.swift— root switched fromNavigationStacktoSidebarViewInboxView.swift— split into main + "This Evening" sections, swipe actions for evening toggleDoitItemRow.swift— moon icon for evening itemsItemDetailView.swift— tag picker section with checkmarks
Decisions:
- Sidebar uses
NavigationSplitView(collapses to stack on iPhone) - This Evening is a section within views, not a standalone screen
- Tags use inline toggle in item detail (checkmark list, not sheet)
- Swipe left to move item to evening, swipe right to delete
Next: Phase 3 — Reminders (local notifications), Repeaters UI, Calendar integration (EventKit).
What: Local notification reminders, repeating to-dos, and EventKit calendar integration.
Files created:
Services/NotificationService.swift— singleton wrappingUNUserNotificationCenterfor scheduling/canceling notificationsServices/CalendarService.swift— singleton wrappingEKEventStorefor fetching calendar eventsViews/ItemDetail/RepeatRuleEditor.swift— subview for configuring repeat rules (frequency, interval, day-of-week, end date)Views/Calendar/CalendarView.swift— unified timeline showing calendar events + to-dos with horizontal date stripViewModels/CalendarViewModel.swift— manages selected date, calendar access, and event fetching
Files updated:
ItemDetailView.swift— added Reminder section (toggle + date/time picker) and Repeat section (toggle + RepeatRuleEditor)ItemDetailViewModel.swift— addedsetReminder,clearReminder,updateRemindermethodsInboxViewModel.swift—toggleCompletenow auto-creates next occurrence for repeating itemsSidebarView.swift— added Calendar destination to sidebar navigationDoitApp.swift— addedAppDelegatewithUNUserNotificationCenterDelegatefor foreground notificationsproject.pbxproj— addedNSCalendarsFullAccessUsageDescriptionInfo.plist key
Decisions:
- Completing a repeating item creates a new item (original stays completed for future Logbook)
- Reminder offset relative to scheduled date is preserved when creating next repeat occurrence
- Calendar uses horizontal week strip (not full month grid) for MVP simplicity
- Notification permission is requested on first visit to item detail; calendar permission on first visit to Calendar view
- NotificationService and CalendarService are singletons (wrapping system frameworks)
- RepeatRuleEditor extracted as a subview to keep ItemDetailView manageable
Next: Phase 4 — Quick Entry, Quick Find, Type Travel, Widgets, or other P0 features from backlog.
What: Calendar events from EventKit were only shown in CalendarView and the Today section of TodayView. They never appeared in InboxView or the "This Evening" section of either view.
Root cause: InboxView only queried SwiftData DoitItem via @Query. TodayView fetched EKEvent objects but lumped them all into the "Today" section without splitting by time of day.
Fix:
- Both InboxView and TodayView now reference
CalendarService.sharedand callfetchEvents(for: Date())on appear - Events are split using a 5 PM cutoff: all-day and pre-5 PM events go in the main section, 5 PM+ events go in "This Evening"
- Event rows use the same calendar-colored circle + time format as CalendarView
Files updated:
Views/Inbox/InboxView.swift— added EventKit import, CalendarService state, daytime/evening event computed properties, event row rendering, loadTodayEvents on appearViews/Today/TodayView.swift— replaced singletodayEventswithdaytimeEvents/eveningEventssplit, evening events now render in This Evening section
What: Reminders can now repeat at a configurable interval if the to-do isn't marked done. Pre-schedules up to 20 follow-up notifications so they fire even if the app isn't running.
Files updated:
Models/DoitItem.swift— addedreminderRepeatInterval: TimeInterval?propertyServices/NotificationService.swift— schedules base + up to 20 follow-up notifications using the nag interval; bulk-cancels all on clearViewModels/ItemDetailViewModel.swift—clearReminder()now also clearsreminderRepeatIntervalViews/ItemDetail/ItemDetailView.swift— added "Repeat Reminder" picker (Never / 5m / 15m / 30m / 1h / 1d) below the reminder date pickerViewModels/InboxViewModel.swift—toggleComplete()cancels all pending notifications;createNextOccurrencepreserves nag interval
Decisions:
- Pre-schedule approach (up to 20 follow-ups) chosen over notification-delegate rescheduling so nagging works without the app running
- iOS allows 64 pending notifications; 20 follow-ups per item is a reasonable cap
- Nag interval is cleared when the reminder toggle is turned off
- Completing an item cancels all its pending notifications immediately
Next: PM review and testing
What: Two P0 features — Quick Entry (create a to-do from anywhere in the app) and Quick Find (instant search across to-dos and tags).
Files created:
Views/QuickEntry/QuickEntryView.swift— half-height sheet with title field, optional schedule date, tag picker, Add/Cancel toolbarViews/QuickFind/QuickFindView.swift— full sheet with.searchabletext field, results split into Tags and To-Dos sections, navigation to detail/filter viewsViewModels/QuickFindViewModel.swift—@ObservableVM querying SwiftData withlocalizedStandardContainspredicates, 20-item limit
Files updated:
Views/Sidebar/SidebarView.swift— added toolbar buttons (magnifying glass for Quick Find, + for Quick Entry) and two.sheet()modifiersDoit.xcodeproj/project.pbxproj— added new files and groups
Decisions:
- Quick Entry presented as sheet from sidebar level so it's accessible from any screen
- Quick Entry supports title + optional date + optional tags (matches existing
addItempattern) - Quick Find uses SwiftUI
.searchablefor native search bar UX - Search queries both
DoitItem.title/.notesandTag.name - Results cap at 20 items to keep UI responsive
- Tapping a search result navigates to ItemDetailView or TagFilterView within the sheet's NavigationStack
PM Feedback: Quick Entry was missing fields compared to Item Detail. Updated to include Notes, Reminder (with repeat interval), Repeat Rule, and inline New Tag creation. Also defaulted the sheet to full height (.large detent) so nothing gets cut off.
Status: Merged to main. Quick Entry and Quick Find both complete.
Next: Remaining P0s: Type Travel, Widgets, Mail to Doit, Markdown
What: Home Screen and Lock Screen widgets showing upcoming to-dos for today.
Files created:
DoitWidgets/DoitWidgets.swift— Widget bundle entry point (@main WidgetBundle)DoitWidgets/DoitWidgetEntry.swift—TimelineEntrywith lightweightWidgetItemstructsDoitWidgets/DoitWidgetProvider.swift—TimelineProviderquerying shared SwiftData container, 30-min refreshDoitWidgets/HomeScreenWidget.swift— Home Screen widget views (small/medium/large families)DoitWidgets/LockScreenWidget.swift— Lock Screen widget views (inline/circular/rectangular)
Files updated:
Doit/App/DoitApp.swift— switched tomakeSharedContainer(), added deep link handling viaonOpenURL, widget timeline reload on foregroundDoit/ViewModels/InboxViewModel.swift—WidgetCenter.shared.reloadAllTimelines()on add/completeDoit/ViewModels/ItemDetailViewModel.swift—WidgetCenter.shared.reloadAllTimelines()on save/deleteDoit.xcodeproj/project.pbxproj— added DoitWidgetsExtension target with shared model/service files
Decisions:
- Main app uses shared App Group container (
group.com.dachi.doit) so widgets read the same SwiftData database - Widget extension shares model files (DoitItem, Tag, Heading, Project, RepeatRule) + PersistenceService + AppConstants + Date+Helpers via target membership
- TimelineProvider fetches all incomplete items — today/overdue first, then unscheduled inbox items
- Deep links:
doit://todayopens Today view,doit://item/{uuid}opens item detail - Home Screen small shows count + next item, medium shows 4 items, large shows 8 items
- Lock Screen inline shows next item title, circular shows count, rectangular shows 2-3 items
- Bundle IDs changed to
com.dachi.doit/com.dachi.doit.widgetsfor personal team signing - Automatic code signing required for App Groups to function;
CODE_SIGN_STYLE = Automatic PersistenceService.makeSharedContainer()has fallback to default location when App Group unavailable- Widget Info.plist with
NSExtensionPointIdentifierrequired for simulator install
Status: Complete and merged to main.
Next: Remaining P0s: Type Travel, Mail to Doit, Markdown
What: Nested sub-tasks within a to-do. Each subtask has its own title and completion state. Parent items show a progress circle in list views based on subtask completion.
Files updated:
Models/DoitItem.swift— addedsubtasksrelationship (@Relationship(deleteRule: .cascade)),parentItemback-reference, computedhasSubtasks,completedSubtaskCount,subtaskProgressViews/ItemDetail/ItemDetailView.swift— Subtasks section (checkbox list, inline add, completion count), "Parent Task" nav link for subtasks, Repeat section hidden for subtasksViewModels/ItemDetailViewModel.swift— addedaddSubtask(title:),deleteSubtask(_:),toggleSubtaskComplete(_:)methodsViews/Components/DoitItemRow.swift— progress circle replaces checkbox when item has subtasks; shows "X/Y" count below title; parent task name shown above subtask titleViews/Inbox/InboxView.swift—@Querypredicate filtersparentItem == nilViews/Today/TodayView.swift— noparentItemfilter (scheduled subtasks appear in Today)Views/Calendar/CalendarView.swift— noparentItemfilter (scheduled subtasks appear in Calendar)ViewModels/QuickFindViewModel.swift— fetch predicate filtersparentItem == nilViewModels/InboxViewModel.swift—createNextOccurrencecopies subtasks (reset to incomplete)DoitWidgets/DoitWidgetProvider.swift— fetch predicates filterparentItem == nilBACKLOG.md— added Subtasks as P0, removed P1 "Checklists within to-dos" (superseded)
Decisions:
- Subtasks reuse
DoitItemmodel with self-referential relationship (no new model type) - Cascade delete: deleting parent removes all subtasks
- Parent and subtask completion are independent (completing parent doesn't auto-complete subtasks)
- Progress circle uses
Circle().trim()with accent color stroke; checkmark shown inside when parent is completed - Subtasks hidden in Inbox (not standalone); but scheduled subtasks appear in Today and Calendar views with parent name
- Subtask detail view shows "Parent Task" link and hides Repeat section
- Subtasks can have their own notes, schedule, reminders, and tags (full ItemDetailView reuse)
- Repeating items copy subtasks to next occurrence (reset to incomplete)
- No widget changes needed (widgets show parent items only)
Status: Complete. Merged to main.
What: Folders feature — organize related tasks into named, colored folders with custom icons. Each item can belong to at most one folder.
Files created:
Doit/Models/Folder.swift—@Modelwithid,name,colorHex,iconName,sortOrder,createdAt, one-to-manyitemsrelationship, 20 built-in productivity iconsDoit/ViewModels/FolderViewModel.swift—@ObservableVM withaddFolder(),deleteFolder(),moveItem()Doit/Views/Folders/FolderListView.swift— CRUD for folders (create, rename, edit color/icon, delete), mirrors TagListViewDoit/Views/Folders/FolderFilterView.swift— filtered item list by folder, mirrors TagFilterViewDoit/Views/Components/FolderIconPicker.swift— grid picker with 20 productivity SF Symbols
Files updated:
Doit/Models/DoitItem.swift— addedfolder: Folder?propertyDoit/Services/PersistenceService.swift— addedFolder.selfto schemaDoit/Views/Sidebar/SidebarView.swift— addedfolderFilter(Folder)destination, "Folders" section in sidebar, "Manage Folders" toolbar linkDoit/Views/ItemDetail/ItemDetailView.swift— added "Folder" section with single-select picker (None + all folders)Doit/Views/QuickEntry/QuickEntryView.swift— added folder picker section (shown when folders exist)Doit.xcodeproj/project.pbxproj— added new files to Doit and DoitWidgets targets
Decisions:
- Folders are single-select (item in ≤1 folder) vs. tags which are many-to-many
- Folder picker uses checkmark list (same pattern as tags) with "None" option at top
- Folder.swift added to widget extension target membership so shared SwiftData schema compiles
- Folders section appears in sidebar between main nav (Inbox/Today/Calendar) and Tags
- Deleting a folder removes folder assignment from all its items (items are not deleted)
- 20 custom icons: folder, briefcase, house, person, heart, figure.run, dollar, cart, airplane, book, code, paintbrush, music, camera, envelope, phone, star, flag, bolt, gear
Status: Complete. Merged to main.
What: Replaced local-only SwiftData persistence with Supabase (hosted PostgreSQL + Auth). Added user authentication with Sign in with Apple, Google, Email, and Phone Number. All data is tied to authenticated users via RLS policies. Local SwiftData kept as read cache for widgets and @Query-driven UI.
Architecture: Supabase-first writes, local SwiftData cache for reads. Views still use @Query. ViewModels call SupabaseDataService (async) for mutations, which writes to Supabase then updates local cache on success.
Files created:
Doit/App/SupabaseConfig.swift— Supabase project URL + anon keyDoit/Services/AuthService.swift—@Observablesingleton with Apple/Google/Email/Phone auth via supabase-swiftDoit/Services/SupabaseDataService.swift— async CRUD for items, tags, headings, projects + local cache syncDoit/Services/SyncService.swift— full sync on login/foreground, stale detection (5 min)Doit/Models/SupabaseDTO/DoitItemDTO.swift— Codable DTO with snake_case mappingDoit/Models/SupabaseDTO/TagDTO.swift— tag DTODoit/Models/SupabaseDTO/HeadingDTO.swift— heading DTODoit/Models/SupabaseDTO/ProjectDTO.swift— project DTODoit/Models/SupabaseDTO/ItemTagDTO.swift— junction table DTODoit/Views/Auth/AuthView.swift— auth gate with all 4 sign-in methodsDoit/Views/Auth/PhoneVerificationView.swift— OTP code entrySupabase/migrations/001_initial_schema.sql— full schema: tables, indexes, RLS policies
Files updated:
project.yml— added supabase-swift and GoogleSignIn-iOS SPM packagesDoit/App/DoitApp.swift— auth gate (loading/auth/main), Google Sign-In URL handler, foreground syncDoit/ViewModels/InboxViewModel.swift— async methods calling SupabaseDataServiceDoit/ViewModels/ItemDetailViewModel.swift— async save (debounced 1s), delete, tag updatesDoit/ViewModels/TagViewModel.swift— async add/delete/toggle with server syncDoit/Views/Inbox/InboxView.swift— Task wrappers for async mutationsDoit/Views/Today/TodayView.swift— Task wrappersDoit/Views/Calendar/CalendarView.swift— Task wrappersDoit/Views/Tags/TagFilterView.swift— Task wrappersDoit/Views/Tags/TagListView.swift— Task wrappersDoit/Views/ItemDetail/ItemDetailView.swift— Task wrappers for tag toggle, deleteDoit/Views/QuickEntry/QuickEntryView.swift— server sync on add item/tagDoit/Views/Sidebar/SidebarView.swift— sign-out button in toolbarBACKLOG.md— added Authentication & PostgreSQL Backend as P0
Decisions:
- Supabase chosen for hosted PostgreSQL + built-in auth (Apple, Google, Email, Phone all supported natively)
- Local SwiftData kept as read cache so @Query in views and widget extension continue working unchanged
- Writes go to Supabase first; local cache updated on success (no offline write queue for MVP)
- repeat_rule_data stored as JSONB in PostgreSQL (not opaque Data blob)
- Many-to-many Tag/Item via
item_tagsjunction table with RLS checking item ownership - ItemDetailViewModel.save() debounced to 1s to avoid network spam on keystroke
- Widget reads from SwiftData cache — no changes needed to widget extension
- SupabaseConfig uses placeholder credentials (TODO: replace with real project URL/key)
Status: Complete. PM approved. Email sign-up/sign-in tested end-to-end (account creation, item CRUD). No confirmation email was required (Supabase email confirmation may be disabled). Apple, Google, and Phone auth NOT yet tested — providers need enabling in Supabase dashboard. Google Sign-In deferred (no OAuth client ID yet).
Next: Remaining P0s: Type Travel, Mail to Doit, Markdown
What: Full dark appearance support with user-facing System/Light/Dark toggle in Settings.
Files created:
Doit/Services/AppearanceManager.swift—@Observablesingleton storing appearance preference (system/light/dark) in UserDefaults, exposescolorScheme: ColorScheme?Doit/Views/Settings/SettingsView.swift— Settings screen with inline appearance picker and Sign Out button (moved from sidebar toolbar)
Files updated:
Doit/App/DoitApp.swift— addedAppearanceManagerstate, applied.preferredColorScheme()to root WindowGroupDoit/Views/Sidebar/SidebarView.swift— added.settingsdestination, replaced Sign Out toolbar button with Settings gear icon linking to SettingsViewDoit/Views/Auth/AuthView.swift— changed Sign in with Apple button style from.blackto.automatic(adapts to dark mode)BACKLOG.md— moved Dark Mode from P2 to P1
Decisions:
- Most existing views already use semantic/system colors (
.primary,.secondary,Color(.systemGroupedBackground)) — no changes needed .foregroundStyle(.white)on calendar event circles and FAB are intentional (white on colored backgrounds works in both modes)- AppearanceManager uses UserDefaults (not SwiftData) since it's a simple app-level preference
- Settings view consolidates account management (Sign Out) with appearance — room for future settings
- Three-option picker: System (follows device), Light (forced), Dark (forced)
Status: Complete. Ready for PM review.
What: Over-the-air configuration system so app behavior can be changed from Supabase without requiring an App Store update. Includes feature flags, force-update version gating, maintenance mode, and announcement banners.
Files created:
Supabase/migrations/002_remote_config.sql—remote_configtable with key-value JSONB storage, RLS (read-only for authenticated users), seeded defaults for min_app_version, maintenance_mode, maintenance_message, announcement, feature_flagsDoit/Services/RemoteConfigService.swift—@Observablesingleton that fetches config from Supabase, caches to UserDefaults for offline use, provides typed accessors for all config values and feature flagsDoit/Models/SupabaseDTO/RemoteConfigDTO.swift— Codable DTO for decoding JSONB config rowsDoit/Views/RemoteConfig/ForceUpdateView.swift— Full-screen view prompting user to update when app version is belowmin_app_versionDoit/Views/RemoteConfig/MaintenanceView.swift— Full-screen maintenance message with retry buttonDoit/Views/RemoteConfig/AnnouncementBanner.swift— Dismissable banner shown at top of app for global announcements
Files updated:
Doit/App/DoitApp.swift— auth gate now checks remote config: force-update → maintenance → announcement banner → main app. Config fetched on launch and on foreground (if stale). Also fetched on auth screen (pre-login) for force-update checksDoit/App/AppConstants.swift— addedappVersionandbuildNumbercomputed propertiesDoit/Views/Settings/SettingsView.swift— added "About" section showing app version, build number, and last config fetch timeBACKLOG.md— added Remote Config (OTA Updates) as P0Doit.xcodeproj/project.pbxproj— added new files and RemoteConfig view group
Decisions:
- Config stored as key-value pairs in
remote_configtable (not per-user — global config) - RLS allows all authenticated users to read; writes only via Supabase dashboard/admin
- UserDefaults cache ensures app works offline — cached config loads in
init()before any network call - Force-update and maintenance only enforced with "fresh" config (< 24 hours old) — stale offline cache won't lock users out
- Feature flags default to
trueif a flag key is missing (fail-open, not fail-closed) - Announcement banner dismissable per-session; reappears if announcement text changes
- Config re-fetched every 5 minutes (same cadence as data sync)
- Seeded feature flags cover all major features (tags, folders, subtasks, calendar, widgets, etc.)
How to use (from Supabase dashboard):
- Force update:
UPDATE remote_config SET value = '"2.0.0"' WHERE key = 'min_app_version'; - Maintenance mode:
UPDATE remote_config SET value = 'true' WHERE key = 'maintenance_mode'; - Announcement:
UPDATE remote_config SET value = '"We have exciting new features!"' WHERE key = 'announcement'; - Disable a feature:
UPDATE remote_config SET value = jsonb_set(value, '{folders}', 'false') WHERE key = 'feature_flags';
Status: Complete. Ready for PM review.
What: Full rewrite of the SwiftUI app in React Native (Expo) to enable true over-the-air code updates via EAS Updates. Replaces Swift code in-place. Same Supabase backend. Full feature parity target.
Tech stack: Expo SDK 55, expo-router v5, TypeScript, Zustand + AsyncStorage persist, @supabase/supabase-js, @shopify/flash-list, @gorhom/bottom-sheet, react-native-gesture-handler, expo-notifications, expo-calendar, expo-apple-authentication, @react-native-google-signin/google-signin, @react-native-community/datetimepicker, react-native-svg, expo-updates (EAS).
Files created (stores):
src/stores/authStore.ts— Zustand: session restore, Apple/Google/Email/Phone authsrc/stores/itemStore.ts— Zustand + persist: items[], itemTags[], CRUD, toggleComplete with repeat-rule next occurrencesrc/stores/tagStore.ts— Zustand + persist: tags[], CRUDsrc/stores/folderStore.ts— Zustand + persist: folders[], CRUDsrc/stores/remoteConfigStore.ts— Zustand + persist: remote config values, fetch, feature flags
Files created (screens):
app/_layout.tsx— Root: GestureHandlerRootView, auth restoreapp/(auth)/_layout.tsx,sign-in.tsx,phone-verify.tsx— Auth flow (Apple/Google/Email/Phone + OTP)app/(app)/_layout.tsx— Drawer navigator with custom drawer content (dynamic folders/tags), remote config gatesapp/(app)/inbox.tsx— FlatList, inline add, swipe-to-delete, searchapp/(app)/today.tsx— SectionList: Overdue/Today/This Eveningapp/(app)/calendar.tsx— Month/week grid, expo-calendar events, merged timelineapp/(app)/item/[id].tsx— Full detail: title, notes, subtasks, schedule, reminder, tags, folder, deleteapp/(app)/settings.tsx— Manage links, version info, sign outapp/(app)/manage-folders.tsx— Folder CRUD with icon + color pickerapp/(app)/manage-tags.tsx— Tag CRUD with color pickerapp/(app)/folder/[id].tsx— Folder filter listapp/(app)/tag/[id].tsx— Tag filter list
Files created (components):
src/components/DoitItemRow.tsx— Task row with checkbox/progress circle, metadata, tag dotssrc/components/AnnouncementBanner.tsx,ForceUpdateView.tsx,MaintenanceView.tsx— Remote config UIsrc/components/QuickEntrySheet.tsx— Bottom sheet for quick task creationsrc/components/QuickFindModal.tsx— Search modal for items + tags
Files created (lib/utils/services):
src/lib/supabase.ts— Client init with AsyncStorage auth persistencesrc/lib/constants.ts— App version, colors, icons, reminder intervalssrc/types/index.ts— Full type system with DB row types + mapperssrc/utils/dateHelpers.ts,repeatRule.ts,versionCompare.tssrc/services/syncService.ts— fullSync, syncIfStale (5-min threshold)
Files created (config):
package.json,app.json,eas.json,tsconfig.jsonSupabase/migrations/003_folders_and_subtasks.sql— folders table + parent_item_id column
Bugs fixed:
- Missing
Platformimport inphone-verify.tsx - Missing dependencies:
uuid,react-native-get-random-values,react-native-svg,@react-navigation/drawer,@types/uuid - Expo SDK 55 package versions (were using old ~7.x/~14.x instead of ~55.x)
- TypeScript errors:
calendarColorcast,ItemTagRowfield names, Ionicons icon name, drawer props typing
Status: All screens, components, stores, and services created. TypeScript compiles cleanly (tsc --noEmit passes with 0 errors). Dependencies install successfully.
Next: PM review. Then: Phase 5 (notifications service), Phase 6 (polish/animations), Phase 7 (widgets config plugin), Phase 8 (EAS Updates testing), Phase 9 (Jest + Maestro tests).
What: All mutations (create/update/delete items, tags, folders, item tags) now update the UI instantly (optimistic) and enqueue the Supabase call for background processing. Works fully offline — queue persists to AsyncStorage and drains when connectivity returns.
Files created:
src/stores/syncQueueStore.ts— Zustand + persist: persistent queue ofQueueEntryobjects with enqueue (with collapse/dedup), processQueue (FIFO with retry/backoff), removeEntry, clearQueuesrc/hooks/useNetworkMonitor.ts— NetInfo listener: drains queue + runs fullSync on reconnect, processes pending queue on mount
Files updated:
src/types/index.ts— addedQueueEntryinterfacesrc/stores/itemStore.ts— all mutations now synchronous (optimistic local update + enqueue). Replacedsupabase.auth.getUser()with cacheduseAuthStore.getState().user?.idsrc/stores/tagStore.ts— same optimistic + enqueue patternsrc/stores/folderStore.ts— same optimistic + enqueue patternsrc/services/syncService.ts—fullSync()now drains queue first before pulling server stateapp/(app)/_layout.tsx— mountsuseNetworkMonitor, processes queue on authapp/(app)/inbox.tsx— fixed infinite render loop (moved filtered items touseMemo)package.json— added@react-native-community/netinfo
Queue collapse rules:
- insert + update → merge into insert
- insert + delete → remove both (never reached server)
- update + update → replace with latest
- update + delete → replace with delete
Conflict resolution: Last-write-wins via modifiedAt. upsert for inserts (idempotent). Max 5 retries with exponential backoff. Auth errors stop processing.
Status: Complete. Ready for PM review.