feat: dynamic notifications, streak nudges, yesterday grace period#1
Merged
minghinmatthewlam merged 6 commits intomainfrom Mar 3, 2026
Merged
Conversation
…treak-aware content Replace old draft-based notification API with self-contained rescheduleAll(store:) and debouncedRescheduleAll(store:) that internally fetch today's entry, compute streak via StreakEngine, and read NotificationPreferences. - Use repeats:false with full date components; schedule tomorrow if time passed - Evening notifications now show streak-aware nudge content - Weekly reminder only re-registers when not already pending - Migrate callers in DailySetupViewModel, HomeFocusDraftsViewModel, SettingsViewModel - Simplify SettingsSheetView call site to match new SettingsViewModel API - Remove unused FocusDrafts.focusModels(from:)
- Add yesterdayEntry to HomeViewModel, loaded when yesterday is incomplete - Add yesterday section in HomeView with toggle-only rows (no edit/swipe/drag) - Add scenePhase observer with 60s cooldown for notification rescheduling - Add notification rescheduling to SignificantTimeChangeListener - Replace rescheduleReminders(for:) with rescheduleReminders(using:) calling new NotificationScheduler.shared.debouncedRescheduleAll(store:) API - Update all action methods (toggleFocus, addFocus, updateTitle, deleteFocus, persistFocusOrder) to use new reschedule API - Add toggleYesterdayFocus with year preview cache busting - Add WidgetSnapshotService.bustYearPreviewCache() static method
- Remove force unwrap in HomeViewModel yesterday entry check (use flatMap) - Deduplicate toggleYesterdayFocus by delegating to toggleFocus with bustYearCache param - Collapse morningBody/middayBody into single reminderBody helper - Track weekly notification registration with flag instead of querying pending requests - Fix requestReview to only trigger when streak crosses threshold, not on initial load
Non-repeating notifications meant users who didn't open the app for 2+ days stopped getting daily reminders. Now schedules 6 generic morning fallback notifications covering the next week. These get replaced with personalized content whenever the app is opened and rescheduleAll fires.
Fallbacks were computed from startOfTomorrow + 1...6 (D+2 through D+7), leaving D+1 uncovered when the personalized morning fires today. Changed to startOfToday + 1...6 (D+1 through D+6) so tomorrow is always covered. Worst case is a harmless duplicate on D+1 when personalized also targets tomorrow (different identifiers).
scheduleFallbackReminders now delegates to makeRequest instead of duplicating content/trigger/request construction. Passes the future date as the reference point so makeRequest extracts the correct day components without triggering its tomorrow-bump logic.
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.
Summary
NotificationSchedulerto use non-repeating, date-specific triggers instead ofrepeats: true. Morning/midday notifications show current focus list or prompt to set focuses. 6-day fallback morning reminders ensure coverage even if the app isn't opened.Key decisions
Files changed
NotificationScheduler.swift— full rewrite with self-containedrescheduleAll(store:)APIHomeView.swift— yesterday section UI, scenePhase observer, requestReview threshold fixHomeView+Actions.swift— caller migration,toggleYesterdayFocusviatoggleFocus(bustYearCache:)HomeViewModel.swift—yesterdayEntrypropertyWidgetSnapshotService.swift—bustYearPreviewCache()static methodDailySetupViewModel.swift,HomeFocusDraftsViewModel.swift,SettingsViewModel.swift,SettingsSheetView.swift— caller migrationFocusDrafts.swift— removed deadfocusModels(from:)codeTest plan