The safe, verifiable slice of iOS multi-host: switch which paired Mac the phone views, reusing the
existing (proven) single-connection reconnect — no rewrite of the connection state machine. The
simultaneous [HostID: HostConnection] multiplexer stays deferred (it needs two real Macs to verify).
RemoteStore: an `activeHostID` (the paired Mac the flat projection reflects); `hostChoices` (paired
registry live, mock hosts in demo); `switchHost(to:)` — live re-points via reconnect(), demo swaps
the mock host preserving in-demo edits; `reconnect()`/`pair()` set the active host; `unpair()` now
forgets the *active* Mac and switches to a remaining one if any (identical for a single host). Demo
seeds two mock Macs ("Andrew's Mac", "Studio Mac") with distinct sessions/dashboards.
SessionsView: a host-switcher menu in the toolbar, shown only when >1 Mac.
Verified in the iOS Simulator (demo mode): the switcher lists both Macs, and switching swaps the
whole session projection + the tab badge (screenshots taken). Single-host behavior is unchanged
(one host ⇒ switcher hidden ⇒ flat state exactly as before).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Completes Phase 5 of the multi-device mesh / session-transfer program (docs/MESH_TRANSFER.md)
except the two-Mac memory-carry spike. All additive + capability-gated; SyncProtocol stays v1.
- Moved-session visibility: additive SessionSummary.movedTo (MovedDestination), decode-defaulted.
A moved session no longer silently vanishes — the source keeps a read-only "Moved to <Mac>"
tombstone under Archived (name resolved live from paired Macs), surfaced on relaunch without
rebuilding a runnable controller, and sent on the wire so phones see it too.
- Relaunch recovery driven from launch (+ on every peer reconnect, single-flight):
AppStore.recoverInterruptedTransfers clears abandoned pre-tombstone locks, discards orphaned
inbound staging, and re-drives a tombstoned commit via SessionTransferCoordinator.recoverTombstoned
(bounded, idempotent; a dest that lost staging leaves the lock, never revives the source).
- Bulk "Hand off active sessions…": transferableSessions + moveSessionsToPeer (sequential, rollup
error) behind a "Hand off…" button → HandoffSheet checklist in RemoteAccessView.
- Arrived-from provenance (mirror of moved-to): GRDB v24 arrived_from_device_id/arrived_at; the
importer stamps them at staging; additive SessionSummary.arrivedFrom (ArrivedFrom); a subtle
"Arrived from <Mac>" marker on the sidebar (live name) + iOS row (host-baked name).
- Stranded-arrival "Activate anyway": the importer persists the staged Session to the staging dir
at .ready, so a destination that relaunches before commit can recoverableInboundTransfers() and
activateRecoveredTransfer()/clearInboundStaging(). AppStore surfaces pendingArrivedTransfers with
activate/discard, shown in a new "Interrupted arrivals" section. (A .ready lock with no manifest
is now cleared as unrecoverable.)
Tests: +6 core, +2 protocol across WireMessageTests, SessionTransferTests, AppStoreTests,
AppStoreSyncBridgeTests. Full package builds; Swift suites green. iOS NucleicRemote edits reviewed
but not compiled here (separate Xcode target).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Brings the phone's ambient surfaces (UX_IOS §5/§8) to maturity:
- Notification pipeline (NotificationRouter): local notifications for
approvals and needs-input transitions while backgrounded; low-risk
approvals are actionable from the banner (Allow requires device
auth, high-risk must open the app's Face ID gate); taps deep-link
to the session; app-icon badge = NEEDS YOU count; resolutions
withdraw the notification (first-responder-wins). The relay's
content-free approval.pending tickle localizes via
Localizable.strings.
- Live Activity: new NucleicRemoteWidgets extension target (lock
screen + Dynamic Island) rendering one aggregate Activity —
N running / M waiting + the most urgent session — started/updated/
ended by LiveActivityManager as session state changes.
- Out-of-band push path: nucleic-edge gains POST /v1/push/register
(admin) so the host can upload tokens for LAN-only pairings; the
host's new PushRelayClient (config-gated on NUCLEIC_RELAY_URL +
NUCLEIC_RELAY_ADMIN_SECRET) mirrors Hello.pushToken to the relay
and wakes non-connected phones when an approval arrives, throttled
per device. Everything stays off until the relay is provisioned.
Worker tests (23) and Swift suites pass apart from the pre-existing
fixture gaps and nvrsion flake. Simulated APNs delivery is blocked in
this environment (notification auth can't be granted headlessly).
Co-Authored-By: Claude Fable 5 <[email protected]>
Port the Mac's MarkdownText renderer, AppPalette (color-vision modes +
night-softening), appearance settings (theme/text size/color vision),
and BuildBanner channel strip to the iPhone remote. Add session-row
attention/unseen-completion washes and marker icons, Discard action,
branch/worktree options in the new-chat composer, and a transient
error bubble for host wire errors. Stamp the build channel via
NucleicChannel in Info.plist (ios-release.sh passes NUCLEIC_CHANNEL,
default beta). Demo mode gains NUCLEIC_DEMO_SESSION and skips the
notifications prompt so UI previews are scriptable.
Co-Authored-By: Claude Fable 5 <[email protected]>
Design system: Palette (teal accent + the Mac's categorical status colors, status→color
refined by turn disposition), StatusStyle glyphs/labels, a card surface modifier, app-wide
teal tint.
Fixes the reported bug: Sessions now hides archived chats and uses disposition to split
NEEDS YOU from DONE (a finished-the-work turn no longer shows as needing you). Rows gain
status dots, favorite star, approval-count badge, diffstat; swipe to favorite/archive/delete.
New surfaces, full control-scope parity with the Mac:
- Home: greeting + day-streak flame, GitHub-style activity grid, stat cards, quick to-dos,
and a start-chat composer (project picker + Auto).
- Projects: per-project session counts → project detail with a scoped composer.
- To-dos: capture, group by project, complete/dispatch/delete.
- Session detail: a control menu (rename, favorite, interrupt, integrate merge/squash/rebase,
archive, delete).
RemoteStore handles the dashboard event + exposes all control intents; claims control scope.
A NUCLEIC_DEMO/NUCLEIC_TAB env seam seeds mock state for offline UI preview.
iOS app builds for the iOS 27 simulator; Home + Sessions verified rendering via screenshots.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
A real iOS Xcode app (ios/NucleicRemote) linking the NucleicProtocol SwiftPM
library as a local package. Builds for the iOS 27 simulator and launches to the
pairing screen.
- Transport: NWFrameChannel (NWConnection) + LANDiscovery (Bonjour _nucleic._tcp).
- Engine: drives NucleicProtocol.SyncClient (Noise XXpsk0 pair / IK reconnect,
hello/welcome, HostMsg→Event stream).
- State: RemoteStore (ObservableObject) — the single on-device projection of host
state; IdentityStore persists the device identity (Keychain) + pinned host.
- UI (UX_IOS): attention-first SessionsView, SessionDetailView (transcript/diff +
status-driven action area / composer), ApprovalCardView with Face ID gate on
high-risk approvals + allow-always menu, PairingScannerView (AVFoundation QR),
SettingsView, connection chip. Same status glyphs/semantics as the Mac.
Add-iPhone QR display + server start live on the macOS side (follow-up); push /
Live Activity are M5 (needs the relay). gitignore keeps this .xcodeproj despite
the blanket *.xcodeproj rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>