Skip to content

Log today's location on app open when the day is blank#91

Merged
kyleve merged 3 commits into
mainfrom
capture-today-on-foreground
Jul 15, 2026
Merged

Log today's location on app open when the day is blank#91
kyleve merged 3 commits into
mainfrom
capture-today-on-foreground

Conversation

@kyleve

@kyleve kyleve commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

Opening the Where app early in the day could show a blank calendar day, because location is logged passively (CoreLocation Visits + significant-change) and hadn't fired yet. On launch and foreground, the app now takes a best-effort one-shot GPS fix and persists it when today has no GPS sample, so the current day fills in instead of rendering blank.

Because the fix flows through the normal ingest path, the widget/reminders reconcile and the calendar refreshes reactively via the store changes() signal — no extra wiring.

Behavior

  • Triggered on cold launch (a foreground-only launch step) and on foreground return (appBecameActive), non-blocking so it never delays UI.
  • Gated on the user wanting tracking and location being authorized (When-In-Use or Always). This notably helps When-In-Use users, who get no passive background data at all — opening the app is the only time their day gets logged.
  • "Already logged" means a GPS sample exists for today; a manual entry does not suppress the fix.
  • A headless background relaunch (already the passive event) does not fire a foreground fix.

Implementation

  • LocationAuthorizationStatus.allowsForegroundFix — any granted status (When-In-Use or Always).
  • LocationIngestor.captureTodayIfNeeded(now:) — non-blocking, single-flight capture. Skips when a GPS sample already exists today, requests the one-shot fix, and persists it. The persist runs on its own capturePersistTask handle (not the stream loop's inFlightIngest), and quiesce() awaits it independently so an erase can't wipe the store out from under an in-flight capture — without stalling the erase on a slow (~10s) GPS fix.
  • WhereSession.captureTodayIfNeeded() — applies the gate; called from start() and appBecameActive().
  • WhereLaunch — foreground-only capture-today step after reconcile-tracking.

Testing

  • LocationAuthorizationTests: allowsForegroundFix truth table.
  • LocationIngestorTests: captures when today is empty; skips when a GPS sample exists; still captures when only a manual entry exists; skips when no fix; single-flight (overlapping call dropped, via a gated source); existing quiesce* tests still pass.
  • WhereSessionTrackingTests: gating (logs when wanted+authorized; no-ops when tracking off or unauthorized).
  • WhereLaunchTests: step order parity, cold-foreground captures, background launch skips capture.

Full Stuff-iOS-Tests scheme is green and ./swiftformat --lint is clean.

Made with Cursor

kyleve and others added 3 commits July 15, 2026 15:47
Opening the Where app early in the day could show a blank calendar day
because passive tracking (Visits + significant-change) hadn't fired yet.
On launch and foreground, take a best-effort one-shot GPS fix and persist
it through the normal ingest path when today has no GPS sample.

- LocationAuthorizationStatus.allowsForegroundFix (When-In-Use or Always)
- LocationIngestor.captureTodayIfNeeded(now:): non-blocking single-flight
  capture that skips when a GPS sample already exists today (a manual entry
  doesn't count), persists via the shared ingest path (widget + reminder
  reconcile + change signal), cancelled on deinit/quiesce
- WhereSession.captureTodayIfNeeded(): gated on wantsTracking + a usable
  authorization; called from start() and appBecameActive()
- WhereLaunch: foreground-only capture-today step after reconcile-tracking
  so a headless background relaunch doesn't fire a foreground fix

Co-authored-by: Cursor <cursoragent@cursor.com>
Address a concurrency concern from review: routing the foreground capture
through ingest(_:) made it a second writer of the single inFlightIngest
slot the stream loop uses, which quiesce() relies on to await an in-flight
persist before an erase wipes the store.

- Capture now persists via processIngestedSample on its own
  capturePersistTask handle (single writer, since capture is single-flight),
  re-checking acceptsSamples synchronously right before registering the
  handle so quiesce() either skips it or awaits it, never neither.
- quiesce() cancels the capture's fix acquisition (best-effort, not awaited
  so an erase never stalls on a slow ~10s GPS fix) and awaits the capture
  persist and the stream persist on their independent handles.
- Add a single-flight test using a gated location source that holds a fix
  in flight, proving an overlapping second call is dropped.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ground

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	Where/WhereCore/AGENTS.md
@kyleve kyleve enabled auto-merge (squash) July 15, 2026 23:26
@kyleve kyleve merged commit f6a4df1 into main Jul 15, 2026
2 checks passed
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