Move questions and reviews into the transcript; agents answer findings
This commit is contained in:
@@ -2,33 +2,12 @@ import AppKit
|
||||
import ByreAPI
|
||||
import SwiftUI
|
||||
|
||||
enum TabBadge {
|
||||
case none
|
||||
case count(Int, String)
|
||||
}
|
||||
|
||||
struct TabBadgeView: View {
|
||||
let badge: TabBadge
|
||||
|
||||
var body: some View {
|
||||
switch badge {
|
||||
case .none:
|
||||
EmptyView()
|
||||
case .count(let n, let help):
|
||||
Badge(count: n)
|
||||
.help(help)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let tabLabels: [TaskTab: String] = [
|
||||
.transcript: "Transcript",
|
||||
.board: "Board",
|
||||
.changes: "Changes",
|
||||
.files: "Files",
|
||||
.terminal: "Terminal",
|
||||
.questions: "Questions",
|
||||
.reviews: "Reviews",
|
||||
.workflow: "Workflow",
|
||||
.project: "Project",
|
||||
]
|
||||
@@ -94,30 +73,24 @@ struct MainView: View {
|
||||
struct PaneTabButton: View {
|
||||
let label: String
|
||||
var active = false
|
||||
var attention = false
|
||||
var badge = TabBadge.none
|
||||
let action: () -> Void
|
||||
@Environment(\.paneAccent) private var accent
|
||||
@ViewState private var hover = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 6) {
|
||||
Text(label)
|
||||
TabBadgeView(badge: badge)
|
||||
}
|
||||
.font(Fonts.font(13, weight: attention ? 700 : 400))
|
||||
.foregroundStyle(hover ? Palette.text : attention ? Palette.warn : active ? Palette.text : Palette.muted)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.background(attention ? Palette.warnTint : .clear)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle()
|
||||
.fill(active ? accent : attention ? Palette.warn : .clear)
|
||||
.frame(height: 2)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
Text(label)
|
||||
.font(Fonts.font(13))
|
||||
.foregroundStyle(hover || active ? Palette.text : Palette.muted)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle()
|
||||
.fill(active ? accent : .clear)
|
||||
.frame(height: 2)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.fixedSize()
|
||||
@@ -196,7 +169,6 @@ private struct LeftPane: View {
|
||||
private struct TaskPane: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var seen: Set<String> = []
|
||||
|
||||
var body: some View {
|
||||
let selected = store.selectedTaskId.flatMap { store.findTask($0) }
|
||||
@@ -219,18 +191,6 @@ private struct TaskPane: View {
|
||||
} action: { frame in
|
||||
overlays.paneFrame = frame
|
||||
}
|
||||
.onAppear {
|
||||
seen = Set(store.inbox.map(\.id))
|
||||
}
|
||||
.onChange(of: store.inbox.map(\.id)) {
|
||||
let fresh = store.inbox.filter { !seen.contains($0.id) }
|
||||
for item in fresh {
|
||||
seen.insert(item.id)
|
||||
}
|
||||
if store.focus.tab == .transcript && !store.paneFocus.drafting && fresh.contains(where: { $0.taskId == store.selectedTaskId }) {
|
||||
store.focusTab(.questions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -247,10 +207,6 @@ private struct TaskPane: View {
|
||||
FilesView()
|
||||
case .terminal:
|
||||
TerminalTab()
|
||||
case .questions:
|
||||
QuestionsView()
|
||||
case .reviews:
|
||||
ReviewsView()
|
||||
case .workflow:
|
||||
WorkflowView()
|
||||
case .project:
|
||||
@@ -381,27 +337,10 @@ private struct TabStrip: View {
|
||||
private struct Info {
|
||||
var order: [TaskTab]
|
||||
var tab: TaskTab
|
||||
var waiting: Int
|
||||
var findings: Int
|
||||
var reviewing: Bool
|
||||
|
||||
func count(_ t: TaskTab) -> Int {
|
||||
t == .questions ? waiting : t == .reviews && !reviewing ? findings : 0
|
||||
}
|
||||
|
||||
func badge(_ t: TaskTab) -> TabBadge {
|
||||
count(t) > 0 ? .count(count(t), t == .questions ? "open questions" :"open findings in the latest review") : .none
|
||||
}
|
||||
}
|
||||
|
||||
private func info() -> Info {
|
||||
let tab = store.focus.tab
|
||||
let waiting = store.inbox.filter { $0.taskId == store.selectedTaskId }.count
|
||||
let transcript = store.transcript
|
||||
let latest = latestReview(transcript)
|
||||
let reviewing = latest?.state == .running
|
||||
let findings = latest.map(findingCount) ?? 0
|
||||
let hasReviews = transcript.contains(where: isReviewEvent)
|
||||
let workflow = store.selectedTaskId.flatMap { store.findTask($0) }?.workflowRun != nil
|
||||
let team = store.selectedTaskId.map { !store.team($0).isEmpty } ?? false
|
||||
let order = taskTabs.filter { t in
|
||||
@@ -410,13 +349,11 @@ private struct TabStrip: View {
|
||||
}
|
||||
switch t {
|
||||
case .board: return team
|
||||
case .questions: return waiting > 0
|
||||
case .reviews: return hasReviews
|
||||
case .workflow: return workflow
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
return Info(order: order, tab: tab, waiting: waiting, findings: findings, reviewing: reviewing)
|
||||
return Info(order: order, tab: tab)
|
||||
}
|
||||
|
||||
private func kept(_ info: Info) -> [TaskTab] {
|
||||
@@ -444,17 +381,16 @@ private struct TabStrip: View {
|
||||
let info = info()
|
||||
let kept = kept(info)
|
||||
let hidden = info.order.filter { !kept.contains($0) }
|
||||
let flagged = hidden.filter { info.count($0) > 0 }
|
||||
HStack(spacing: 4) {
|
||||
ForEach(info.order.filter { kept.contains($0) }, id: \.self) { t in
|
||||
PaneTabButton(label: tabLabels[t]!, active: t == info.tab, attention: info.count(t) > 0, badge: info.badge(t)) {
|
||||
PaneTabButton(label: tabLabels[t]!, active: t == info.tab) {
|
||||
store.focusTab(t)
|
||||
}
|
||||
}
|
||||
if !hidden.isEmpty {
|
||||
MoreButton(badges: flagged.map(info.badge)) {
|
||||
MoreButton {
|
||||
overlays.menu = OverlayMenu(anchor: moreFrame, accent: accent, entries: hidden.map { t in
|
||||
MenuEntry(id: t.rawValue, label: tabLabels[t]!, badge: info.badge(t)) {
|
||||
MenuEntry(id: t.rawValue, label: tabLabels[t]!) {
|
||||
store.focusTab(t)
|
||||
}
|
||||
})
|
||||
@@ -473,14 +409,14 @@ private struct TabStrip: View {
|
||||
.background {
|
||||
HStack(spacing: 4) {
|
||||
ForEach(info.order, id: \.self) { t in
|
||||
PaneTabButton(label: tabLabels[t]!, active: t == info.tab, attention: info.count(t) > 0, badge: info.badge(t)) {}
|
||||
PaneTabButton(label: tabLabels[t]!, active: t == info.tab) {}
|
||||
.onGeometryChange(for: CGFloat.self) { g in
|
||||
g.size.width
|
||||
} action: { w in
|
||||
widths[t] = w
|
||||
}
|
||||
}
|
||||
MoreButton(badges: []) {}
|
||||
MoreButton {}
|
||||
.onGeometryChange(for: CGFloat.self) { g in
|
||||
g.size.width
|
||||
} action: { w in
|
||||
@@ -502,28 +438,18 @@ private struct TabStrip: View {
|
||||
}
|
||||
|
||||
private struct MoreButton: View {
|
||||
let badges: [TabBadge]
|
||||
let action: () -> Void
|
||||
@ViewState private var hover = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 6) {
|
||||
Text("More ▾")
|
||||
ForEach(Array(badges.enumerated()), id: \.offset) { _, b in
|
||||
TabBadgeView(badge: b)
|
||||
}
|
||||
}
|
||||
.font(Fonts.font(13, weight: badges.isEmpty ? 400 : 700))
|
||||
.foregroundStyle(hover ? Palette.text : badges.isEmpty ? Palette.muted : Palette.warn)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.background(badges.isEmpty ? .clear : Palette.warnTint)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle().fill(badges.isEmpty ? .clear : Palette.warn).frame(height: 2)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
Text("More ▾")
|
||||
.font(Fonts.font(13))
|
||||
.foregroundStyle(hover ? Palette.text : Palette.muted)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 6)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.fixedSize()
|
||||
|
||||
Reference in New Issue
Block a user