Skip to content

Never miss a logging day#10

Open
kyleve wants to merge 11 commits into
mainfrom
never-miss-a-logging-day
Open

Never miss a logging day#10
kyleve wants to merge 11 commits into
mainfrom
never-miss-a-logging-day

Conversation

@kyleve
Copy link
Copy Markdown
Owner

@kyleve kyleve commented May 29, 2026

Summary

Adds a missing-day safety net to Where: it detects unlogged days since Jan 1 of the current year and surfaces them four ways — a daily local notification, an app-icon badge, an in-app warning banner, and a guided backfill flow.

  • MissingDays (WhereCore) — pure calculator for unlogged days in [Jan 1, today], collapsed into consecutive MissingDayRanges (boundaries, leap years, through-clamping, dedup/sort).
  • LoggingReminderScheduler (WhereCore)ReminderTime + LoggingReminderScheduling protocol behind a UNUserNotificationCenter-backed impl: per-day cancellable UNCalendarNotificationTriggers keyed by date id, owned-only cleanup, and setBadgeCount. NoopLoggingReminderScheduler added for previews/tests.
  • WhereController — injectable scheduler + clock; configureReminders, and badge/reminder reconciliation after GPS ingest, manual entry, and startGPS (the "recorded successfully → drop today's reminder & lower the badge" path).
  • WhereModel — persisted remindersEnabled/reminderTime (default on, 8 PM), setters that reconcile through the controller, configureReminders on start(), and missingDays/missingDayCount derived from the loaded report (current year only).
  • UI — a tappable Liquid-Glass warning banner on the Primary tab opening a new MissingDaysView, which lists each gap and deep-links into a prefilled ManualDayEntryView (new init(prefill:)); plus a Reminders section in Settings (toggle + time picker + "Allow notifications" affordance when denied).
  • AppAppDelegate is now the UNUserNotificationCenterDelegate so reminders present while foregrounded. No entitlement / Info.plist changes were needed.

Notes / deviations from the plan

  • Notification copy lives in the WhereCore string catalog (that's where it's posted), not WhereUI.
  • Added an explicit WhereCoreTests scheme to Project.swift (the autogenerated one has no working test action, matching the existing note for the other unit-test bundles).

Test plan

  • tuist test WhereCoreTestsMissingDaysTests + spy-driven WhereControllerTests (backlog badge, today-logged cancellation, enable/disable)
  • tuist test WhereUITestsWhereModelMissingDaysTests (computation + settings persistence) and hosting tests for the Primary banner branch + MissingDaysView
  • tuist test WhereTests — app target green after the AppDelegate change
  • ./swiftformat --lint clean across the tree
  • Manual: enable reminders, miss a day → notification + badge appear; log the day → both clear; backfill a range via the banner

Note: local test loop uses tuist test <scheme> --no-selective-testing; Tuist's selective testing otherwise reports "no tests to run" for unchanged targets.

Made with Cursor

kyleve and others added 8 commits May 29, 2026 15:08
Closes the "MissingDays calculator + MissingDayRange" plan step: pure
WhereCore logic that finds unlogged days in [Jan 1, through] for a year
and collapses them into consecutive ranges, with tests covering
boundaries, leap years, clamping, and range collapsing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "ReminderTime + LoggingReminderScheduling + scheduler" plan
step: ReminderTime value, the scheduling protocol, and a
UserNotifications-backed implementation that schedules per-day
cancellable reminders and drives the app-icon badge. Notification copy
added to the WhereCore string catalog.

Pure groundwork: exercised by the controller wiring + spy tests in the
next step, so there are no standalone tests here. Verified to compile
via `tuist build Where`.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "controller reminder wiring" plan step: WhereController now
takes an injectable reminder scheduler + clock, exposes
configureReminders / notification-auth passthroughs, and reconciles the
badge + per-day reminders after GPS ingest, manual entry, and startGPS.
A frozen-clock spy scheduler drives deterministic tests for the backlog
badge, the today-logged cancellation path, and enable/disable.

Also adds isAuthorized() to the scheduler protocol and declares an
explicit WhereCoreTests scheme so the bundle can run on its own (the
autogenerated scheme has no working test action).

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "extend WhereModel" plan step: persisted remindersEnabled /
reminderTime (default on, 8 PM), setters that push the config to the
controller on change, configureReminders on start(), and
missingDays/missingDayCount derived from the loaded report for the
current year. Adds tests for the computation + settings persistence.

Adds a NoopLoggingReminderScheduler in WhereCore (alongside the existing
ScriptedLocationSource preview/test seam) and wires it into
PreviewSupport and the view-model tests so they never touch
UNUserNotificationCenter.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "MissingDaysView + prefill" plan step: a sheet listing the
year's unlogged day ranges, each opening a ManualDayEntryView prefilled
with that range (new ManualDayEntryView(prefill:) init that seeds the
single-day vs range mode and dates). Adds the missing-days strings (plus
the banner strings the next step consumes) and a missingDaysModel
preview fixture.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "warning banner" plan step: when the current year has
unlogged days, the Primary tab shows a tappable Liquid Glass banner
above the content that opens MissingDaysView to backfill them. Hidden
when there's nothing missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "Reminders settings" plan step: a toggle for the daily
logging reminder plus an hour/minute time picker, both routed through
WhereModel so changes reconcile the scheduled reminders and badge. When
reminders are on but notifications are denied, the section surfaces an
"Allow notifications" affordance that deep-links to system Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
Closes the "strings + AppDelegate delegate + UI tests" plan step:
- AppDelegate becomes the UNUserNotificationCenterDelegate so logging
  reminders present (banner/sound/badge) even while Where is open.
- Adds the missing-days, banner, and reminder-settings keys to the
  WhereUI string catalog so the new copy is translatable.
- Adds hosting tests covering the Primary banner branch and
  MissingDaysView with real ranges.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kyleve
Copy link
Copy Markdown
Owner Author

kyleve commented May 29, 2026

@cursoragent review

@kyleve
Copy link
Copy Markdown
Owner Author

kyleve commented May 29, 2026

@codex review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 29, 2026

Findings addressed:

  1. High: reminder time changes left existing notifications at the old time
    Fixed UserNotificationReminderScheduler to detect pending requests with mismatched trigger times and replace them. Added scheduler tests.

  2. High: delayed GPS visits for past days could leave badge/reminders stale
    Fixed GPS reconciliation so past-day ingests still trigger a full reminder reconcile even after today is already covered. Added regression test.

  3. Medium: clearing the current year did not update badge/reminders
    clearYear(_:) now reconciles reminders after wiping data. Added regression test.

  4. Medium: revoked notification authorization left stale owned reminders/badge
    Scheduler now clears owned reminders and badge when authorization is no longer granted. Also added foreground refresh so returning from Settings updates notification state.

Pushed commits:

  • 2d58239 Fix reminder reconciliation edge cases
  • 0e6f112 Tighten reminder scheduler test seam

Verification:

  • Attempted ./swiftformat --lint → blocked: mise: command not found
  • Attempted tuist test WhereCoreTests/WhereUITests/WhereTests --no-selective-testing → blocked: tuist: command not found
  • Working tree is clean and branch is pushed.

This cloud image needs the repo’s Swift toolchain setup (mise, Tuist, SwiftFormat) before local checks can run here.

Open in Web Open in Cursor 

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f505a28132

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +172 to +173
for (id, day) in desiredIDs where !pendingIDs.contains(id) {
await scheduleReminder(identifier: id, day: day, time: reminderTime)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reschedule pending reminders when time changes

When the user changes the reminder time, the desired identifiers stay the same because they are keyed only by day, so any existing pending request is skipped here instead of being replaced with a trigger using the new hour/minute. In that scenario Settings persists and displays the new time, but already-scheduled notifications continue firing at the old time until they fall out of the rolling window; remove/re-add owned pending requests when reminderTime changes, or include enough trigger state in the reconciliation to detect stale requests.

Useful? React with 👍 / 👎.

cursoragent and others added 3 commits May 29, 2026 22:56
Co-authored-by: Kyle Van Essen <k@kve.me>
Co-authored-by: Kyle Van Essen <k@kve.me>
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.

2 participants