Skip to content

Fix stale overlay tap + redesign expanded Dynamic Island#581

Merged
bjorkert merged 5 commits intoloopandlearn:live-activityfrom
achkars-org:live-activity
Mar 28, 2026
Merged

Fix stale overlay tap + redesign expanded Dynamic Island#581
bjorkert merged 5 commits intoloopandlearn:live-activityfrom
achkars-org:live-activity

Conversation

@MtlPhil
Copy link
Copy Markdown

@MtlPhil MtlPhil commented Mar 28, 2026

Summary

  • Fix stale overlay tap: Introduced endingForRestart flag in LiveActivityManager to prevent a race condition where tapping a stale Live Activity overlay would set dismissedByUser = true, permanently stopping auto-restart. The flag is set synchronously before activity.end() and cleared after the restart completes, so the state observer ignores our own programmatic dismissal.

  • Redesign expanded Dynamic Island: The expanded DI leading view now matches the CarPlay/Watch SmallFamilyView layout — glucose value + trend arrow (color-keyed) with delta+unit below. The trailing view shows the configurable smallWidgetSlot metric (label + value) instead of hardcoded IOB/COB. The bottom "Updated at" banner is unchanged.

  • Remove Watch/CarPlay toggles: Removed per-surface enable/disable toggles. No viable runtime API exists to distinguish Watch Smart Stack from CarPlay at render time without requiring iOS 26 minimum. The settings UI retains the slot picker for the right-side metric on both surfaces.

🤖 Generated with Claude Code

MtlPhil and others added 5 commits March 27, 2026 14:14
Use GeometryReader in LockScreenFamilyAdaptiveView to distinguish
Watch Smart Stack (height ≤ 75 pt) from CarPlay Dashboard (height > 75 pt)
at render time — both surfaces share ActivityFamily.small with no API
to tell them apart, so canvas height is the only runtime signal.

Adds la.watchEnabled and la.carPlayEnabled App Group keys (default true).
The Right Slot picker hides when both are disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…black

Two bugs with the height-based threshold:
1. System padding pushes Watch Smart Stack canvas above 75 pt, causing both
   Watch and CarPlay to be classified as CarPlay (blank on both when
   CarPlay toggle is off).
2. Color.clear is transparent — cached Watch renders show through it,
   leaving stale data visible after the toggle is turned off.

Fix: switch to width-based detection. Watch Ultra 2 (widest model) is
~183 pt; CarPlay is always at least ~250 pt. A 210 pt threshold gives a
~14% buffer above the max Watch width. Replace Color.clear with Color.black
so old frames are fully covered when the widget is disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… race

Root cause: handleForeground() clears laRenewalFailed=false synchronously
before calling activity.end(). When the state observer fires .dismissed,
renewalFailed is already false and staleDatePassed may also be false, so
it falls into the user-swipe branch and sets dismissedByUser=true.

Fix 4 (dismissedByUser=false in the Task) was meant to override this, but
the state observer's MainActor write can be queued *after* the Task's reset
write, winning the race and leaving dismissedByUser=true. The result: LA
stops after tapping the overlay and never restarts.

Add endingForRestart flag set synchronously (on the MainActor) before end()
is called. The state observer checks it first — before renewalFailed or
staleDatePassed — so any .dismissed delivery triggered by our own end() call
is never misclassified as a user swipe, regardless of MainActor queue order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tState

The LA must be ended and recreated for Watch/CarPlay content changes to
take effect immediately. refreshFromCurrentState only sends a content
update to the existing activity; forceRestart ends the activity and
starts a fresh one, so the widget extension re-evaluates and the
black/clear tile appears (or disappears) without APNs latency.

Note: true per-surface dismissal (tile fully gone from Watch OR CarPlay
while the other remains) requires splitting into two LAs and is a
future architectural change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Watch/CarPlay toggles removed: disfavoredLocations API requires iOS 26
and GeometryReader-based detection is unreliable. Reverts to always
showing SmallFamilyView on .small activity family.

Expanded Dynamic Island redesigned to match SmallFamilyView layout:
- Leading: glucose + trend arrow (colour-keyed, firstTextBaseline), delta below
- Trailing: configurable slot (same smallWidgetSlot setting as CarPlay/Watch)
  with label + value, replaces hardcoded IOB/COB
- Bottom: unchanged — "Updated at" or "Not Looping" banner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bjorkert bjorkert merged commit 3d62d8e into loopandlearn:live-activity Mar 28, 2026
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