new task all projects

This commit is contained in:
Ian Gulliver
2026-10-20 10:16:31 -07:00
parent 1d53304e27
commit ff986d8d32
2 changed files with 7 additions and 3 deletions
+1 -1
View File
@@ -280,7 +280,7 @@ Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and
**Drafts.** Unsent composer text (task and lead) and unsent inbox answers (checked choices and Other text) are kept in memory by `Drafts` (`mac/Sources/Byre/Drafts.swift`) and saved to a SwiftData `DraftRecord` per server and login (`docs/macos.md`, "Drafts"), one JSON object with `composer` (keyed by `message:<task id>` or `message:lead`, each `{text, images}`), `checked` and `texts` (both keyed by inbox item id), and `comments`, the Changes view's pending line comments keyed by task id, each `{anchor: {path, line, old, quote}, text}`. A comment shows only under a diff line whose path, line number, side and text all still match, so after the agent moves lines a comment never attaches to a different line. Every pending comment not shown under a line (its file closed or not loaded, or its line gone from the diff) is listed at the top of the Changes view with its `path:line`, the quoted line, its editable text and **remove**, so nothing is sent unseen. Writes are debounced (400 ms) and flushed at once when a composer or answer box loses focus, on send and on quit. A composer draft is cleared only once its send succeeds, and an inbox answer only once its response is accepted; each send in flight is tracked on its own and the stored draft holds the text of every unconfirmed send (oldest first, then the composer's current text, separated by blank lines) and counts its images, so a relaunch mid-send brings back everything not yet confirmed. A failed send's text goes back into the composer ahead of whatever is there now (text typed since, or earlier failures), separated by a blank line, rather than replacing it. The composer follows its `Drafts` text as it changes, so text put back from outside it (a withdrawn queued message, a draft synced in from another Mac) shows at once. Drafts for tasks that are no longer active and for inbox items no longer open are pruned whenever the task list or inbox reloads. A record that doesn't decode is logged and ignored, as is a write that fails; drafts then live only in memory. Pending images are not stored, only their count: after a relaunch the composer shows how many need re-attaching, until dismissed or the next successful send.
The task switcher (⌘K, or the project ▸ task picker centred in the right pane's top row) lists active tasks with their task state and open inbox item count (the Project tab shows the same state), live while it is open. Tasks are grouped by state (`taskRank` in `mac/Sources/Byre/Store.swift`, which also orders the Project tab's task list, ties in the server's order, subtasks sorted among their siblings under their parent): with inbox items first, then **Ready to merge** or **Ready to discard**, then idle, then working or reviewing, and merging, held or discarding last; within each group, the task least recently visited comes first (never-visited tasks before all others, the right pane's own task after all others, ties in the server's order: projects by name, tasks newest first), so ⌘K Enter repeated cycles through the tasks. Visit times are SwiftData `VisitRecord`s synced through iCloud (`docs/macos.md`, "Persistence"), stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. With nothing typed, a last "New task in …" row creates an untitled task with no session in the right pane's project (the first project when there is none) and opens it with its composer focused; it shows the user's default model (see "Agent sessions"), the model the composer offers first for the new task's first message. ⌘N (File ▸ New Task) does the same without the switcher. Typing filters tasks and projects and replaces that row with a "Start in …" row, which creates an untitled task and starts its agent on the default model with the typed text as its first message, through `POST /api/projects/{id}/tasks` with `prompt` set to the text and no `title`; Enter picks the highlighted row. Text before a first colon that starts at least one project's name (ignoring case, no spaces) picks those projects instead: `by: fix` lists only their tasks whose titles contain "fix", their project rows that match, and the create rows for each of those projects with "fix" as the message. When no project name starts with it, the whole text is the message, so "Fix: login" stays as typed. The app shows an untitled task as "New task" until its agent names it. Picking a task points the right pane at it on the Transcript tab, scrolled to its oldest open question when it has open inbox items, otherwise to its latest summary when that is unread (see below and "Lead agent").
The task switcher (⌘K, or the project ▸ task picker centred in the right pane's top row) lists active tasks with their task state and open inbox item count (the Project tab shows the same state), live while it is open. Tasks are grouped by state (`taskRank` in `mac/Sources/Byre/Store.swift`, which also orders the Project tab's task list, ties in the server's order, subtasks sorted among their siblings under their parent): with inbox items first, then **Ready to merge** or **Ready to discard**, then idle, then working or reviewing, and merging, held or discarding last; within each group, the task least recently visited comes first (never-visited tasks before all others, the right pane's own task after all others, ties in the server's order: projects by name, tasks newest first), so ⌘K Enter repeated cycles through the tasks. Visit times are SwiftData `VisitRecord`s synced through iCloud (`docs/macos.md`, "Persistence"), stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. With nothing typed, the last rows are one "New task in …" row per project, the right pane's project first, then the rest by most recently used (the latest visit to any of the project's tasks, ties in the server's order); each creates an untitled task with no session in its project and opens it with its composer focused, and shows the user's default model (see "Agent sessions"), the model the composer offers first for the new task's first message. ⌘N (File ▸ New Task) creates one in the right pane's project (the first project when there is none) without the switcher. Typing filters tasks and projects and replaces each of those rows with a "Start in …" row (and a "Start workflow… in …" row), which creates an untitled task and starts its agent on the default model with the typed text as its first message, through `POST /api/projects/{id}/tasks` with `prompt` set to the text and no `title`; Enter picks the highlighted row. Text before a first colon that starts at least one project's name (ignoring case, no spaces) picks those projects instead: `by: fix` lists only their tasks whose titles contain "fix", their project rows that match, and the create rows for each of those projects with "fix" as the message. When no project name starts with it, the whole text is the message, so "Fix: login" stays as typed. The app shows an untitled task as "New task" until its agent names it. Picking a task points the right pane at it on the Transcript tab, scrolled to its oldest open question when it has open inbox items, otherwise to its latest summary when that is unread (see below and "Lead agent").
**Task state.** The switcher and the Project tab show one label per task for its `state` (see "Task state in the store"; `stateTitles` in `mac/Sources/Byre/Store.swift`), with a dot and a tooltip saying why: **Needs your answer** (`asking`), **Merging** (`merging`), **Merge held** (`merge_held`), **Merge after review** (`merge_after_review`), **Reviewing** (`review_pending` or `reviewing`), **Working** (`running`), **Discarding** (`discarding`), **Ready to merge** (`ready_to_merge`), **Ready to discard** (`discard_suggested`), **Idle** (`idle` or `no_agent`). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Reviewing**, **Working**, **Discarding** and **Merging**. The label follows the tasks the app already holds, so it needs no extra fetch.
+6 -2
View File
@@ -217,8 +217,12 @@ final class SwitcherModel {
for p in (targets ?? store.projects) where includes("\(p.name) project".lowercased(), q) {
rows.append(SwitcherRow(id: "project:\(p.id)", kind: .project(p)))
}
let fallback = store.focusedProject() ?? store.projects.first
for project in targets ?? (fallback.map { [$0] } ?? []) {
let focused = store.focusedProject()?.id
let used = store.projects.enumerated().map { i, p in
(i: i, project: p, at: p.id == focused ? Double.infinity : (store.tasks[p.id] ?? []).map { visits[$0.id] ?? 0 }.max() ?? 0)
}
let recent = used.sorted { $0.at != $1.at ? $0.at > $1.at : $0.i < $1.i }.map(\.project)
for project in targets ?? recent {
if title.isEmpty {
rows.append(SwitcherRow(id: "new:\(project.id)", kind: .newTask(project)))
continue