Fix stale overlay tap + redesign expanded Dynamic Island#581
Merged
bjorkert merged 5 commits intoloopandlearn:live-activityfrom Mar 28, 2026
Merged
Fix stale overlay tap + redesign expanded Dynamic Island#581bjorkert merged 5 commits intoloopandlearn:live-activityfrom
bjorkert merged 5 commits intoloopandlearn:live-activityfrom
Conversation
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>
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
Fix stale overlay tap: Introduced
endingForRestartflag inLiveActivityManagerto prevent a race condition where tapping a stale Live Activity overlay would setdismissedByUser = true, permanently stopping auto-restart. The flag is set synchronously beforeactivity.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
smallWidgetSlotmetric (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