Skip to content

fix: iOS 17 launch crash in the Lockdown gate presentation binding#2094

Merged
garthvh merged 1 commit into
mainfrom
fix/lockdown-fullscreencover-ios17-launch-crash
Jul 16, 2026
Merged

fix: iOS 17 launch crash in the Lockdown gate presentation binding#2094
garthvh merged 1 commit into
mainfrom
fix/lockdown-fullscreencover-ios17-launch-crash

Conversation

@garthvh

@garthvh garthvh commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a launch-time SIGTRAP that is specific to iOS/iPadOS 17 (iOS 18+ is unaffected). It is the dominant crash for the 2.7.16 App Store release by a wide margin.

Root cause

The Lockdown gate in ContentView was presented with:

.fullScreenCover(isPresented: Binding(
    get: { isLockdownGateActive },              // reads the `lockdown` @EnvironmentObject
    set: { _ in /* non-dismissable */ }          // no-op
)) { LockdownSheet() }

A presentation Binding whose getter reads observable state and whose setter is a no-op can never converge: SwiftUI's presentation machinery writes the binding, the no-op setter drops the write, and the getter still reflects the coordinator. On iOS 17's SwiftUI this re-enters the AttributeGraph until it trips _assertionFailure during the first layout pass — so affected iOS 17 users crash at launch. iOS 18+'s presentation code tolerates the same construct.

A symbolicated production crash report pins the crashed thread to ContentView.body.getter → the cover's binding closure → isLockdownGateActive.getter, recursing through AttributeGraph to _assertionFailure, exclusively on iOS/iPadOS 17.

The Lockdown gate shipped in 2.7.16 (#1779), which is why the crash is exclusive to this release.

Fix

Drive the cover from a plain @State (isShowingLockdownGate) synced from the coordinator's blocking state via .onChange (with an initial sync in .onAppear) — a normal, converging presentation binding.

Behavior is unchanged: LockdownSheet exposes no dismiss control and fullScreenCover has no interactive dismiss, so the gate stays non-dismissable and closes only when the coordinator leaves a blocking state — exactly as before.

Testing

  • Builds clean.
  • Exercised the presentation path on an iOS 17.5 simulator (gate forced active at launch): both the current build and this fix present the lockdown gate without crashing on 17.5, and the fix is confirmed behavior-preserving (the gate still presents/persists correctly via the new @State binding). The production crash did not reproduce on the 17.5 simulator — it is a real-device launch race (crashing samples are on iOS 17.7.x; simulator SwiftUI on 17.5 tolerates the binding).
  • Recommend a final smoke test on a real iOS 17 device (ideally 17.7.x) before release, exercising a device that enters a lockdown-blocking state, to confirm the gate presents/dismisses correctly and the launch crash is gone.

Risk

Low — one view, no behavior change to the gate, no API changes. Good candidate for a 2.7.17 hotfix given the iOS 17 launch impact.

The Lockdown fullScreenCover was driven by a computed Binding whose
getter read the `lockdown` @EnvironmentObject and whose setter was a
no-op. That presentation binding can never converge; on iOS 17's SwiftUI
it re-enters the attribute graph until `_assertionFailure` fires during
the first layout pass, crashing at launch. iOS 18+ tolerates it. The
lockdown gate shipped in 2.7.16, so the crash is exclusive to that
release.

Drive the cover from a plain @State (`isShowingLockdownGate`) synced
from the coordinator's blocking state via `.onChange` (plus an initial
sync in `.onAppear`). LockdownSheet has no dismiss control and
fullScreenCover has no interactive dismiss, so the gate remains
non-dismissable — behavior is unchanged.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

ContentView now uses dedicated state for lockdown gate presentation, initializes it on appearance, and keeps it synchronized with the coordinator’s blocking state.

Changes

Lockdown gate presentation

Layer / File(s) Summary
Synchronize lockdown presentation state
Meshtastic/Views/ContentView.swift
Adds isShowingLockdownGate state and replaces the computed binding with state-backed fullScreenCover presentation, initialized and updated from isLockdownGateActive.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: copilot, niccellular

Poem

I’m a bunny guarding the gate,
With state that keeps its timing straight.
When blocking starts, the cover shows,
When launch begins, onboarding grows.
Hop-hop—no binding bugs await!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: fixing the iOS 17 Lockdown gate launch crash.
Description check ✅ Passed The description covers what changed, why, and how it was tested, though it does not follow the template headings exactly.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
Meshtastic/Views/ContentView.swift (1)

60-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use initial: true to avoid .onAppear flashes.

In iOS 17+, you can pass initial: true to .onChange so the closure evaluates and applies the state before the view's first render. This eliminates the need to manually synchronize the state in .onAppear and guarantees the view won't suffer from a single-frame flash where the cover isn't presented yet on launch.

♻️ Proposed refactor
-				// Present the gate if the device is already in a blocking state when
-				// this view appears.
-				isShowingLockdownGate = isLockdownGateActive
 			}
-			.onChange(of: isLockdownGateActive) { _, active in
+			.onChange(of: isLockdownGateActive, initial: true) { _, active in
 				// Follow the coordinator's blocking state. The gate never closes from
 				// user interaction (fullScreenCover has no interactive dismiss and
 				// LockdownSheet exposes no dismiss control), so this is the only path
 				// that shows or hides it.
 				isShowingLockdownGate = active
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Meshtastic/Views/ContentView.swift` around lines 60 - 70, Update the
.onChange handler for isLockdownGateActive to use the iOS 17+ initial: true
option, allowing it to synchronize isShowingLockdownGate before the first
render. Remove the redundant .onAppear synchronization while preserving the
existing active-state behavior and presentation logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Meshtastic/Views/ContentView.swift`:
- Around line 60-70: Update the .onChange handler for isLockdownGateActive to
use the iOS 17+ initial: true option, allowing it to synchronize
isShowingLockdownGate before the first render. Remove the redundant .onAppear
synchronization while preserving the existing active-state behavior and
presentation logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bb5a4aa-0dee-469c-b50a-d76949bb416f

📥 Commits

Reviewing files that changed from the base of the PR and between 963e811 and f4ae4e6.

📒 Files selected for processing (1)
  • Meshtastic/Views/ContentView.swift

@meshtastic meshtastic deleted a comment from github-actions Bot Jul 16, 2026
@garthvh
garthvh merged commit da47f06 into main Jul 16, 2026
4 of 5 checks passed
@garthvh
garthvh deleted the fix/lockdown-fullscreencover-ios17-launch-crash branch July 16, 2026 16:45
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