diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index 5ab7880..ed11a52 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -614,25 +614,38 @@ final class RemoteStore: ObservableObject { } + /// Unpair this device entirely — drop every paired Mac and its connection (the "Unpair this + /// device" button). Nothing left to show, so the app returns to the pairing intro. func unpair() { - // Forget + drop the active Mac's connection specifically (the switcher may have made it not - // the last). Other Macs' connections keep running. - if let id = activeHostID { - connections[id]?.teardown() - connections[id] = nil - IdentityStore.removePairedHost(id: id) + for (id, conn) in connections { conn.teardown(); connections[id] = nil } + for host in IdentityStore.pairedHosts() { IdentityStore.removePairedHost(id: host.fingerprint) } + IdentityStore.clearPairedHost() + finishUnpairIfEmpty() + } + + /// Forget one paired Mac (the Settings ▸ Mesh per-row remove): drop just its connection + + /// registry record. Other Macs keep running and stay in the merged view. + func unpair(_ hostID: String) { + connections[hostID]?.teardown() + connections[hostID] = nil + IdentityStore.removePairedHost(id: hostID) + if IdentityStore.pairedHosts().isEmpty { + finishUnpairIfEmpty() } else { - IdentityStore.clearPairedHost() - } - activeHostID = nil - // Mesh P3: if other Macs remain paired, switch to one (already connected) rather than unpairing. - if let next = IdentityStore.pairedHosts().last { - activeHostID = next.fingerprint - reconnect() - return + rebuildAggregate() + refreshAggregate() } + } + + /// Wind the app back to the unpaired state once no Macs remain: clear the open transcript + flat + /// aggregate and spin the embedded Tailscale node down. + private func finishUnpairIfEmpty() { + openSessionID = nil + openSessionHostID = nil + openEvents = []; openApprovals = []; openDiff = nil; diffLoading = false connectivity = .unpaired sessions = [] + dashboard = .empty // Nothing left to dial — spin the embedded Tailscale node down if it was running. Task { await TailnetNode.shared.stop() } tailnetStatus = nil