Squash-merge nucleic/trunk into dev (234 commits of accumulated nvrsion work).
Resolved two auto-generated-file conflicts:
- cloud/nucleic-edge/wrangler.jsonc: kept dev's real KV namespace ids and the
xyz.blakeslee.nucleic.remote APNS topic, with binding names corrected to
RELAY_TOKENS/PUSH_TOKENS to match the worker code on both branches.
- Package.resolved: took trunk's newer dependency pins.
Nucleic-Promote: 1
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Desktop channels → xyz.blakeslee.nucleic.desktop.{dev,beta,rc,release} (the stable channel keyword is unchanged; only its bundle-id suffix is "release"). iOS remote → xyz.blakeslee.nucleic.remote, including its Keychain account namespaces, the scanner log subsystem, and the coupled APNS topic.
Correct the Apple Developer Team ID to L7UDTQ6F5W across the Xcode project, ExportOptions, the APNS config + tests, and the signing/cloud docs.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
On-device logs proved the camera is being blocked by device state, not
the app: every retry showed window=true app=active scene=foregroundActive
yet reason-1 (videoDeviceNotAvailableInBackground) persisted for 6s — the
signature of iPhone Mirroring / a locked tethered device, where iOS
disables the camera (the recurring com.apple.PointerUI log is the Mac
pointer driving the phone).
Rather than sit on a black screen after retries are exhausted, report a
new .couldNotStart state explaining the likely cause (locked / mirrored)
with a "Try Again" button that rebuilds the capture session from scratch
(via .id(retryToken)). App-side capture code is correct; this is a
graceful fallback for an environment that withholds the camera.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
On device the camera still reported reason 1 (videoDeviceNotAvailable-
InBackground) even when started from viewDidAppear with the app
foreground-active: the camera assertion isn't granted until the sheet's
presentation transition fully settles (a few hundred ms), and the
back-to-back retries all fired inside that unsettled window.
Add a short delayed retry (0.5s, bounded to 12 attempts) driven by the
interruption notification, plus a proactive +0.6s retry after the view
appears, resetting the counter once the session actually starts. Also log
the window/app/scene activation state to confirm the foreground signals.
Co-Authored-By: Claude Opus 4.8 <[email protected]>