diff --git a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift index 789927b..6d19d71 100644 --- a/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift +++ b/NucleicRemote/NucleicRemote/Views/SessionDetailView.swift @@ -222,8 +222,40 @@ struct SessionDetailView: View { /// The floating action layer: one Liquid Glass bar carrying the queued pills, the working /// indicator, the session controls, and the composer — or the approval card when a gate is /// pending. Content scrolls beneath it; nothing renders when there's nothing to act on. - @ViewBuilder private var actionArea: some View { + VStack(spacing: 8) { + // Interacting with a chat while the owning Mac is unreachable surfaces this first, so a + // disabled composer reads as "offline / read-only history" rather than broken. + if !store.connectivity.isLive { disconnectedBanner } + actionContent + } + .padding(.horizontal, 12) + .padding(.bottom, 8) + } + + /// The offline notice over the transcript: the chat's history is cached and readable, but the + /// composer + controls below stay disabled until the Mac that owns it is reachable again. + private var disconnectedBanner: some View { + HStack(spacing: 10) { + Image(systemName: "wifi.slash").foregroundStyle(.secondary) + VStack(alignment: .leading, spacing: 1) { + Text("Disconnected").font(.footnote.weight(.semibold)) + Text("Showing saved history · reconnect to send") + .font(.caption2).foregroundStyle(.secondary) + } + Spacer(minLength: 8) + Button("Reconnect") { store.reconnect() } + .font(.caption.weight(.semibold)) + .buttonStyle(.borderless) + .disabled(!store.isPaired) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 14).padding(.vertical, 10) + .glassSurface(cornerRadius: 20) + } + + @ViewBuilder + private var actionContent: some View { Group { if let approval = store.openApprovals.first { // AskUserQuestion isn't a permission gate — it collects the user's answers and