Add review workflows, tree-shaped workflows and a workflow editor
This commit is contained in:
+2
-1
@@ -61,6 +61,7 @@ SwiftUI's own macros don't build here: the SDK declares `@State`, `@Entry`, `@An
|
||||
| `Workflow.swift` | Stage graph and attempts |
|
||||
| `Project.swift` | Project tab and TODO items |
|
||||
| `Notes.swift`, `Workflows.swift`, `Status.swift`, `Settings.swift` | Left-pane tabs |
|
||||
| `WorkflowEditor.swift` | The Workflows tab's editor: a workflow's steps as a tree, with each stage's model and prompt |
|
||||
| `Switcher.swift` | ⌘K overlay, the shortcuts help and the Go actions (`runGo`) |
|
||||
| `Drafts.swift` | Composer, answer and line-comment drafts |
|
||||
| `Notifications.swift` | `UNUserNotificationCenter` delivery |
|
||||
@@ -104,7 +105,7 @@ Connections belong to `Servers`, not to the window: every saved server's store a
|
||||
|
||||
## Views
|
||||
|
||||
- **Transcript and Lead.** `TranscriptBuilder` is a pure function from events to a list of rows: user and agent messages, consecutive tool calls, byre prompts and subagents folded into one group with its summary ("Read file ×3"), subagents nested with their status, pending tool calls and running subagents (background ones included after the turn ends) outlined in the accent colour with a 0.6s fade, thinking, the byre prompt collapsed, held turns, `NO_REPLY` turns folded away, handoffs, snapshot rows with changes, fork and review (and stop while the review runs), question bubbles that follow their item's `inbox.updated` events, a finished review as its summary row followed by one row per finding, the agent's `respond_to_finding` calls as their own rows, and notes for the other event kinds. Rows that hold a review (`Row.review`) are kept per review id and updated by each later `review.*` event. It is incremental: a new batch extends the existing rows. The composer bar's status (`StatusButton`, from `ChatModel.status`) shows the task's closed state, its working state, or what needs the user (`ChatModel.attention`: open questions, findings of the latest review that neither the user nor the agent has answered, and the current ready-for-merge overview); a click on one of the latter scrolls to the next one, wrapping, and on any other to the end; and when one of those rows closes the transcript scrolls to the next remaining one (`ChatModel.closed`). Jumps from elsewhere (`Store.jumpTo` with a `JumpTarget`: a summary, a question or a snapshot's review) select the task's transcript and scroll there once the row exists. The view is a `ScrollView` with a plain `VStack`, stuck to the bottom unless the user has scrolled more than 80 points up; while an attention row is held (`ChatModel.held`), being stuck instead keeps that row top-anchored, which the scroll view clamps to the bottom until the row reaches the top. The stack isn't lazy because a `LazyVStack` estimates the height of rows it hasn't shown yet and corrects it when they scroll into view, without moving the scroll offset, so scrolling up through rows not yet shown jumped by each correction; SwiftUI can't move the offset mid-scroll without stopping its momentum. While the user is scrolling (`onScrollPhaseChange`), content height changes only update whether it is stuck and never pull it back to the bottom. The lead pages back when the view comes within one screen of the top: the fetched page is held while the scroll phase is scrolling and inserted once it is idle (`ChatModel.older`, `prependOlder`), and the next geometry change moves the offset down by the height added, so the rows on screen stay put; a page that adds no rows (all folded `NO_REPLY` turns) fetches the next one at once. Each Markdown text run's `NSTextView` measures its height with one TextKit stack kept in its coordinator, and a repeat measurement at the same width and text returns the stored size. Pending sends show at the end until the server echoes them. A turn summary counts as read when its row is on screen and its server's window is the key window of the active app (`PaneFocus.windowKey`, `markSummaryRead`), checked again whenever the app activates or a window becomes key. An active task's head carries Compact (with its context-usage fill; a long press opens a menu of models to continue on), Sync from main, Merge and Discard; a closed task has no head, and its `task.merged` note is drawn with a ✓ in the ok colour.
|
||||
- **Transcript and Lead.** `TranscriptBuilder` is a pure function from events to a list of rows: user and agent messages, consecutive tool calls, byre prompts and subagents folded into one group with its summary ("Read file ×3"), subagents nested with their status, pending tool calls and running subagents (background ones included after the turn ends) outlined in the accent colour with a 0.6s fade, thinking, the byre prompt collapsed, held turns, `NO_REPLY` turns folded away, handoffs, snapshot rows with changes, fork and review (and stop while the review runs), question bubbles that follow their item's `inbox.updated` events, one folded row per reviewer session holding that reviewer's events as a subagent's, with its latest step while it runs and its outcome after, a finished review with findings as its summary row followed by one row per finding, the agent's `respond_to_finding` calls as their own rows, and notes for the other event kinds. Rows that hold a review (`Row.review`) are kept per review id and updated by each later `review.*` event. It is incremental: a new batch extends the existing rows. The composer bar's status (`StatusButton`, from `ChatModel.status`) shows the task's closed state, its working state, or what needs the user (`ChatModel.attention`: open questions, findings of the latest review that neither the user nor the agent has answered, and the current ready-for-merge overview); a click on one of the latter scrolls to the next one, wrapping, and on any other to the end; and when one of those rows closes the transcript scrolls to the next remaining one (`ChatModel.closed`). Jumps from elsewhere (`Store.jumpTo` with a `JumpTarget`: a summary, a question or a snapshot's review) select the task's transcript and scroll there once the row exists. The view is a `ScrollView` with a plain `VStack`, stuck to the bottom unless the user has scrolled more than 80 points up; while an attention row is held (`ChatModel.held`), being stuck instead keeps that row top-anchored, which the scroll view clamps to the bottom until the row reaches the top. The stack isn't lazy because a `LazyVStack` estimates the height of rows it hasn't shown yet and corrects it when they scroll into view, without moving the scroll offset, so scrolling up through rows not yet shown jumped by each correction; SwiftUI can't move the offset mid-scroll without stopping its momentum. While the user is scrolling (`onScrollPhaseChange`), content height changes only update whether it is stuck and never pull it back to the bottom. The lead pages back when the view comes within one screen of the top: the fetched page is held while the scroll phase is scrolling and inserted once it is idle (`ChatModel.older`, `prependOlder`), and the next geometry change moves the offset down by the height added, so the rows on screen stay put; a page that adds no rows (all folded `NO_REPLY` turns) fetches the next one at once. Each Markdown text run's `NSTextView` measures its height with one TextKit stack kept in its coordinator, and a repeat measurement at the same width and text returns the stored size. Pending sends show at the end until the server echoes them. A turn summary counts as read when its row is on screen and its server's window is the key window of the active app (`PaneFocus.windowKey`, `markSummaryRead`), checked again whenever the app activates or a window becomes key. An active task's head carries Compact (with its context-usage fill; a long press opens a menu of models to continue on), Sync from main, Merge and Discard; a closed task has no head, and its `task.merged` note is drawn with a ✓ in the ok colour.
|
||||
- **Composer.** A `TextEditor`, with paste and drop handled as in "Gaps in SwiftUI". Text, the model menu (filtered to the live session's vendor), Interrupt and Send. Enter sends, Shift-Enter adds a newline, and Enter during IME composition does neither. Pasted or dropped PNG, JPEG, GIF and WebP images become thumbnails with ×, uploaded to `POST /api/blobs` on send; any other image `NSBitmapImageRep` decodes, such as TIFF, is converted to PNG first, its size checked after conversion. A failed send puts the text and images back and shows the error. The ⌄/⌃ button collapses the text box (with its thumbnails) into the bar and brings it back; it is collapsed by default while the task has an open question or is merged or discarded (`ChatModel.composerHiddenByDefault`), and the human's choice holds until that default changes or an explicit focus request (picking the task in the switcher, focusing the lead) opens it; the automatic focus of a task with no session yet only focuses a box that is shown.
|
||||
- **Questions.** An open question's bubble in the transcript holds its card, rendered from its Markdown context with each choice's marker replaced by a clickable block (pick-one, sends at once, with any Other… text as notes) or checkbox (pick-any, with Submit), the "recommended" tag, image-choice grids, attachments, Let the agent decide and Other…. Arrow keys move between choices. A 409 shows its error. Once answered or dismissed, the bubble shrinks to the question and who answered what.
|
||||
- **Changes, Files, Workflow, Project, Status, Settings.** Including the pinned-snapshot stepper, open-diff memory, per-task place in Files, nested subtasks, TODO Start and Discuss, rate-limit pace bars and lead notes. The Workflow tab draws its stage graph natively from the `WorkflowRun` (stages as boxes coloured by state, `after` as edges, laid out top to bottom by depth) with the layout and drawing code from "Diagrams", fed from the run's data rather than Mermaid text.
|
||||
|
||||
Reference in New Issue
Block a user