Remove the task Project tab; ⌘P selects the project

This commit is contained in:
Ian Gulliver
2026-10-21 20:53:54 -07:00
parent f0213e2aed
commit e6da67087e
10 changed files with 230 additions and 281 deletions
-1
View File
@@ -52,7 +52,6 @@ final class Overlays {
var switcher: SwitcherModel?
var leftTab = LeftTab.lead
var leadFocus = 0
var projectFocus = 0
@ObservationIgnored var pickerFrame: CGRect = .zero
@ObservationIgnored var menuHover = false
var paneFrame: CGRect = .zero
+9 -8
View File
@@ -10,7 +10,6 @@ let tabLabels: [TaskTab: String] = [
.page: "Page",
.terminal: "Terminal",
.workflow: "Workflow",
.project: "Project",
]
struct HalfSplit: Layout {
@@ -191,9 +190,13 @@ private struct TaskPane: View {
VStack(spacing: 0) {
TaskBar()
VStack(spacing: 0) {
TabStrip()
TaskTabBody(tab: store.primaryTab)
.frame(maxWidth: .infinity, maxHeight: .infinity)
if store.selectedTaskId == nil {
ProjectView()
} else {
TabStrip()
TaskTabBody(tab: store.primaryTab)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.overlay {
if let sheet = store.sheet, sheet.owner == .lead {
@@ -239,8 +242,6 @@ struct TaskTabBody: View {
TerminalTab()
case .workflow:
WorkflowView()
case .project:
ProjectView()
}
}
}
@@ -377,13 +378,13 @@ private struct TabStrip: View {
private func info() -> Info {
let tab = store.sheet.flatMap { $0.owner == .lead ? nil : $0.tab } ?? store.primaryTab
guard let taskId = store.selectedTaskId else {
return Info(order: [.project], tab: .project)
return Info(order: [], tab: tab)
}
let run = store.findTask(taskId)?.workflow != nil
let team = !store.team(taskId).isEmpty
let order = taskTabs.filter { t in
if run {
return t == .workflow || t == .project
return t == .workflow
}
if t == tab {
return true
+8 -56
View File
@@ -8,48 +8,22 @@ private let refreshKinds: Set<String> = ["task.merged", "project.created"]
struct ProjectView: View {
@Environment(Store.self) private var store
@Environment(Overlays.self) private var overlays
@ViewState private var todos: [String: Result<Todo, ApiError>] = [:]
@ViewState private var off: (() -> Void)?
@FocusState private var keys: Bool
var body: some View {
ScrollViewReader { proxy in
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id])
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
}
}
.onChange(of: store.selectedTaskId) {
if let id = store.selectedTaskId {
proxy.scrollTo(id)
}
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id])
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.focusable()
.focusEffectDisabled()
.focused($keys)
.onKeyPress(keys: [.upArrow, .downArrow]) { press in
step(press.key == .downArrow ? 1 : -1)
return .handled
}
.onKeyPress(.return) {
guard store.selectedTaskId != nil, store.focusedProject() != nil else {
return .ignored
}
store.sheet = nil
return .handled
}
.onAppear {
if !(overlays.window?.firstResponder is NSTextView) {
keys = true
}
off = store.events.add { ev in
if refreshKinds.contains(ev.kind), let projectId = ev.projectId {
load(projectId)
@@ -57,9 +31,6 @@ struct ProjectView: View {
}
refresh()
}
.onChange(of: overlays.projectFocus) {
keys = true
}
.onDisappear {
off?()
off = nil
@@ -79,25 +50,6 @@ struct ProjectView: View {
}
}
private func step(_ by: Int) {
guard let p = store.focusedProject() else {
return
}
let active = (store.tasks[p.id] ?? []).filter { $0.status == .active }
let rows = nested(store, active).map(\.task.id)
if rows.isEmpty {
return
}
let next: Int
if let i = rows.firstIndex(where: { $0 == store.selectedTaskId }) {
next = min(max(i + by, 0), rows.count - 1)
} else {
next = by > 0 ? 0 : rows.count - 1
}
store.sheet = Sheet(owner: .task(rows[next]), view: .tab(.project))
store.focusTask(rows[next])
}
private func refresh() {
if let p = store.focusedProject() {
load(p.id)
+4 -7
View File
@@ -3,10 +3,10 @@ import Observation
import SwiftData
enum TaskTab: String {
case transcript, board, changes, files, page, terminal, workflow, project
case transcript, board, changes, files, page, terminal, workflow
}
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .page, .terminal, .workflow, .project]
let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .page, .terminal, .workflow]
enum JumpTarget: Equatable {
case question(String)
@@ -470,10 +470,7 @@ final class Store {
}
var primaryTab: TaskTab {
guard let id = selectedTaskId else {
return .project
}
return findTask(id)?.workflow != nil ? .workflow : .transcript
selectedTaskId.flatMap { findTask($0) }?.workflow != nil ? .workflow : .transcript
}
func openTab(_ tab: TaskTab) {
@@ -661,7 +658,7 @@ final class Store {
func selectTask(_ taskId: String?) {
if case .task(let owner)? = sheet?.owner, owner != taskId {
sheet = sheet?.tab == .project && taskId != nil ? Sheet(owner: .task(taskId), view: .tab(.project)) : nil
sheet = nil
}
markVisited(selectedTaskId)
markVisited(taskId)
+5 -5
View File
@@ -17,10 +17,10 @@ func runGo(_ key: String, _ store: Store, _ overlays: Overlays) {
createTask(project.id, .init(), store, overlays)
}
case "p":
overlays.window?.makeFirstResponder(nil)
overlays.projectFocus += 1
if let id = store.selectedTaskId {
store.sheet = Sheet(owner: .task(id), view: .tab(.project))
overlays.switcher?.close()
store.sheet = nil
if let project = store.focusedProject() ?? store.projects.first {
store.focusProject(project.id)
}
case "l":
overlays.switcher?.close()
@@ -71,7 +71,7 @@ private let shortcuts: [(String, [(String, String)])] = [
("Anywhere", [
("⌘K", "Switch task, or create one"),
("⌘N", "New task in the current project"),
("⌘P", "Open the Project tab"),
("⌘P", "Show the current project's dashboard"),
("⌘L", "Open the Lead tab and its composer, closing any view opened over a pane"),
("⌘S", "Merge the right pane's task, or discard it when the agent suggests discarding"),
("⌘[ ⌘]", "Bring the previous or next server's window forward"),