From 90104be78871b4abb1c98959f37b9998fb71a76f Mon Sep 17 00:00:00 2001 From: Andrew Blakeslee Moore Date: Sun, 5 Jul 2026 01:51:49 -0700 Subject: [PATCH] Sessions Integration Feature Nucleic-Session: FC94353F-9F0D-4BCC-AD7C-0FB14E38C2E5 Co-authored-by: Nucleic --- .../NucleicRemote/Models/RemoteStore.swift | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index 72acc23..c9c63d0 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -184,13 +184,57 @@ final class RemoteStore: ObservableObject { /// Proactive network-path awareness (shared across every host connection): flips transports the /// instant Wi-Fi drops or returns, instead of waiting for a dead LAN socket to time out. let pathMonitor = NetworkPathMonitor() - /// One live connection per paired Mac (mesh P3 multiplexer). All connected at once; the flat - /// `sessions`/`connectivity`/`capabilities`/… above mirror the *active* one (`activeHostID`), so - /// switching hosts is instant (no reconnect). Empty in demo mode (demo seeds state directly). + /// One live connection per paired Mac (mesh P3 multiplexer). All connected at once, and the flat + /// `sessions`/`dashboard` above are the *merged* projection across every one of them — the phone + /// shows all your Macs together, with no active-host selector. Intents route to the Mac that owns + /// the target session/project (see `send`). Empty in demo mode (demo seeds state directly). private var connections: [String: HostConnection] = [:] - /// The active connection — the Mac whose projection the flat state reflects + whose transcript - /// is open. - private var activeConnection: HostConnection? { activeHostID.flatMap { connections[$0] } } + + /// The connection whose projection drives the host-specific flat values (capabilities, scope, + /// catalog, connectivity, host name): the open session's Mac while a transcript is open, else a + /// representative live host (one with a populated catalog, falling back to any live/any host). + private var contextConnection: HostConnection? { + if let id = openSessionHostID, let conn = connections[id] { return conn } + return connections.values.first { $0.connectivity.isLive && !$0.modelCatalog.groups.isEmpty } + ?? connections.values.first { $0.connectivity.isLive } + ?? connections.values.first + } + + /// A live connection to fall back on for host-agnostic intents (e.g. a project-less to-do + /// capture, which has no owning Mac). + private var firstLiveConnection: HostConnection? { + connections.values.first { $0.connectivity.isLive } ?? connections.values.first + } + + // MARK: Intent routing (mesh P3) — resolve the Mac that owns a session / project / to-do. + + private func connection(owningSession id: SessionID) -> HostConnection? { + connections.values.first { $0.sessions.contains { $0.sessionID == id } } + } + private func connection(owningProject id: ProjectID) -> HostConnection? { + connections.values.first { $0.dashboard.projects.contains { $0.id == id } } + } + private func connection(owningTodo id: TodoID) -> HostConnection? { + connections.values.first { $0.dashboard.todos.contains { $0.id == id } } + } + /// Send to every live connection — for host-agnostic pulls (list sessions/dashboard) and as the + /// safety fallback for an approval whose owning Mac isn't known (a notification-driven decision). + private func broadcastLive(_ msg: ClientMsg) { + for conn in connections.values where conn.connectivity.isLive { conn.send(msg) } + } + + /// How many Macs are currently connected — the Sessions list shows a per-row host tag only when + /// more than one, so a single-Mac view stays clean. + var connectedHostCount: Int { + if demoMode { return 1 } + return connections.values.filter { $0.connectivity.isLive }.count + } + + /// The name of the Mac that owns a session, for the per-row host tag. `nil` in demo (no + /// connections) or when the session isn't found on any connected Mac. + func hostName(forSession id: SessionID) -> String? { + connection(owningSession: id)?.hostName + } /// The phone's embedded Tailscale node state, for Settings (nil = not running). Shared across all /// connections (one embedded node, many dials). @Published private(set) var tailnetStatus: String?