From 25fb2eb8ad09ad2fa395a5dfcc8769f8e202d5f4 Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Sat, 4 Jul 2026 00:39:30 -0700 Subject: [PATCH] Mesh + session transfer: P1 done, P2 core, P4 foundation Multi-device mesh + session-transfer program (docs/MESH_TRANSFER.md). P1 (multi-select connection methods, Mac): SyncTransportSet with legacy migration; CompositeSyncListener partial-failure tolerant + per-method health; AppStore listens on all enabled methods; HostInfo.hostID = DeviceIdentity.hostID (key hash, not display name); Settings 3 method toggles + LAN-only recommendation banner. P2 core (relay hardening, security-critical, tested): nucleic-edge relayEnroll.ts X25519 proof-of-possession enroll + server-side roomId derivation; room.ts role-routed per-peer forwarding with deviceId-tag envelope + frame cap + one-host-per-room eviction; host-bearer minting bound to the PoP room. Swift cross-stack contracts pinned to test vectors: RelayEnrollment (PoP proof), RelayEnvelope (routing tag), PairingPayload relay fields. Socket transport + iOS un-gating remain deploy-gated. P4 foundation (peer model + listPeers, tested): PeerTypes (PeerKind/ PeerCapabilities/PeerSummary); Hello.deviceKind+clientCaps; WireCapabilities.canListPeers; ClientMsg.listPeers -> HostMsg.peerList; PairedDevice.kind+capabilities (decode-defaulted); ConnectionHandler records kind at pairing; SyncHost.connectedDeviceIDs(); AppStore.peerSummaries(); SyncClient .peerList event; iOS RemoteStore.meshPeers. PeerClient + Mac<->Mac pairing UI remain. All additive + capability-gated; SyncProtocol.version stays 1; pre-mesh stores and clients unaffected. Swift 724 core + 91 protocol tests green; edge 50 green. Co-Authored-By: Claude Fable 5 --- NucleicRemote/NucleicRemote/Models/RemoteStore.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index 68398c0..095c6eb 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -49,6 +49,11 @@ final class RemoteStore: ObservableObject { /// Home / Projects / To-Dos state — the dashboard projection. @Published private(set) var dashboard = DashboardSnapshot.empty + /// The host's mesh peers (mesh P4), from `HostMsg.peerList`. Populated only when the host + /// advertises `capabilities.canListPeers` and the client asks; drives the future mesh device + /// list and session-transfer destination picker. + @Published private(set) var meshPeers: [PeerSummary] = [] + // Open session projection. @Published private(set) var openSessionID: SessionID? @Published private(set) var openEvents: [AgentEvent] = [] @@ -1034,6 +1039,10 @@ final class RemoteStore: ObservableObject { guard diff.sessionID == openSessionID else { break } openDiff = diff diffLoading = false + case .peerList(let peers): + // Mesh P4: the host's known peers, for the (future) mesh device list and transfer + // picker. Recorded now so multi-host UI can render it; single-host builds ignore it. + meshPeers = peers case .wireError(let error): // A channel mismatch means the host and this remote were built from incompatible // release channels. Surface it as a persistent failure with a clear message rather