The relay Worker (nucleic-edge at relay.nucleic.blakeslee.xyz) is deployed, so land the formerly deploy-gated client side of the data path: - NucleicProtocol/Sync/RelayTransport.swift: RelayAPI (one base URL for REST + WS, membership -> connection token trade), RelayWebSocket (ordered sends, ping keepalive, ping-confirmed connect), RelayFrameChannel (client leg, WireFraming inside WS binary, presence fail-fast when the room has no host), RelayPresence. - NucleicCore/Sync/RelayAccess.swift: X25519 PoP enrollment (RelayEnrollment), room credential in the login Keychain (separate from the push credential), membership minting with re-enroll-on-401. - NucleicCore/Sync/RelayListener.swift: host SyncListener demuxing the room socket into per-tag virtual FrameChannels via RelayEnvelope; presence-driven reaping; backoff redial; injectable RelayRoomSocket seam for tests. - Wire: additive HostMsg.relayMembership(WireRelayMembership) pushed after every hello (SyncHost.register -> AppStore mint) so devices paired before the relay adopt it and the ~90-day token refreshes on each connect; the pairing QR also carries a bootstrap membership so first contact can ride the relay. Old clients ignore the unknown tag. - AppStore: .relay joins the listener composite behind the Connection-methods checkbox (failure degrades to a status row), advertises relayRoomID in PeerAddresses, mints the QR bootstrap in beginPairing. - Desktop UI: the Nucleic Private Relay toggle is enabled (was "coming soon"); the LAN-only banner offers it alongside Tailnet. - iOS: relay is the last dial candidate in HostConnection pair + reconnect (10s handshake watchdog); PairedHost persists relayRoomID/relayMembershipToken/relayURL; the relayMembership push updates the registry in place; Settings shows Relay in Transports. Tests: RelayTransportTests, RelayListenerTests, SyncHostTests relay push + QR bootstrap. Full suite green (783 core + 113 protocol + 2 new); iOS simulator build succeeds. Live smoke test against the deployed Worker passed end-to-end (PoP enroll, both token tiers, two-socket frame round-trip through the Room DO with correct envelope tags). Known limits: host revoke-on-unpair not wired (endpoint is admin-only); PeerClient (Mac<->Mac) doesn't dial the relay yet. Co-Authored-By: Claude Fable 5 <[email protected]>
NucleicRemote (iPhone client)
The thin iOS remote client for Nucleic (PLAN milestone M4). It's a pure projection of the
Mac host over LAN: monitor sessions, read transcripts/diffs, answer approvals, and send
follow-up input — scope approve. No local git or CLI; the Mac is the single authority
(see docs/UX_IOS.md and docs/SYNC_PROTOCOL.md).
Architecture
All wire/crypto logic is shared with the Mac via the NucleicProtocol SwiftPM library
(this Xcode project links it as a local package at ../..):
- Transport —
NWFrameChannel(NWConnection) +LANDiscovery(Bonjour_nucleic._tcp). - Engine —
NucleicProtocol.SyncClientruns the Noise handshake (XXpsk0 to pair, IK to reconnect), exchanges hello/welcome, and turnsHostMsgs into aSyncClient.Eventstream. - State —
RemoteStore(ObservableObject) is the single on-device UI state, a pure projection of the host. Identity + pinned host live inIdentityStore(Keychain + UserDefaults). - UI — SwiftUI:
SessionsView(attention-first list),SessionDetailView(transcript/diff + status-driven action area),ApprovalCardView(Face ID gate on high-risk),PairingScannerView(QR),SettingsView.
Build & run
# Resolves the local NucleicProtocol package automatically.
xcodebuild -project ios/NucleicRemote/NucleicRemote.xcodeproj \
-scheme NucleicRemote \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro' build
Or open NucleicRemote.xcodeproj in Xcode and run. To pair, start the sync server on the Mac
(Nucleic ▸ Settings ▸ Add iPhone shows the QR), then scan it. On a real device, both must be
on the same Wi‑Fi.
Status
The full pair → list → subscribe → approve → reconnect path is implemented and the protocol/
server side is covered by tests in Tests/NucleicProtocolTests and Tests/NucleicCoreTests.
Push notifications / Live Activity (UX_IOS §5.1/§5.3) are the M5 follow-up (needs the relay).