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]>
Root cause (from on-device logs): the session was started from
viewDidLoad while the pairing sheet was still presenting, so iOS
interrupted it with reason 1 (videoDeviceNotAvailableInBackground) and it
never ran (isRunning=false) — a black preview. The CMVideoFormatDescription
-12710 errors were unrelated noise.
Defer startRunning until the camera can actually run: configure the graph
up front, then start only once the view is on screen and the app is
foreground-active (viewDidAppear + a guarded startSessionIfReady). Recover
on AVCaptureSessionInterruptionEnded / didBecomeActive / runtimeError. All
start triggers funnel through the session queue and no-op if already
running, so it's idempotent.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Black preview persists on a real device though the session reports
running, so instrument the capture lifecycle to pinpoint it: logs the
authorization status, the selected device, isRunning/inputs/outputs after
startRunning, and on AVCaptureSessionDidStartRunning the preview layer's
connection (present/active/enabled) plus the view/layer bounds. Also
observes AVCaptureSessionRuntimeError and ...WasInterrupted.
Filterable via os.Logger subsystem com.nucleic.remote / category scanner
(marker "📷"). To be trimmed once the root cause is fixed. Also sets an
explicit .high preset and gates metadataObjectTypes on availability.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The feed stayed black even when the session reported running: the preview
was a manually-framed sublayer (frame set in viewDidLoad/viewDidLayout,
which raced the async permission callback and could end up zero-sized),
and the session was configured across threads (addInput/Output on main,
startRunning on a queue).
Switch to the canonical AVFoundation pattern: the preview is now the
view's backing layer (CameraPreviewView via layerClass), so it always
fills the view with no frame bookkeeping, and all session configuration +
start/stop run on a dedicated serial queue with delegate/state callbacks
hopped to main.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The QR scanner sat on a silent black screen when the camera couldn't
start: it called AVCaptureDevice.default(for: .video), got nil (e.g. on
the Simulator, which has no camera), and bailed via an early guard with
no UI feedback. It also never requested camera permission (a denied
device stayed black with no recovery) and sized the preview layer only
once in viewDidLoad.
Now it gates on AVCaptureDevice authorization (requesting access when
undetermined, surfacing a Settings link when denied), reports an
"unavailable" state when there's no camera so the UI explains the black
screen, and updates the preview frame in viewDidLayoutSubviews. Camera
runs work on a background queue.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Bring NucleicRemote closer to desktop parity in two areas (the core
sync loop was already at parity — shared protocol, control scope).
Transcript fidelity (iOS): a client-side TranscriptProjection coalesces
streaming text by messageID and folds each tool call's lifecycle
(start/deltas/complete/result/fileChange) into one expandable card —
fixing the duplicate started+completed rows. Adds Markdown bubbles, the
gold Orchestra card for Task/Agent spawns, and the previously-dropped
usage/cost, rate-limit, file-change, turn-boundary and session-started
rows, plus a context-window % header badge.
Mid-session controls + model catalog (protocol/host/iOS): project the
host ModelCatalog over the wire as WireModelCatalog (in Welcome); add 5
control-scope setters (setSessionModel/Effort/Auto/AutoShip/ShipBranch)
backed by the existing AppStore.mutateSession + SessionController hooks;
enrich WireSessionSummary with model/effort/auto/autoShip/shipBranch/
contextInputTokens (all forward-compatible). The composer gains a model
picker and a catalog-driven effort menu (per-backend caps: Codex→xhigh,
Grok→auto), and the session header gains a model/effort/auto/autoship
control bar.
Tests: CBOR round-trips for the new messages, Welcome.modelCatalog, the
new summary fields, forward-compat decode of old bytes, and the setters
reaching the host. Verified in the Simulator (NUCLEIC_DEMO=1).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Lengthens the glow pulse period from 0.9s to 2.6s (macOS + iOS), keeping the bold
amplitude — the easeInOut lingers at the baseline, so each pulse now lands
deliberately with a clear gap rather than strobing. The effort-label sparkle keeps
time with it.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Swaps the Orchestra accent from violet to a warm gold (AppTheme.orchestra +
iOS Palette.orchestra), so the composer glow, effort label, and subagent cards all
read gold. Tunes the glow harder — a ~0.9s pulse (was 1.8s) with a thicker stroke
and a bigger outer bloom that swells on the beat — so an Orchestra turn announces
itself. Reduce Motion still holds the glow steady. Against a Control project's
lavender accent, the gold glow stands out cleanly.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Orchestra is a Nucleic Control capability now:
- Core: SessionController withholds the fan-out consent unless the session's project
is under Control (resolvedEffort still maps the sentinel to xhigh, so a stray
selection never reaches a backend verbatim). Covered by a new SessionController test.
- macOS: the effort menu shows Orchestra disabled with a 'Requires Nucleic Control'
note + tooltip off-control; effectiveEffort demotes a carried-over selection so the
label/glow never show it active where it can't run.
- Home composer themes lavender (the Control accent) for a Control project — the
Auto/Merge accents and a persistent ring on the field, tracking the project picker.
- Remote: WireProject carries isNucleicControlled (decode-tolerant) so the iOS effort
picker gates Orchestra the same way.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Renames the orchestration mode's user-facing brand and all code identifiers
(UltracodeStyle→OrchestraStyle, isUltracode→isOrchestra, AppTheme.ultracode→
.orchestra, etc.). The canonical effort sentinel becomes "orchestra"; the legacy
"ultracode" token is still recognized so a persisted/in-flight session keeps
working. Comment referencing Claude Code's own ultracode mode kept accurate.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
dev received an intermediate snapshot of this branch that had the accent
scoping machinery (AppPalette.make(controlled:), controlAccent, RootView's
contextProjectID gate) but NOT the reverts of the base accent back to teal.
Result: the standard palette's base accent AND controlAccent were both
lavender, so control and non-control projects rendered identically lavender.
Re-assert the defaults so the gate is actually visible:
- macOS AppTheme: standard base accent -> teal (0.04,0.52,0.50); controlAccent
stays lavender (0.45,0.32,0.82). Non-control/home now render teal; only
Nucleic Control projects get lavender.
- iOS NucleicRemote: Palette.accent + AccentColor.colorset back to flat teal
(remote has no Control concept).
Merges current dev first so this branch is strictly ahead of it — autoshipping
it back to dev will now override dev's lavender base with teal.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>