From 831c30bcea6a73d04aa236306bdaf273b3c1de3a Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Sun, 5 Jul 2026 22:03:48 -0700 Subject: [PATCH] Settings Feature Enhancement Nucleic-Session: 89BFAB13-D559-43FC-BE28-0B2639BECF0F Co-authored-by: Nucleic --- .../NucleicRemote/Models/RemoteStore.swift | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index f8c4016..d3177af 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -765,6 +765,44 @@ final class RemoteStore: ObservableObject { send(.fetchDiff(sessionID)) } + // MARK: - Add a device to this mesh (relayed pairing-code mint) + + /// Whether the "add a device" button should appear: a live Mac that advertises + /// `canMintPairingCode` is reachable to mint a join code (or we're in demo). + var canAddDeviceToMesh: Bool { + demoMode || pairingMintConnection != nil + } + + /// The connection we route a mint request to: prefer the context host if it can mint, else any + /// live host that can. A code minted by *any* mesh member joins the whole group (mesh "join"). + private var pairingMintConnection: HostConnection? { + if let ctx = contextConnection, ctx.connectivity.isLive, ctx.capabilities.canMintPairingCode { + return ctx + } + return connections.values.first { $0.connectivity.isLive && $0.capabilities.canMintPairingCode } + } + + /// Ask a connected Mac to open a pairing window and hand back its join code. The reply arrives + /// asynchronously as `AddDeviceState.ready` (or `.unavailable`) via the host connection. + func requestPairingCode() { + if demoMode { addDevice = .ready(Self.demoPairingCode); return } + guard let conn = pairingMintConnection else { addDevice = .unavailable; return } + pairingMintHostID = conn.hostID + addDevice = .requesting + conn.send(.requestPairingCode) + } + + /// The user dismissed the "add a device" sheet — tell the minting Mac to close its pairing + /// window (retire the one-time secret) and reset to idle. + func cancelPairingCode() { + if !demoMode, let id = pairingMintHostID { connections[id]?.send(.cancelPairingCode) } + pairingMintHostID = nil + addDevice = .idle + } + + /// A stand-in join code for the offline demo so the QR/copy sheet renders without a Mac. + private static let demoPairingCode = "nucleic://pair?d=demo" + /// Record that the user looked at this session now (clears its unseen-completion wash). func markOpened(_ sessionID: SessionID) { lastOpenedAt[sessionID] = Date()