Skip to content

feat: dynamic notifications, streak nudges, yesterday grace period#1

Merged
minghinmatthewlam merged 6 commits intomainfrom
feature/notification-streak-yesterday
Mar 3, 2026
Merged

feat: dynamic notifications, streak nudges, yesterday grace period#1
minghinmatthewlam merged 6 commits intomainfrom
feature/notification-streak-yesterday

Conversation

@minghinmatthewlam
Copy link
Copy Markdown
Owner

Summary

  • Dynamic notifications: Rewrote NotificationScheduler to use non-repeating, date-specific triggers instead of repeats: 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.
  • Streak-aware evening nudge: Evening notification content adapts based on completion progress and active streak (e.g., "Complete 1 more to keep your 5-day streak!").
  • Yesterday grace period: Users can toggle completion on yesterday's focuses (no add/edit/delete). Yesterday section appears below today's focuses when incomplete, auto-hides when all done. Streak and widget recalculate on toggle.

Key decisions

  • Fallback notifications cover D+1 through D+6 with generic content; replaced with personalized content on next app open
  • Yesterday section is toggle-only — no adding/reordering past focuses to prevent gaming
  • Notification rescheduling triggers: day boundary, app foreground (60s cooldown), focus toggle/add/edit/delete/reorder, entry creation, settings change

Files changed

  • NotificationScheduler.swift — full rewrite with self-contained rescheduleAll(store:) API
  • HomeView.swift — yesterday section UI, scenePhase observer, requestReview threshold fix
  • HomeView+Actions.swift — caller migration, toggleYesterdayFocus via toggleFocus(bustYearCache:)
  • HomeViewModel.swiftyesterdayEntry property
  • WidgetSnapshotService.swiftbustYearPreviewCache() static method
  • DailySetupViewModel.swift, HomeFocusDraftsViewModel.swift, SettingsViewModel.swift, SettingsSheetView.swift — caller migration
  • FocusDrafts.swift — removed dead focusModels(from:) code

Test plan

  • Create focuses, advance day, verify morning notification shows "set your focuses" not stale text
  • With partial completion and active streak, verify evening notification shows streak-aware copy
  • Create entry with incomplete focuses, advance to next day, verify yesterday section appears
  • Toggle yesterday completion, verify streak updates and section hides when all done
  • Verify widget year progress grid updates after yesterday toggle
  • Background/foreground the app, verify notifications reschedule
  • Don't open app for 2 days, verify fallback morning notification fires

…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.
@minghinmatthewlam minghinmatthewlam merged commit b80b19f into main Mar 3, 2026
@minghinmatthewlam minghinmatthewlam deleted the feature/notification-streak-yesterday branch March 3, 2026 01:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant