From 33518eb8002e143b25a454b19c01573c9e62dcd3 Mon Sep 17 00:00:00 2001 From: Nucleic Date: Sat, 18 Jul 2026 17:30:38 -0700 Subject: [PATCH] Merge nucleic/olive-umber-vole-0xpn into dev --- .../NucleicRemote/Models/HostConnection.swift | 8 +++++++- NucleicRemote/NucleicRemote/Views/Theme.swift | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/NucleicRemote/NucleicRemote/Models/HostConnection.swift b/NucleicRemote/NucleicRemote/Models/HostConnection.swift index d016355..9048257 100644 --- a/NucleicRemote/NucleicRemote/Models/HostConnection.swift +++ b/NucleicRemote/NucleicRemote/Models/HostConnection.swift @@ -659,7 +659,13 @@ final class HostConnection { previous = nil sessions.append(summary) } - let becameWaiting = summary.status == .awaitingInput && previous?.status != .awaitingInput + // A `.waitingBackground` turn is parked on an armed monitor, not on the user, so it + // must not fire the needs-you notification — each monitor tick's re-invoked turn would + // otherwise re-alert. It settles at `.awaitingInput` like any other turn end, so gate + // on the disposition explicitly. + let becameWaiting = summary.status == .awaitingInput + && previous?.status != .awaitingInput + && summary.disposition != .waitingBackground if becameWaiting, !summary.archived { callbacks.sessionBecameWaiting(summary) } callbacks.didUpdate() case .dashboard(let snapshot): diff --git a/NucleicRemote/NucleicRemote/Views/Theme.swift b/NucleicRemote/NucleicRemote/Views/Theme.swift index 66ac596..5d9a236 100644 --- a/NucleicRemote/NucleicRemote/Views/Theme.swift +++ b/NucleicRemote/NucleicRemote/Views/Theme.swift @@ -222,6 +222,9 @@ enum Palette { /// finished-the-work turn reads as "done" (success) rather than the calm "ready" accent. static func status(_ status: SessionStatus, disposition: TurnDisposition? = nil) -> Color { if status == .awaitingInput, disposition == .completed { return success } + // Waiting on an armed background watch — still working in the background, so the calm + // "active" tint rather than the "ready for you" accent that pulls the eye. + if status == .awaitingInput, disposition == .waitingBackground { return active } switch status { case .idle, .provisioning: return neutral case .running: return active @@ -254,6 +257,7 @@ enum Palette { enum StatusStyle { static func symbol(_ status: SessionStatus, disposition: TurnDisposition? = nil) -> String { if status == .awaitingInput, disposition == .completed { return "checkmark.circle.fill" } + if status == .awaitingInput, disposition == .waitingBackground { return "clock" } switch status { case .idle: return "circle" case .provisioning: return "circle.dotted" @@ -268,6 +272,7 @@ enum StatusStyle { static func label(_ status: SessionStatus, disposition: TurnDisposition? = nil) -> String { if status == .awaitingInput, disposition == .completed { return "Done" } + if status == .awaitingInput, disposition == .waitingBackground { return "Waiting…" } return status.displayName } @@ -275,7 +280,11 @@ enum StatusStyle { static func sortRank(_ summary: WireSessionSummary) -> Int { switch summary.status { case .awaitingApproval: return 0 - case .awaitingInput: return summary.disposition == .completed ? 4 : 1 + // Both "Done" and "Waiting…" (parked on a background monitor) are calm — they sort below + // running rather than up with the genuinely-needs-you turns. + case .awaitingInput: + return (summary.disposition == .completed || summary.disposition == .waitingBackground) + ? 4 : 1 case .running, .provisioning: return 2 case .idle: return 3 case .finished, .interrupted, .error: return 5 @@ -296,7 +305,8 @@ extension SessionStatus { func needsYou(_ disposition: TurnDisposition?) -> Bool { switch self { case .awaitingApproval: return true - case .awaitingInput: return disposition != .completed + // `.waitingBackground` is parked on a monitor, not on you — not a needs-you state. + case .awaitingInput: return disposition != .completed && disposition != .waitingBackground default: return false } }