Skip to content

Latest commit

 

History

History
446 lines (339 loc) · 28.5 KB

File metadata and controls

446 lines (339 loc) · 28.5 KB

Context Log

Incremental context for the Ralph Loop. Each iteration appends an entry below.


Phase 1: Core Foundation (2026-03-13)

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: DoitApp entry 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.


Phase 2: Navigation, Tags, This Evening, Today (2026-03-14)

What: Sidebar navigation, Today view, This Evening sections, full tag system.

Files created:

  • Views/Sidebar/SidebarView.swiftNavigationSplitView with Inbox, Today, and tag filter links
  • Views/Today/TodayView.swift — smart list: overdue, today, and evening sections
  • Views/Tags/TagListView.swift — tag CRUD with color picker
  • Views/Tags/TagFilterView.swift — filtered item list by tag
  • ViewModels/TagViewModel.swift — tag create/delete/toggle on items

Files updated:

  • DoitApp.swift — root switched from NavigationStack to SidebarView
  • InboxView.swift — split into main + "This Evening" sections, swipe actions for evening toggle
  • DoitItemRow.swift — moon icon for evening items
  • ItemDetailView.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).


Phase 3: Reminders, Repeaters, Calendar Integration (2026-03-14)

What: Local notification reminders, repeating to-dos, and EventKit calendar integration.

Files created:

  • Services/NotificationService.swift — singleton wrapping UNUserNotificationCenter for scheduling/canceling notifications
  • Services/CalendarService.swift — singleton wrapping EKEventStore for fetching calendar events
  • Views/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 strip
  • ViewModels/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 — added setReminder, clearReminder, updateReminder methods
  • InboxViewModel.swifttoggleComplete now auto-creates next occurrence for repeating items
  • SidebarView.swift — added Calendar destination to sidebar navigation
  • DoitApp.swift — added AppDelegate with UNUserNotificationCenterDelegate for foreground notifications
  • project.pbxproj — added NSCalendarsFullAccessUsageDescription Info.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.


Fix: Calendar Events in Inbox & This Evening (2026-03-14)

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.shared and call fetchEvents(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 appear
  • Views/Today/TodayView.swift — replaced single todayEvents with daytimeEvents/eveningEvents split, evening events now render in This Evening section

Feature: Nagging Reminders (2026-03-14)

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 — added reminderRepeatInterval: TimeInterval? property
  • Services/NotificationService.swift — schedules base + up to 20 follow-up notifications using the nag interval; bulk-cancels all on clear
  • ViewModels/ItemDetailViewModel.swiftclearReminder() now also clears reminderRepeatInterval
  • Views/ItemDetail/ItemDetailView.swift — added "Repeat Reminder" picker (Never / 5m / 15m / 30m / 1h / 1d) below the reminder date picker
  • ViewModels/InboxViewModel.swifttoggleComplete() cancels all pending notifications; createNextOccurrence preserves 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


Phase 4: Quick Entry & Quick Find (2026-03-14)

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 toolbar
  • Views/QuickFind/QuickFindView.swift — full sheet with .searchable text field, results split into Tags and To-Dos sections, navigation to detail/filter views
  • ViewModels/QuickFindViewModel.swift@Observable VM querying SwiftData with localizedStandardContains predicates, 20-item limit

Files updated:

  • Views/Sidebar/SidebarView.swift — added toolbar buttons (magnifying glass for Quick Find, + for Quick Entry) and two .sheet() modifiers
  • Doit.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 addItem pattern)
  • Quick Find uses SwiftUI .searchable for native search bar UX
  • Search queries both DoitItem.title/.notes and Tag.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


Phase 5: Widgets (2026-03-15)

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.swiftTimelineEntry with lightweight WidgetItem structs
  • DoitWidgets/DoitWidgetProvider.swiftTimelineProvider querying shared SwiftData container, 30-min refresh
  • DoitWidgets/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 to makeSharedContainer(), added deep link handling via onOpenURL, widget timeline reload on foreground
  • Doit/ViewModels/InboxViewModel.swiftWidgetCenter.shared.reloadAllTimelines() on add/complete
  • Doit/ViewModels/ItemDetailViewModel.swiftWidgetCenter.shared.reloadAllTimelines() on save/delete
  • Doit.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://today opens 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.widgets for 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 NSExtensionPointIdentifier required for simulator install

Status: Complete and merged to main.

Next: Remaining P0s: Type Travel, Mail to Doit, Markdown


Feature: Subtasks (2026-03-16)

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 — added subtasks relationship (@Relationship(deleteRule: .cascade)), parentItem back-reference, computed hasSubtasks, completedSubtaskCount, subtaskProgress
  • Views/ItemDetail/ItemDetailView.swift — Subtasks section (checkbox list, inline add, completion count), "Parent Task" nav link for subtasks, Repeat section hidden for subtasks
  • ViewModels/ItemDetailViewModel.swift — added addSubtask(title:), deleteSubtask(_:), toggleSubtaskComplete(_:) methods
  • Views/Components/DoitItemRow.swift — progress circle replaces checkbox when item has subtasks; shows "X/Y" count below title; parent task name shown above subtask title
  • Views/Inbox/InboxView.swift@Query predicate filters parentItem == nil
  • Views/Today/TodayView.swift — no parentItem filter (scheduled subtasks appear in Today)
  • Views/Calendar/CalendarView.swift — no parentItem filter (scheduled subtasks appear in Calendar)
  • ViewModels/QuickFindViewModel.swift — fetch predicate filters parentItem == nil
  • ViewModels/InboxViewModel.swiftcreateNextOccurrence copies subtasks (reset to incomplete)
  • DoitWidgets/DoitWidgetProvider.swift — fetch predicates filter parentItem == nil
  • BACKLOG.md — added Subtasks as P0, removed P1 "Checklists within to-dos" (superseded)

Decisions:

  • Subtasks reuse DoitItem model 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.


Phase 6: Folders (2026-03-16)

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@Model with id, name, colorHex, iconName, sortOrder, createdAt, one-to-many items relationship, 20 built-in productivity icons
  • Doit/ViewModels/FolderViewModel.swift@Observable VM with addFolder(), deleteFolder(), moveItem()
  • Doit/Views/Folders/FolderListView.swift — CRUD for folders (create, rename, edit color/icon, delete), mirrors TagListView
  • Doit/Views/Folders/FolderFilterView.swift — filtered item list by folder, mirrors TagFilterView
  • Doit/Views/Components/FolderIconPicker.swift — grid picker with 20 productivity SF Symbols

Files updated:

  • Doit/Models/DoitItem.swift — added folder: Folder? property
  • Doit/Services/PersistenceService.swift — added Folder.self to schema
  • Doit/Views/Sidebar/SidebarView.swift — added folderFilter(Folder) destination, "Folders" section in sidebar, "Manage Folders" toolbar link
  • Doit/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.


Phase 7: Authentication & PostgreSQL Backend (Supabase) (2026-03-16)

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 key
  • Doit/Services/AuthService.swift@Observable singleton with Apple/Google/Email/Phone auth via supabase-swift
  • Doit/Services/SupabaseDataService.swift — async CRUD for items, tags, headings, projects + local cache sync
  • Doit/Services/SyncService.swift — full sync on login/foreground, stale detection (5 min)
  • Doit/Models/SupabaseDTO/DoitItemDTO.swift — Codable DTO with snake_case mapping
  • Doit/Models/SupabaseDTO/TagDTO.swift — tag DTO
  • Doit/Models/SupabaseDTO/HeadingDTO.swift — heading DTO
  • Doit/Models/SupabaseDTO/ProjectDTO.swift — project DTO
  • Doit/Models/SupabaseDTO/ItemTagDTO.swift — junction table DTO
  • Doit/Views/Auth/AuthView.swift — auth gate with all 4 sign-in methods
  • Doit/Views/Auth/PhoneVerificationView.swift — OTP code entry
  • Supabase/migrations/001_initial_schema.sql — full schema: tables, indexes, RLS policies

Files updated:

  • project.yml — added supabase-swift and GoogleSignIn-iOS SPM packages
  • Doit/App/DoitApp.swift — auth gate (loading/auth/main), Google Sign-In URL handler, foreground sync
  • Doit/ViewModels/InboxViewModel.swift — async methods calling SupabaseDataService
  • Doit/ViewModels/ItemDetailViewModel.swift — async save (debounced 1s), delete, tag updates
  • Doit/ViewModels/TagViewModel.swift — async add/delete/toggle with server sync
  • Doit/Views/Inbox/InboxView.swift — Task wrappers for async mutations
  • Doit/Views/Today/TodayView.swift — Task wrappers
  • Doit/Views/Calendar/CalendarView.swift — Task wrappers
  • Doit/Views/Tags/TagFilterView.swift — Task wrappers
  • Doit/Views/Tags/TagListView.swift — Task wrappers
  • Doit/Views/ItemDetail/ItemDetailView.swift — Task wrappers for tag toggle, delete
  • Doit/Views/QuickEntry/QuickEntryView.swift — server sync on add item/tag
  • Doit/Views/Sidebar/SidebarView.swift — sign-out button in toolbar
  • BACKLOG.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_tags junction 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


Feature: Dark Mode (2026-03-16)

What: Full dark appearance support with user-facing System/Light/Dark toggle in Settings.

Files created:

  • Doit/Services/AppearanceManager.swift@Observable singleton storing appearance preference (system/light/dark) in UserDefaults, exposes colorScheme: 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 — added AppearanceManager state, applied .preferredColorScheme() to root WindowGroup
  • Doit/Views/Sidebar/SidebarView.swift — added .settings destination, replaced Sign Out toolbar button with Settings gear icon linking to SettingsView
  • Doit/Views/Auth/AuthView.swift — changed Sign in with Apple button style from .black to .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.


Feature: Remote Config / OTA Updates (2026-03-16)

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.sqlremote_config table with key-value JSONB storage, RLS (read-only for authenticated users), seeded defaults for min_app_version, maintenance_mode, maintenance_message, announcement, feature_flags
  • Doit/Services/RemoteConfigService.swift@Observable singleton that fetches config from Supabase, caches to UserDefaults for offline use, provides typed accessors for all config values and feature flags
  • Doit/Models/SupabaseDTO/RemoteConfigDTO.swift — Codable DTO for decoding JSONB config rows
  • Doit/Views/RemoteConfig/ForceUpdateView.swift — Full-screen view prompting user to update when app version is below min_app_version
  • Doit/Views/RemoteConfig/MaintenanceView.swift — Full-screen maintenance message with retry button
  • Doit/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 checks
  • Doit/App/AppConstants.swift — added appVersion and buildNumber computed properties
  • Doit/Views/Settings/SettingsView.swift — added "About" section showing app version, build number, and last config fetch time
  • BACKLOG.md — added Remote Config (OTA Updates) as P0
  • Doit.xcodeproj/project.pbxproj — added new files and RemoteConfig view group

Decisions:

  • Config stored as key-value pairs in remote_config table (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 true if 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.


React Native (Expo) Rewrite — Phases 0-4 (2026-03-16)

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 auth
  • src/stores/itemStore.ts — Zustand + persist: items[], itemTags[], CRUD, toggleComplete with repeat-rule next occurrence
  • src/stores/tagStore.ts — Zustand + persist: tags[], CRUD
  • src/stores/folderStore.ts — Zustand + persist: folders[], CRUD
  • src/stores/remoteConfigStore.ts — Zustand + persist: remote config values, fetch, feature flags

Files created (screens):

  • app/_layout.tsx — Root: GestureHandlerRootView, auth restore
  • app/(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 gates
  • app/(app)/inbox.tsx — FlatList, inline add, swipe-to-delete, search
  • app/(app)/today.tsx — SectionList: Overdue/Today/This Evening
  • app/(app)/calendar.tsx — Month/week grid, expo-calendar events, merged timeline
  • app/(app)/item/[id].tsx — Full detail: title, notes, subtasks, schedule, reminder, tags, folder, delete
  • app/(app)/settings.tsx — Manage links, version info, sign out
  • app/(app)/manage-folders.tsx — Folder CRUD with icon + color picker
  • app/(app)/manage-tags.tsx — Tag CRUD with color picker
  • app/(app)/folder/[id].tsx — Folder filter list
  • app/(app)/tag/[id].tsx — Tag filter list

Files created (components):

  • src/components/DoitItemRow.tsx — Task row with checkbox/progress circle, metadata, tag dots
  • src/components/AnnouncementBanner.tsx, ForceUpdateView.tsx, MaintenanceView.tsx — Remote config UI
  • src/components/QuickEntrySheet.tsx — Bottom sheet for quick task creation
  • src/components/QuickFindModal.tsx — Search modal for items + tags

Files created (lib/utils/services):

  • src/lib/supabase.ts — Client init with AsyncStorage auth persistence
  • src/lib/constants.ts — App version, colors, icons, reminder intervals
  • src/types/index.ts — Full type system with DB row types + mappers
  • src/utils/dateHelpers.ts, repeatRule.ts, versionCompare.ts
  • src/services/syncService.ts — fullSync, syncIfStale (5-min threshold)

Files created (config):

  • package.json, app.json, eas.json, tsconfig.json
  • Supabase/migrations/003_folders_and_subtasks.sql — folders table + parent_item_id column

Bugs fixed:

  • Missing Platform import in phone-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: calendarColor cast, ItemTagRow field 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).


Feature: Offline Write Queue + Optimistic UI (2026-03-16)

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 of QueueEntry objects with enqueue (with collapse/dedup), processQueue (FIFO with retry/backoff), removeEntry, clearQueue
  • src/hooks/useNetworkMonitor.ts — NetInfo listener: drains queue + runs fullSync on reconnect, processes pending queue on mount

Files updated:

  • src/types/index.ts — added QueueEntry interface
  • src/stores/itemStore.ts — all mutations now synchronous (optimistic local update + enqueue). Replaced supabase.auth.getUser() with cached useAuthStore.getState().user?.id
  • src/stores/tagStore.ts — same optimistic + enqueue pattern
  • src/stores/folderStore.ts — same optimistic + enqueue pattern
  • src/services/syncService.tsfullSync() now drains queue first before pulling server state
  • app/(app)/_layout.tsx — mounts useNetworkMonitor, processes queue on auth
  • app/(app)/inbox.tsx — fixed infinite render loop (moved filtered items to useMemo)
  • 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.