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()