Watch: notification switches, a model list and Ask before acting on new threads, and Ask before acting on a thread

This commit is contained in:
gib committed 2026-11-02 12:10:48 -05:00
1 parent 3cc7e40852
commit 6bc9cac6ca
14 files changed
+623 -81

No files matched your search

+36 -3
View File
@@ -1,6 +1,7 @@
// One thread: the agent's latest message large, older messages one scroll up, the pending approval
// or question with its options, Reply by dictation or Scribble, Stop while a run is active, and
// Settle once it finished or failed. A subagent's request shows first, labeled with the subagent.
// or question with its options, Reply by dictation or Scribble, Settle once it finished or failed,
// an Ask before acting switch, and Stop while a run is active. A subagent's request shows first,
// labeled with the subagent.
import SwiftUI
@@ -19,6 +20,10 @@ struct ThreadView: View {
/// Options ticked so far on multi-select questions, by question id.
@State private var selections: [String: Set<String>] = [:]
@State private var scrolledToLatest = false
/// Why the last Ask before acting change failed. It shows under the switch.
@State private var modeError: String?
/// The Ask before acting value still being sent, which a poll landing meanwhile keeps.
@State private var pendingAskFirst: Bool?
/// Snapshots render the column without its ScrollView, which ImageRenderer leaves blank.
private let scrolls: Bool
@@ -149,6 +154,15 @@ struct ThreadView: View {
.disabled(sending)
}
if let askFirst = detail.askFirst {
SwitchCard(title: "Ask before acting", isOn: Binding(get: { askFirst }, set: { setAskFirst($0) }))
if let modeError {
Text(modeError)
.font(.system(size: 11))
.foregroundStyle(Theme.bad)
}
}
if detail.canStop {
Button(role: .destructive) { stop() } label: {
Label("Stop", systemImage: "stop.fill")
@@ -300,7 +314,9 @@ struct ThreadView: View {
private func load() async {
do {
detail = try await model.api.thread(hostId: hostId, threadId: threadId)
var fresh = try await model.api.thread(hostId: hostId, threadId: threadId)
if let pendingAskFirst { fresh.askFirst = pendingAskFirst }
detail = fresh
error = nil
} catch {
self.error = model.message(for: error)
@@ -357,6 +373,23 @@ struct ThreadView: View {
perform { try await model.api.stop(hostId: hostId, threadId: threadId) }
}
/// Flips the switch at once and tells the gateway. If it refuses, the switch flips back and the
/// line under it shows why.
private func setAskFirst(_ on: Bool) {
detail?.askFirst = on
pendingAskFirst = on
modeError = nil
Task {
do {
try await model.api.setAskFirst(hostId: hostId, threadId: threadId, askFirst: on)
} catch {
detail?.askFirst = !on
modeError = model.message(for: error)
}
pendingAskFirst = nil
}
}
/// Settles the thread and goes back to the feed. On failure the screen stays and shows why.
private func settle() {
sending = true