Watch: snooze finished threads, name the files a file approval would edit, Settle on finished notifications, an instant launch from the saved feed, plain Loading states, and no more cancelled errors
Gateway: an approval push for a file change says which files it would edit
This commit is contained in:
1 parent
4dc4e57dac
commit
edc4b88990
35 files changed
+1162
-144
No files matched your search
@@ -1,8 +1,10 @@
|
||||
// Mock B's attention feed: the count waiting on you, then cards for every host. Needs you first
|
||||
// (with Allow and Deny on the card), then working (with the step the agent is on and how long it
|
||||
// has run), then recent. A Recent row swipes left to settle, and the Recent header settles every
|
||||
// completed thread. Past Recent's first 10, an "N more" row opens the full Recent list. The last
|
||||
// row opens All threads.
|
||||
// (with Allow and Deny on the card, and the files a file approval would edit), then working (with
|
||||
// the step the agent is on and how long it has run), then recent. A Recent row swipes left to settle
|
||||
// or snooze, and the Recent header settles every completed thread. Past Recent's first 10, an "N
|
||||
// more" row opens the full Recent list, and a violet row under it opens the snoozed threads. The last
|
||||
// row opens All threads. At launch the feed saved last time shows at once, marked Updating until the
|
||||
// first refresh answers.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@@ -13,7 +15,7 @@ struct FeedView: View {
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
FeedHeader(feed: model.feed, error: model.feedError)
|
||||
FeedHeader(feed: model.feed, error: model.feedError, cached: model.feedIsCached)
|
||||
.feedRow()
|
||||
|
||||
if let feed = model.feed {
|
||||
@@ -34,6 +36,9 @@ struct FeedView: View {
|
||||
if more > 0 {
|
||||
MoreRecentRow(count: more).feedRow()
|
||||
}
|
||||
if feed.snoozedTotal > 0 {
|
||||
SnoozedRow(count: feed.snoozedTotal).feedRow()
|
||||
}
|
||||
if feed.needsYou.isEmpty && feed.working.isEmpty && recent.isEmpty && more == 0 {
|
||||
Text("No threads right now.")
|
||||
.font(.system(size: 13))
|
||||
@@ -87,18 +92,28 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
/// The big count of threads waiting on you, plus offline hosts and refresh errors.
|
||||
private struct FeedHeader: View {
|
||||
/// The big count of threads waiting on you, plus offline hosts and refresh errors. A feed from the
|
||||
/// cache says Updating beside the count until the first refresh answers.
|
||||
struct FeedHeader: View {
|
||||
var feed: Feed?
|
||||
var error: String?
|
||||
var cached = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
if let feed {
|
||||
let waiting = feed.needsYou.count
|
||||
Text("\(waiting)")
|
||||
.font(.system(size: 34, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(waiting > 0 ? Theme.you : Theme.done)
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text("\(waiting)")
|
||||
.font(.system(size: 34, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(waiting > 0 ? Theme.you : Theme.done)
|
||||
Spacer(minLength: 4)
|
||||
if cached {
|
||||
Text("Updating")
|
||||
.font(.system(size: 11, weight: .semibold))
|
||||
.foregroundStyle(Theme.done)
|
||||
}
|
||||
}
|
||||
Text(subtitle(feed))
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
@@ -166,7 +181,8 @@ struct RecentHeader: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// A Recent thread: its card, which opens the thread, and a left swipe to settle it.
|
||||
/// A Recent thread: its card, which opens the thread, and a left swipe to settle it or to snooze it,
|
||||
/// which opens the Snooze until picker.
|
||||
struct RecentRow: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var item: FeedItem
|
||||
@@ -174,10 +190,15 @@ struct RecentRow: View {
|
||||
var body: some View {
|
||||
StatusCard(item: item)
|
||||
.swipeActions(edge: .trailing) {
|
||||
// The first button sits at the edge, and a full swipe runs it.
|
||||
Button { Task { await model.settle([item]) } } label: {
|
||||
Label("Settle", systemImage: "checkmark")
|
||||
}
|
||||
.tint(Theme.work)
|
||||
Button { model.snoozing = item } label: {
|
||||
Label("Snooze", systemImage: "moon.fill")
|
||||
}
|
||||
.tint(Theme.snooze)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +253,8 @@ struct AllThreadsRow: View {
|
||||
}
|
||||
|
||||
/// An amber card for a thread waiting on an approval or an answer, with Allow and Deny inline.
|
||||
/// When a subagent asked, one line names it, and the answer goes to the subagent.
|
||||
/// When a subagent asked, one line names it, and the answer goes to the subagent. A file approval
|
||||
/// shows "Wants to edit" and the files in place of its prompt.
|
||||
struct NeedsYouCard: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var item: FeedItem
|
||||
@@ -252,13 +274,16 @@ struct NeedsYouCard: View {
|
||||
.lineLimit(2)
|
||||
if let via = item.via, let request = item.request {
|
||||
let name = Text(via.title).bold().foregroundStyle(.white)
|
||||
Text(request.isQuestion ? "Subagent \(name) asks:" : "Subagent \(name) wants to run:")
|
||||
Text(request.isQuestion ? "Subagent \(name) asks:" : request.fileEdit != nil ? "Subagent \(name):" : "Subagent \(name) wants to run:")
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
.lineLimit(2)
|
||||
.padding(.top, 2)
|
||||
}
|
||||
if let request = item.request {
|
||||
if let edit = item.request?.fileEdit {
|
||||
FileEditRows(edit: edit, limit: 3)
|
||||
.padding(.top, 2)
|
||||
} else if let request = item.request {
|
||||
Text(request.summary)
|
||||
.font(request.isQuestion ? .system(size: 12) : .system(size: 12, design: .monospaced))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
|
||||
Reference in new issue
Block a user