Watch: the Subagents section on the thread screen collapses behind a header with the count and how many wait or work, and opens on its own when one waits
This commit is contained in:
1 parent
480db90e98
commit
d10df56cc9
6 files changed
+51
-13
No files matched your search
+2
-2
@@ -60,8 +60,8 @@ xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -screen thread
|
||||
status screens, the subagent card and thread, Recent with Settle all and the "N more" row, the full
|
||||
Recent list, a finished thread with Settle, a running thread with Ask before acting, New thread
|
||||
with its model list, the working cards' activity lines, All threads' hosts and a project's threads,
|
||||
a thread's subagents, Load earlier with markdown, the changes line and Changed list, search and
|
||||
its empty state, the complication's two modes, the Smart Stack widget, and a question
|
||||
a thread's subagents collapsed and open, Load earlier with markdown, the changes line and Changed
|
||||
list, search and its empty state, the complication's two modes, the Smart Stack widget, and a question
|
||||
notification are rendered by `SnapshotTests` instead, which only run when given a directory:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// One thread: the agent's latest message large, older messages one scroll up with Load earlier above
|
||||
// them, the pending approval or question with its options, the thread's subagents, the files its
|
||||
// latest run changed with their line counts, Reply by
|
||||
// them, the pending approval or question with its options, the thread's subagents under a header
|
||||
// that collapses them, the files its latest run changed with their line counts, 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. Message text
|
||||
// renders as markdown.
|
||||
@@ -29,21 +29,26 @@ struct ThreadView: View {
|
||||
/// Pages Load earlier fetched. The 10 s refresh replaces only the latest window.
|
||||
@State private var history = MessageHistory()
|
||||
@State private var loadingEarlier = false
|
||||
/// Whether the Subagents section is open, once tapped. Nil follows the default: open while a
|
||||
/// subagent waits on you, closed otherwise. Kept for as long as the screen is.
|
||||
@State private var subagentsOpen: Bool?
|
||||
|
||||
/// Snapshots render the column without its ScrollView, which ImageRenderer leaves blank.
|
||||
private let scrolls: Bool
|
||||
|
||||
/// `detail` and `selections` start the screen with a thread loaded and options ticked, for
|
||||
/// snapshots and previews.
|
||||
/// `detail`, `selections` and `subagentsOpen` start the screen with a thread loaded, options
|
||||
/// ticked and the Subagents section set, for snapshots and previews.
|
||||
init(
|
||||
hostId: String, threadId: String,
|
||||
detail: ThreadDetail? = nil, selections: [String: Set<String>] = [:], scrolls: Bool = true
|
||||
detail: ThreadDetail? = nil, selections: [String: Set<String>] = [:], subagentsOpen: Bool? = nil,
|
||||
scrolls: Bool = true
|
||||
) {
|
||||
self.hostId = hostId
|
||||
self.threadId = threadId
|
||||
self.scrolls = scrolls
|
||||
_detail = State(initialValue: detail)
|
||||
_selections = State(initialValue: selections)
|
||||
_subagentsOpen = State(initialValue: subagentsOpen)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -144,11 +149,14 @@ struct ThreadView: View {
|
||||
}
|
||||
|
||||
if !detail.subagents.isEmpty {
|
||||
sectionLabel("Subagents")
|
||||
ForEach(detail.subagents) { row in
|
||||
ThreadLine(row: row) {
|
||||
Text(row.statusWord)
|
||||
.foregroundStyle(row.isActive ? row.color : Theme.secondaryText)
|
||||
let open = subagentsOpen ?? detail.subagents.contains { $0.statusWord == "waiting" }
|
||||
subagentsHeader(detail.subagents, open: open)
|
||||
if open {
|
||||
ForEach(detail.subagents) { row in
|
||||
ThreadLine(row: row) {
|
||||
Text(row.statusWord)
|
||||
.foregroundStyle(row.isActive ? row.color : Theme.secondaryText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +208,7 @@ struct ThreadView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// A small grey title over a list, such as Subagents or Changed.
|
||||
/// A small grey title over a list, such as Changed.
|
||||
private func sectionLabel(_ title: String) -> some View {
|
||||
Text(title)
|
||||
.font(.system(size: 11, weight: .semibold))
|
||||
@@ -209,6 +217,35 @@ struct ThreadView: View {
|
||||
.padding(.top, 4)
|
||||
}
|
||||
|
||||
/// The Subagents title as a row that opens and closes the list, with the count, how many wait or
|
||||
/// work, and a chevron on the right, as in "2 · 1 working".
|
||||
private func subagentsHeader(_ rows: [ThreadRow], open: Bool) -> some View {
|
||||
let counts = ["waiting", "working"].compactMap { word -> String? in
|
||||
let count = rows.count { $0.statusWord == word }
|
||||
return count > 0 ? "\(count) \(word)" : nil
|
||||
}
|
||||
let summary = (["\(rows.count)"] + counts).joined(separator: " · ")
|
||||
return Button { subagentsOpen = !open } label: {
|
||||
HStack(spacing: 4) {
|
||||
Text("Subagents")
|
||||
Spacer(minLength: 4)
|
||||
Text(summary)
|
||||
.lineLimit(1)
|
||||
Image(systemName: open ? "chevron.down" : "chevron.right")
|
||||
.font(.system(size: 10, weight: .semibold))
|
||||
.frame(width: 12)
|
||||
}
|
||||
.font(.system(size: 11, weight: .semibold))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Subagents, \(summary)")
|
||||
.accessibilityValue(open ? "Expanded" : "Collapsed")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func requestCards(_ request: PendingRequest, from via: FeedItem.Via?) -> some View {
|
||||
if request.isQuestion {
|
||||
|
||||
@@ -127,6 +127,7 @@ struct SnapshotTests {
|
||||
@Test func threadWithSubagents() async throws {
|
||||
let detail = try await DemoGateway().thread(hostId: "server-gib", threadId: "t-container")
|
||||
try write(titled("Thread", ThreadView(hostId: "server-gib", threadId: "t-container", detail: detail, scrolls: false)), "thread-subagents.png")
|
||||
try write(titled("Thread", ThreadView(hostId: "server-gib", threadId: "t-container", detail: detail, subagentsOpen: true, scrolls: false)), "thread-subagents-open.png")
|
||||
}
|
||||
|
||||
@Test func threadWithEarlierAndMarkdown() async throws {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 90 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 65 KiB |
Reference in new issue
Block a user