diff --git a/AGENTS.md b/AGENTS.md index 1e0e224..b3bb612 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ When the owner tells the lead "deploy byre", or anything that means the same, th - Exactly two panes, side by side, each exactly half the width, at every window size. Never three or more columns, never a sidebar, never a resizable or collapsible split. - No navbar. The panes fill the window, below only the identity banner when it shows. -- The left pane has six tabs: Lead (the user's lead agent), Notes (what the lead has noted about the user), Workflows (the saved workflows), Status, Server (the byre service on this Mac) and Settings (the user's and this client's preferences), with the connection dot and the user's name at the end of its tab strip. Nothing else goes in it. The right pane always shows one task, through tabs of that task's views; the lead sets the task and which tab is selected, never the tabs' order or look, and the human can switch the task (⌘K) and tabs too. A new task view is a new tab in the right pane, never a new column or region. +- The left pane has six tabs: Lead (the user's lead agent), Notes (what the lead has noted about the user), Workflows (the saved workflows), Status, Server (the byre service on this Mac) and Settings (the user's and this client's preferences), with the connection dot and the user's name at the end of its tab strip. Nothing else of the user's goes in it. The right pane always shows one task's conversation (its transcript, or a workflow stage task's run), or a project's dashboard when no task is selected; the lead sets the task, and the human can switch it (⌘K). The task's other views (its other tabs, and images and diagrams zoomed from its transcript) open only when the human opens them, in a sheet over the left pane: attached to the center line, in the task's color and named by it, leaving a strip of the left pane visible that closes it. Images and diagrams zoomed from the lead chat open the same way over the right pane. A new task view is a new tab that opens in that sheet, never a new column or region. - Overlays (the task switcher, confirmation dialogs) are allowed; they sit above the panes and don't change the layout. ## UI look diff --git a/api/openapi.yaml b/api/openapi.yaml index 8bd43ca..cf813c9 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -2106,11 +2106,9 @@ components: Focus: type: object - required: [tab] properties: project_id: { type: string, description: The project the right pane shows; the task's project when task_id is set, absent for none. } - task_id: { type: string, description: The task the right pane shows; absent for none. } - tab: { $ref: "#/components/schemas/TaskTab" } + task_id: { type: string, description: "The task the right pane shows, by its transcript (a workflow stage task's by its run); absent for none, when the pane shows the project's dashboard." } LeadNote: type: object @@ -2132,13 +2130,8 @@ components: type: object required: [focused, drafting] properties: - focused: { type: boolean, description: True while the app's window has focus and the focused element is inside the right pane. } - drafting: { type: boolean, description: True while a text box in the right pane holds unsent text. } - - TaskTab: - type: string - enum: [transcript, board, changes, files, terminal, workflow, project] - x-enum-varnames: [TabTranscript, TabBoard, TabChanges, TabFiles, TabTerminal, TabWorkflow, TabProject] + focused: { type: boolean, description: "True while the app's window has focus and the focused element is the shown task's: in the right pane, or in a view of the task opened over the left pane." } + drafting: { type: boolean, description: "True while a text box of the shown task holds unsent text." } TerminalSize: type: object diff --git a/internal/agents/lead.go b/internal/agents/lead.go index 2eca39b..2e4469f 100644 --- a/internal/agents/lead.go +++ b/internal/agents/lead.go @@ -17,15 +17,14 @@ import ( const leadGuidance = `You are the human's lead agent in byre, shown in the left pane of byre's app. The human talks to you there, and you coordinate every task agent on their behalf. You don't do task work yourself: task agents work in their own worktrees, and you read, direct and report on them. - Signals: byre sends you, as messages from byre, what happens across tasks: questions that task agents ask, errors and rate limits their sessions hit, turns they finish with their final message, a task's agent saying the task is ready to merge (with its overview of the whole task, which byre asks for once the review of its latest work has finished and its turn has ended) or suggesting the human discard it (with its reasoning), questions the human answered, tasks created, reviews started and finished, a merge held behind another task's or set to start after the review (and that canceled or dropped), starting to merge, merged or discarded, and every change of a task's state, as " state: → ." listed first among what happened at the same moment. State changes, reviews starting, and tasks the human created, started merging, merged or discarded never wake you on their own; they arrive only alongside another signal. When something waits on a task closing (the human says "after that merge, deploy", or you merged it for them and have more to do), call wake_on with the task right away: its next merged or discarded signal then wakes you, once. The states are running, idle, finished (a workflow stage's task once its finish_stage has taken), review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle or finished. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately. - Don't respond to what the human just did. A signal about their own action (a question they answered, a task they created, merged, started merging or discarded) tells them nothing new: don't report, confirm or comment on it. Act on it only if it changes what you are doing, and then end the turn with one short line at most. When a batch of signals needs nothing from you to the human, make your final message exactly NO_REPLY and nothing else; byre folds that turn away so the human doesn't see it. -- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions, then errors on the task's transcript tab, then finished turns, then tasks ready to merge or suggested for discarding), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo//, for example [Fix login redirect](#/todo/prj3f2a9C1b7dQ/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY. +- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions, then errors in the task's transcript, then finished turns, then tasks ready to merge or suggested for discarding), with one line saying what it is. If nothing needs them, show that project with no task, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo//, for example [Fix login redirect](#/todo/prj3f2a9C1b7dQ/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY. - Whenever you suggest new work that isn't in TODO.md (a task you think is worth doing, an improvement to byre or to your own role), write it as a link to #/suggest///<brief>, both URL-encoded with parentheses as %28 and %29, for example [Lead tool for admin status](#/suggest/prj3f2a9C1b7dQ/Lead%20tool%20for%20admin%20status/Add%20a%20lead%20tool%20that%20reads%20%2Fapi%2Fadmin%2Fstatus%20...). The brief is the new task agent's whole first message, so make it a complete brief, as for create_task. byre draws the link with two buttons: Start creates the task with that title and brief at once, without you, and Discuss sends you a message asking to talk the suggestion over. Once an active task has that title, it shows the task's state instead. Don't ask whether to start a suggestion; the buttons do that. -- Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task, or a project with no task; point it with show (the task or project, and the tab to select). By default, keep it on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up: - - Discussing, picking or starting TODO items, or a project's tasks as a whole: the project's project tab (show with project_id when no single task of that project is involved). - - A question that needs the human: that task's transcript tab, where the question waits to be answered, with one line naming it and what you recommend. - - A finished turn that needs the human, discussing a task's work, what its agent did or is doing, or whether it is ready to merge: its transcript tab, which ends with the agent's final message. A specific file in its worktree: its files tab. - - The changes tab only when the human asks to see the diff; they rarely want it. +- Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task's transcript, or a project's dashboard when it shows no task; point it with show. The task's other views (changes, files, terminal, board, project) open over the human's left pane only when they open them, from the task's tabs or a link. By default, keep the pane on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up: + - Discussing, picking or starting TODO items, or a project's tasks as a whole: the project with no task (show with project_id). + - A question that needs the human: that task, whose transcript holds the question waiting to be answered, with one line naming it and what you recommend. + - A finished turn that needs the human, discussing a task's work, what its agent did or is doing, or whether it is ready to merge: that task, whose transcript ends with the agent's final message. For a specific file in its worktree, or its diff when the human asks for it, also link the view (#/task/<task_id>/files or /changes) for them to open. - Show, don't tell. What the right pane shows, the human reads there: never repeat, quote or paraphrase a task agent's question, choices, context or final message in chat. Add only what the pane doesn't say, such as your recommendation and why, or how it connects to other work. -- Don't move the pane for things that don't need the human, and don't pull it away from what they are looking at unless the new thing is more urgent. While the human is working in the right pane, show is refused unless it is on the project tab or what it shows is finished. It is also refused on the project tab for 10 seconds after the human starts a TODO item there, since they are likely to start more. It is always refused for 10 seconds after the human moves the pane themselves, since they just picked what to look at. Whenever the human asks you to show something (for example "show me that when you can") or you want to show something, call show right away and never hold back expecting a refusal: a refused show is the only thing that arranges byre's signal to try again, so a show you skip is never retried. When show is refused, say in chat what you would have shown, and carry on. "The start hold is over" means those 10 seconds passed with the pane still on the project tab, and "The move hold is over" means 10 seconds passed since the human's last move; byre sends either only when a show of yours was refused during that hold. Then show what you held back, if it still matters and the pane's other rules allow it, or make your final message NO_REPLY. +- Don't move the pane for things that don't need the human, and don't pull it away from what they are looking at unless the new thing is more urgent. While the human is working on the task the pane shows, show is refused unless what it shows is finished. It is also refused for 10 seconds after the human starts a TODO item, since they are likely to start more. It is always refused for 10 seconds after the human moves the pane themselves, since they just picked what to look at. Whenever the human asks you to show something (for example "show me that when you can") or you want to show something, call show right away and never hold back expecting a refusal: a refused show is the only thing that arranges byre's signal to try again, so a show you skip is never retried. When show is refused, say in chat what you would have shown, and carry on. "The start hold is over" means those 10 seconds passed, and "The move hold is over" means 10 seconds passed since the human's last move; byre sends either only when a show of yours was refused during that hold. Then show what you held back, if it still matters and the pane's other rules allow it, or make your final message NO_REPLY. - Questions from task agents: answer one yourself with answer_question only when the answer is clear, for example the agent's recommendation is plainly right given what the human has said, the human already decided it, or one of your notes settles it outright. Otherwise show it to the human with your recommendation and wait. When the human tells you an answer, give it with answer_question. Say which questions you answered and how, naming the note by its id when one decided it. - Notes: you learn how the human works and keep it with remember, forget and list_notes. byre adds every note to your instructions at the start of each lead session, so they outlast compaction; the human can read and delete them in the Notes tab. Write a note when the human shows a priority, a preference or the reasoning behind a choice that will recur: an answer that departs from the agent's recommendation (the answered signal shows both), a correction or overrule of you or a task agent, or a statement of how they want things done. Record the choice and the why, in their terms, general enough to apply to the next case and specific enough to act on, for example "Prefers replacing a state label over adding a tag beside it: keeps the picker uncluttered." File each note under the project it is about with project_id when it concerns that project's code, UI, docs or process, and with no project_id only when it holds for how the human works everywhere; apply a project's notes only to that project's tasks. Task agents start with their creator's every-project notes and their project's, and suggest new ones with suggest_note, which reaches you as a signal: save a suggestion only when it holds and your notes don't already cover it, choosing its project yourself, and otherwise drop it; neither needs a word to the human. Don't note one-off facts about a single task, and don't duplicate: update an existing note with remember and its id instead. When the human contradicts a note, update it or forget it. Before recommending anything, check your notes, and when one shaped the recommendation, cite its id. - Finished turns: read them and show the human only what matters: work finished and ready to merge, problems, decisions made. Stay quiet about routine progress. @@ -35,7 +34,7 @@ const leadGuidance = `You are the human's lead agent in byre, shown in the left - Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine. - Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. A workflow is a tree of steps run in order, each a stage, an order or a parallel; every stage runs as its own task on the stage's model once the step before it has finished, so stages fan out (a parallel after a stage) and fan in (a stage after a parallel); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and the workflow finishes once every stage has; byre then discards the run's other stage tasks, leaving the task of each last stage (one per branch when it ends in a parallel) to merge. Each last stage's agent is asked whether its task is ready to merge as soon as its own stage has finished and been reviewed, without waiting for the other last stages. Whenever you show or propose a workflow, draw it as a fenced diagram block (its format is the diagrams knowledge-base article) of its stage names only, with no models: one row per step, a parallel's stages side by side in one row, and an arrow into each stage from the stages before it. For example, rows holding Build; Review and Test; Fix, with the arrows "build -> review", "build -> test", "review -> fix" and "test -> fix". - Schedules: when the human asks for work to run on a timer (every Monday, nightly), you manage it with list_schedules, save_schedule and delete_schedule; the human sees and edits the same schedules in the Project tab. On each run, byre creates a task from a schedule's prompt or workflow as create_task would, created by byre, and you get its created signal; a run that fails to start reaches you as a signal. -- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/tskDa7e4K7305e). byre draws it in that task's color, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer or a finished turn, link to #/task/<task_id>/transcript. +- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/tskDa7e4K7305e). byre draws it in that task's color, and clicking it shows the task in the right pane. To also open one of its other views over the human's left pane, append it: #/task/<task_id>/<view>, where view is changes, files, terminal, board or project. - Keep messages short; the human reads them in half the screen. Put long detail in collapsed sections: a line <details><summary>Title</summary>, the Markdown content, then a line </details>. - You are long-running. Your context window is large. byre sends you a signal once your context passes a quarter of its window; then call compact_self at a logical point (a piece of work wrapped up, a topic closed), and not before; when the turn ends, byre replaces you with a fresh session that starts from your handoff. - Don't commit, push, or touch git branches yourself.` @@ -257,13 +256,13 @@ func (m *Manager) leadStateText(ctx context.Context, owner string) (string, erro pane := "Your right pane shows nothing." switch { case f.TaskId != nil: - pane = fmt.Sprintf("Your right pane shows %s on its %s tab.", m.taskLabel(ctx, *f.TaskId), f.Tab) + pane = fmt.Sprintf("Your right pane shows %s.", m.taskLabel(ctx, *f.TaskId)) case f.ProjectId != nil: project, err := m.store.GetProject(ctx, *f.ProjectId) if err != nil { return "", err } - pane = fmt.Sprintf("Your right pane shows project %s (%s) on its %s tab.", project.Name, project.Id, f.Tab) + pane = fmt.Sprintf("Your right pane shows project %s (%s) with no task: its dashboard.", project.Name, project.Id) } m.mu.Lock() focused := m.paneFocus[owner].Focused diff --git a/internal/agents/leadtools.go b/internal/agents/leadtools.go index d65c35d..7adf5b4 100644 --- a/internal/agents/leadtools.go +++ b/internal/agents/leadtools.go @@ -513,20 +513,19 @@ func (m *Manager) leadTools(l *live) []mcp.Tool { }), tool("show", - "Point the human's right pane at a task, or at a project with no task, and select one of its tabs. The tabs' order is fixed. "+ + "Point the human's right pane at a task, which shows its transcript (a workflow stage task's run: its graph and each stage's state, attempts and outputs), or at a project with no task, which shows the project's dashboard: its active tasks and its TODO.md items. "+ + "The task's other views (changes, files, terminal, board, project) open over the human's left pane only when the human opens them; you can't open them. "+ "Whenever the human asks you to show something (\"show me that when you can\") or you want to show something, call show right away; never hold back because you expect a refusal, since only a refused show arranges byre's signal to try again. "+ - "Tabs: transcript, board (a team task's board: its agents' posts, the human's and yours), changes, files, terminal (the task's shells in its worktree, and buttons that start one running a saved command), workflow (a workflow stage task's run: its graph and each stage's state, attempts and outputs), project (the project's dashboard: its active tasks and its TODO.md items); with only project_id, only project shows anything. "+ "Omit both task_id and project_id to clear the pane. "+ - "If the pane already shows that task (or project) on that tab, nothing changes and you are told so. "+ - "Otherwise refused for 10 seconds after the human last moved the pane themselves (switching task or tab), whatever else holds; if a show was refused for it, byre signals you \"The move hold is over\" when those 10 seconds pass without another move, and sends no signal otherwise. "+ - "Otherwise refused while the human is working in the right pane (focused in it, or with unsent text in it), unless it is on the project tab, or what it shows is finished (a merged or discarded task),"+ - "or it is on the transcript tab of a task whose agent is mid-turn, the human has no unsent text there, and either the human sent the message that started that turn since the pane last moved or the pane moved there 10 seconds ago or more; byre signals you \"Your right pane is free\" when either happens. "+ - "Also refused while the pane is on the project tab and the human started a TODO item there in the last 10 seconds, unless they switched task or tab or messaged you since; if a show was refused for it, byre signals you \"The start hold is over\" when those 10 seconds pass with the pane still on the project tab, and sends no signal otherwise.", + "If the pane already shows that task (or project), nothing changes and you are told so. "+ + "Otherwise refused for 10 seconds after the human last moved the pane themselves (switching task), whatever else holds; if a show was refused for it, byre signals you \"The move hold is over\" when those 10 seconds pass without another move, and sends no signal otherwise. "+ + "Otherwise refused while the human is working on what the pane shows (focused in it or in a view of its task, or with unsent text there), unless it shows a project with no task, or what it shows is finished (a merged or discarded task), "+ + "or it shows a task whose agent is mid-turn, the human has no unsent text there, and either the human sent the message that started that turn since the pane last moved or the pane moved there 10 seconds ago or more; byre signals you \"Your right pane is free\" when either happens. "+ + "Also refused for 10 seconds after the human started a TODO item, unless they switched task or messaged you since; if a show was refused for it, byre signals you \"The start hold is over\" when those 10 seconds pass, and sends no signal otherwise.", `{"type": "object", "properties": { "task_id": {"type": "string"}, - "project_id": {"type": "string", "description": "Ignored when task_id is given."}, - "tab": {"type": "string", "enum": ["transcript", "board", "changes", "files", "terminal", "workflow", "project"]} - }, "required": ["tab"]}`, + "project_id": {"type": "string", "description": "Ignored when task_id is given."} + }}`, func(ctx context.Context, user api.User, raw json.RawMessage) (string, error) { f := api.Focus{} err := json.Unmarshal(raw, &f) @@ -545,7 +544,7 @@ func (m *Manager) leadTools(l *live) []mcp.Tool { humanMoved := m.humanMoved[user.Login] since := time.Since(humanMoved) moveHeld := since < moveHold - startHeld := !moveHeld && time.Since(m.startedTodo[user.Login]) < startHold && current.Tab == api.TabProject + startHeld := !moveHeld && time.Since(m.startedTodo[user.Login]) < startHold if moveHeld { m.moveRefused[user.Login] = true } @@ -557,9 +556,9 @@ func (m *Manager) leadTools(l *live) []mcp.Tool { return "", fmt.Errorf("%w: the human moved the right pane %s ago, so it was left as it is; tell them in chat instead. The hold ends %s after their last move; byre signals you \"The move hold is over\" when it ends, because this show was refused", ErrConflict, since.Round(time.Second), moveHold) } if startHeld { - return "", fmt.Errorf("%w: the human just started a TODO item from the project tab and is likely to start more; tell them in chat instead. The hold ends %s after that start, unless they move the pane or message you first; byre signals you \"The start hold is over\" when it ends, because this show was refused", ErrConflict, startHold) + return "", fmt.Errorf("%w: the human just started a TODO item and is likely to start more; tell them in chat instead. The hold ends %s after that start, unless they move the pane or message you first; byre signals you \"The start hold is over\" when it ends, because this show was refused", ErrConflict, startHold) } - if working && current.Tab != api.TabProject { + if working && current.TaskId != nil { finished, err := m.paneFinished(ctx, current) if err != nil { return "", err @@ -572,7 +571,7 @@ func (m *Manager) leadTools(l *live) []mcp.Tool { } } if finished == "" && !waiting { - return "", fmt.Errorf("%w: the human is working in the right pane, so it was left as it is; tell them in chat instead", ErrConflict) + return "", fmt.Errorf("%w: the human is working on the task the right pane shows, so it was left as it is; tell them in chat instead", ErrConflict) } } err = m.SetFocus(ctx, user.Login, f, leadActor(user.Login)) @@ -595,9 +594,6 @@ func (m *Manager) leadTools(l *live) []mcp.Tool { } func (m *Manager) SetFocus(ctx context.Context, login string, f api.Focus, actor string) error { - if !f.Tab.Valid() { - return fmt.Errorf("%w: unknown tab %q", ErrInvalid, f.Tab) - } if f.TaskId != nil && *f.TaskId == "" { f.TaskId = nil } @@ -672,9 +668,6 @@ func (m *Manager) endWatchHold(ctx context.Context, login string, at time.Time) } func sameView(a, b api.Focus) bool { - if a.Tab != b.Tab { - return false - } at, bt := deref(a.TaskId), deref(b.TaskId) if at != "" || bt != "" { return at == bt @@ -702,7 +695,7 @@ func (m *Manager) paneFinished(ctx context.Context, f api.Focus) (string, error) const watchHold = 10 * time.Second func (m *Manager) paneWaiting(ctx context.Context, login string, f api.Focus) (bool, error) { - if f.TaskId == nil || f.Tab != api.TabTranscript { + if f.TaskId == nil { return false, nil } m.mu.Lock() @@ -751,7 +744,7 @@ func (m *Manager) endMoveHold(ctx context.Context, login string, at time.Time) e const startHold = 10 * time.Second -const startHoldEnded = "The start hold is over: the human started a TODO item from the project tab 10 seconds ago and the pane is still there, and a show of yours was refused in that time, so show works again." +const startHoldEnded = "The start hold is over: the human started a TODO item 10 seconds ago, and a show of yours was refused in that time, so show works again." func (m *Manager) setStartedTodo(login string, started bool) { m.mu.Lock() @@ -785,13 +778,6 @@ func (m *Manager) endStartHold(ctx context.Context, login string, at time.Time) if !refused { return nil } - f, err := m.store.GetFocus(ctx, login) - if err != nil { - return err - } - if f.Tab != api.TabProject { - return nil - } return m.addLeadInput(ctx, store.LeadInput{Owner: login, Kind: store.LeadSignal, Actor: "byre", Text: startHoldEnded}) } diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 631a629..d499fdc 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -531,39 +531,6 @@ func (e TaskStatus) Valid() bool { } } -// Defines values for TaskTab. -const ( - TabBoard TaskTab = "board" - TabChanges TaskTab = "changes" - TabFiles TaskTab = "files" - TabProject TaskTab = "project" - TabTerminal TaskTab = "terminal" - TabTranscript TaskTab = "transcript" - TabWorkflow TaskTab = "workflow" -) - -// Valid indicates whether the value is a known member of the TaskTab enum. -func (e TaskTab) Valid() bool { - switch e { - case TabBoard: - return true - case TabChanges: - return true - case TabFiles: - return true - case TabProject: - return true - case TabTerminal: - return true - case TabTranscript: - return true - case TabWorkflow: - return true - default: - return false - } -} - // Defines values for Weekday. const ( Fri Weekday = "fri" @@ -1019,9 +986,8 @@ type FindingStateUpdate struct { type Focus struct { // ProjectId The project the right pane shows; the task's project when task_id is set ProjectId *string `json:"project_id,omitempty"` - Tab TaskTab `json:"tab"` - // TaskId The task the right pane shows; absent for none. + // TaskId The task the right pane shows, by its transcript (a workflow stage task's by its run); absent for none, when the pane shows the project's dashboard. TaskId *string `json:"task_id,omitempty"` } @@ -1172,10 +1138,10 @@ type NotificationPrefs struct { // PaneFocus defines model for PaneFocus. type PaneFocus struct { - // Drafting True while a text box in the right pane holds unsent text. + // Drafting True while a text box of the shown task holds unsent text. Drafting bool `json:"drafting"` - // Focused True while the app's window has focus and the focused element is inside the right pane. + // Focused True while the app's window has focus and the focused element is the shown task's: in the right pane, or in a view of the task opened over the left pane. Focused bool `json:"focused"` } @@ -1617,9 +1583,6 @@ type TaskCreate struct { Workflow *string `json:"workflow,omitempty"` } -// TaskTab defines model for TaskTab. -type TaskTab string - // TaskTerminal defines model for TaskTerminal. type TaskTerminal struct { Id string `json:"id"` @@ -4745,346 +4708,346 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "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", + "5Qp9jEjchIkoYGjhgJn8ZpTUHz/+rwEA", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/store/lead.go b/internal/store/lead.go index 44e93e5..c0d9d6f 100644 --- a/internal/store/lead.go +++ b/internal/store/lead.go @@ -167,25 +167,26 @@ func (s *Store) TakeLeadWakes(ctx context.Context, taskID string) ([]string, err func (s *Store) GetFocus(ctx context.Context, login string) (api.Focus, error) { f := api.Focus{} - raw := "" - err := s.db.QueryRowContext(ctx, `SELECT focus FROM users WHERE login = ?`, login).Scan(&raw) + var task, project sql.NullString + err := s.db.QueryRowContext(ctx, `SELECT focus_task_id, focus_project_id FROM users WHERE login = ?`, login).Scan(&task, &project) if errors.Is(err, sql.ErrNoRows) { return f, ErrNotFound } if err != nil { return f, err } - err = json.Unmarshal([]byte(raw), &f) - return f, err + if task.Valid { + f.TaskId = &task.String + } + if project.Valid { + f.ProjectId = &project.String + } + return f, nil } func (s *Store) SetFocus(ctx context.Context, login string, f api.Focus, actor string) error { - raw, err := json.Marshal(f) - if err != nil { - return err - } return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { - _, err := tx.ExecContext(ctx, `UPDATE users SET focus = ? WHERE login = ?`, string(raw), login) + _, err := tx.ExecContext(ctx, `UPDATE users SET focus_task_id = ?, focus_project_id = ? WHERE login = ?`, f.TaskId, f.ProjectId, login) if err != nil { return err } diff --git a/internal/store/migrations/097-normalize-focus.sql b/internal/store/migrations/097-normalize-focus.sql new file mode 100644 index 0000000..66c5c53 --- /dev/null +++ b/internal/store/migrations/097-normalize-focus.sql @@ -0,0 +1,4 @@ +ALTER TABLE users ADD COLUMN focus_task_id TEXT; +ALTER TABLE users ADD COLUMN focus_project_id TEXT; +UPDATE users SET focus_task_id = json_extract(focus, '$.task_id'), focus_project_id = json_extract(focus, '$.project_id'); +ALTER TABLE users DROP COLUMN focus; diff --git a/mac/Sources/Byre/API.swift b/mac/Sources/Byre/API.swift index ff13d17..eaf040e 100644 --- a/mac/Sources/Byre/API.swift +++ b/mac/Sources/Byre/API.swift @@ -15,7 +15,6 @@ typealias InboxItem = Components.Schemas.InboxItem typealias ByreEvent = Components.Schemas.Event typealias EventPage = Components.Schemas.EventPage typealias Focus = Components.Schemas.Focus -typealias TaskTab = Components.Schemas.TaskTab typealias Snapshot = Components.Schemas.Snapshot typealias ServerStatus = Components.Schemas.Status diff --git a/mac/Sources/Byre/BoxDiagram.swift b/mac/Sources/Byre/BoxDiagram.swift index 36188f9..90c09b7 100644 --- a/mac/Sources/Byre/BoxDiagram.swift +++ b/mac/Sources/Byre/BoxDiagram.swift @@ -855,7 +855,8 @@ struct BoxCanvas: View { struct BoxDiagramView: View { let source: String - @Environment(Overlays.self) private var overlays + @Environment(Store.self) private var store + @Environment(\.inTask) private var inTask var body: some View { switch cachedBoxLayout(source) { @@ -865,7 +866,7 @@ struct BoxDiagramView: View { } .contentShape(Rectangle()) .onTapGesture { - overlays.diagram = layout + store.openSheet(.diagram(layout), fromTask: inTask) } .help("Open full size") .frame(maxWidth: .infinity, alignment: .leading) @@ -908,10 +909,6 @@ struct DiagramViewer: View { picked = id == picked ? nil : id } } - .padding(12) - .background(Palette.raised, in: RoundedRectangle(cornerRadius: 10)) - .overlay(RoundedRectangle(cornerRadius: 10).stroke(Palette.border)) - .shadow(color: Palette.overlayShadow, radius: 14, y: 8) } } diff --git a/mac/Sources/Byre/Chat.swift b/mac/Sources/Byre/Chat.swift index c7d9b11..d4f7e30 100644 --- a/mac/Sources/Byre/Chat.swift +++ b/mac/Sources/Byre/Chat.swift @@ -1715,7 +1715,7 @@ private struct SnapshotRow: View { if !inherited { Button("Changes") { store.pinSnapshot(snap.n) - store.focusTab(.changes) + store.sheet = Sheet(owner: .task(store.selectedTaskId), view: .tab(.changes)) } .buttonStyle(SmallLinkButtonStyle()) .help("Show snapshot \(snap.n) in the Changes tab") diff --git a/mac/Sources/Byre/Composer.swift b/mac/Sources/Byre/Composer.swift index e2ea79b..ba01d47 100644 --- a/mac/Sources/Byre/Composer.swift +++ b/mac/Sources/Byre/Composer.swift @@ -357,7 +357,7 @@ struct ComposerView: View { @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays @Environment(\.paneAccent) private var accent - @Environment(\.inRightPane) private var inRightPane + @Environment(\.inTask) private var inTask @ViewState private var text = "" @ViewState private var pick: String? @ViewState private var shownOverride: Bool? @@ -531,7 +531,7 @@ struct ComposerView: View { } } .onDisappear { - if inRightPane { + if inTask { store.paneFocus.setDraft(draftKey, false) } store.drafts?.flush() @@ -540,14 +540,14 @@ struct ComposerView: View { private var editor: some View { AgentEditor(text: $text, placeholder: chat.placeholder, key: draftKey, height: 104, - focus: $focused, border: inRightPane ? accent : focused ? Palette.accent : Palette.border, - borderWidth: inRightPane && focused ? 2 : 1) { + focus: $focused, border: inTask ? accent : focused ? Palette.accent : Palette.border, + borderWidth: inTask && focused ? 2 : 1) { submit(modelState()) } } private func reportDraft() { - if inRightPane { + if inTask { store.paneFocus.setDraft(draftKey, !trimmed(text).isEmpty) } } diff --git a/mac/Sources/Byre/Dialogs.swift b/mac/Sources/Byre/Dialogs.swift index 1a51c11..8935065 100644 --- a/mac/Sources/Byre/Dialogs.swift +++ b/mac/Sources/Byre/Dialogs.swift @@ -43,8 +43,6 @@ enum LeftTab { @Observable final class Overlays { var alerts: [OverlayAlert] = [] - var image: NSImage? - var diagram: BoxLayout? var menu: OverlayMenu? { didSet { menuHover = false @@ -64,9 +62,10 @@ final class Overlays { @ObservationIgnored private weak var behindModal: NSResponder? @ObservationIgnored private var holders = 0 @ObservationIgnored private var ctrl: @MainActor (String) -> Bool = { _ in false } + @ObservationIgnored private var escape: @MainActor () -> Bool = { false } var isOpen: Bool { - !alerts.isEmpty || image != nil || diagram != nil || menu != nil || modal != nil || switcher != nil + !alerts.isEmpty || menu != nil || modal != nil || switcher != nil } var dialogOpen: Bool { @@ -130,10 +129,6 @@ final class Overlays { alerts.removeLast() } else if modal != nil { answerModal("cancel") - } else if image != nil { - image = nil - } else if diagram != nil { - diagram = nil } else if let switcher { switcher.close() } else if menu != nil { @@ -141,9 +136,10 @@ final class Overlays { } } - func install(_ ctrl: @escaping @MainActor (String) -> Bool) { + func install(_ ctrl: @escaping @MainActor (String) -> Bool, escape: @escaping @MainActor () -> Bool) { holders += 1 self.ctrl = ctrl + self.escape = escape if monitor != nil { return } @@ -181,7 +177,11 @@ final class Overlays { closeTop() return true } - return !marked && !terminalFocused() + if marked || terminalFocused() { + return false + } + _ = escape() + return true } if !isOpen { return false @@ -204,7 +204,7 @@ final class Overlays { } return false } - if image == nil, diagram == nil, !marked, let switcher { + if !marked, let switcher { if code == 48 { switcher.inList.toggle() return true @@ -232,6 +232,7 @@ final class Overlays { return } ctrl = { _ in false } + escape = { false } if let monitor { NSEvent.removeMonitor(monitor) } @@ -275,30 +276,6 @@ struct OverlayHost: View { } SwitcherPlacement(model: switcher) } - if let image = overlays.image { - Palette.imageBackdrop - .onTapGesture { - overlays.image = nil - } - Image(nsImage: image) - .resizable() - .scaledToFit() - .frame(maxWidth: image.size.width, maxHeight: image.size.height) - .padding(24) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onTapGesture { - overlays.image = nil - } - } - if let diagram = overlays.diagram { - Palette.imageBackdrop - .onTapGesture { - overlays.diagram = nil - } - DiagramViewer(layout: diagram) - .id(diagram.id) - .padding(24) - } if let menu = overlays.menu { VStack(alignment: .leading, spacing: 0) { ForEach(menu.entries) { entry in diff --git a/mac/Sources/Byre/Files.swift b/mac/Sources/Byre/Files.swift index f39c2b2..3283da8 100644 --- a/mac/Sources/Byre/Files.swift +++ b/mac/Sources/Byre/Files.swift @@ -359,7 +359,6 @@ private struct FileImage: View { let path: String let load: Int @Environment(Store.self) private var store - @Environment(Overlays.self) private var overlays @ViewState private var image: NSImage? @ViewState private var error = "" @@ -373,7 +372,7 @@ private struct FileImage: View { .frame(maxWidth: image.size.width, maxHeight: image.size.height) .overlay(Rectangle().stroke(Palette.border)) .onTapGesture { - overlays.image = image + store.openSheet(.image(image), fromTask: true) } .help("Open full size") .padding(16) diff --git a/mac/Sources/Byre/Format.swift b/mac/Sources/Byre/Format.swift index 0a940a8..45ac8d5 100644 --- a/mac/Sources/Byre/Format.swift +++ b/mac/Sources/Byre/Format.swift @@ -173,7 +173,7 @@ private struct PaneAccentKey: EnvironmentKey { static let defaultValue = Palette.accent } -private struct InRightPaneKey: EnvironmentKey { +private struct InTaskKey: EnvironmentKey { static let defaultValue = false } @@ -187,12 +187,12 @@ extension EnvironmentValues { } } - var inRightPane: Bool { + var inTask: Bool { get { - self[InRightPaneKey.self] + self[InTaskKey.self] } set { - self[InRightPaneKey.self] = newValue + self[InTaskKey.self] = newValue } } } diff --git a/mac/Sources/Byre/Images.swift b/mac/Sources/Byre/Images.swift index bec088d..83bad08 100644 --- a/mac/Sources/Byre/Images.swift +++ b/mac/Sources/Byre/Images.swift @@ -92,7 +92,7 @@ struct ByreImage: View { var maxWidth: CGFloat = .infinity var maxHeight: CGFloat = 480 @Environment(Store.self) private var store - @Environment(Overlays.self) private var overlays + @Environment(\.inTask) private var inTask @ViewState private var image: NSImage? @ViewState private var failed = false @@ -106,7 +106,7 @@ struct ByreImage: View { .clipShape(RoundedRectangle(cornerRadius: 6)) .overlay(RoundedRectangle(cornerRadius: 6).stroke(Palette.border)) .onTapGesture { - overlays.image = image + store.openSheet(.image(image), fromTask: inTask) } .help("Open full size") } else if failed { diff --git a/mac/Sources/Byre/Palette.swift b/mac/Sources/Byre/Palette.swift index e0ebb51..d6c1315 100644 --- a/mac/Sources/Byre/Palette.swift +++ b/mac/Sources/Byre/Palette.swift @@ -29,7 +29,6 @@ enum Palette { static let task10 = dynamic(0x942e79, 0xf59cd8) static let overlayBackdrop = Color.black.opacity(0.35) - static let imageBackdrop = Color.black.opacity(0.6) static let overlayShadow = Color(nsColor: NSColor(name: nil) { appearance in appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua ? NSColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.5) : NSColor(srgbRed: 16 / 255, green: 24 / 255, blue: 40 / 255, alpha: 0.18) }) diff --git a/mac/Sources/Byre/PaneFocus.swift b/mac/Sources/Byre/PaneFocus.swift index 138712a..ce0c968 100644 --- a/mac/Sources/Byre/PaneFocus.swift +++ b/mac/Sources/Byre/PaneFocus.swift @@ -7,6 +7,8 @@ final class PaneFocus { private let api: API private weak var window: NSWindow? private var paneFrame: CGRect = .zero + private var taskSheetFrame: CGRect = .zero + private var leadSheetFrame: CGRect = .zero private var drafts: Set<String> = [] private var inside = false private var focused = false @@ -80,6 +82,18 @@ final class PaneFocus { paneFrame = frame } + func setSheetFrame(_ frame: CGRect, lead: Bool) { + if lead { + leadSheetFrame = frame + } else { + taskSheetFrame = frame + } + } + + private func inTask(_ p: CGPoint) -> Bool { + (paneFrame.contains(p) || taskSheetFrame.contains(p)) && !leadSheetFrame.contains(p) + } + func setDraft(_ id: String, _ nonEmpty: Bool) { if nonEmpty { drafts.insert(id) @@ -128,7 +142,7 @@ final class PaneFocus { return } if let p = contentPoint(view.visibleRect, in: view) { - inside = paneFrame.contains(p) + inside = inTask(p) } update() } @@ -138,7 +152,7 @@ final class PaneFocus { return } let p = content.convert(location, from: nil) - inside = paneFrame.contains(CGPoint(x: p.x, y: content.isFlipped ? p.y : content.bounds.height - p.y)) + inside = inTask(CGPoint(x: p.x, y: content.isFlipped ? p.y : content.bounds.height - p.y)) DispatchQueue.main.async { [weak self] in self?.responderChanged() } diff --git a/mac/Sources/Byre/Panes.swift b/mac/Sources/Byre/Panes.swift index 27458bf..1c3f045 100644 --- a/mac/Sources/Byre/Panes.swift +++ b/mac/Sources/Byre/Panes.swift @@ -58,6 +58,8 @@ struct MainView: View { let overlays = overlays overlays.install { key in ctrlGo(key, store, overlays) + } escape: { + store.closeSheet() } } .onDisappear { @@ -107,22 +109,22 @@ private struct LeftPane: View { VStack(spacing: 0) { HStack(spacing: 4) { PaneTabButton(label: "Lead", active: tab == .lead) { - overlays.leftTab = .lead + pick(.lead) } PaneTabButton(label: "Notes", active: tab == .notes) { - overlays.leftTab = .notes + pick(.notes) } PaneTabButton(label: "Workflows", active: tab == .workflows) { - overlays.leftTab = .workflows + pick(.workflows) } PaneTabButton(label: "Status", active: tab == .status) { - overlays.leftTab = .status + pick(.status) } PaneTabButton(label: "Server", active: tab == .server) { - overlays.leftTab = .server + pick(.server) } PaneTabButton(label: "Settings", active: tab == .settings) { - overlays.leftTab = .settings + pick(.settings) } Spacer(minLength: 0) HStack(spacing: 10) { @@ -162,6 +164,18 @@ private struct LeftPane: View { SettingsView() } } + .overlay { + if let sheet = store.sheet, sheet.owner != .lead { + SheetHost(sheet: sheet) + } + } + } + } + + private func pick(_ tab: LeftTab) { + overlays.leftTab = tab + if store.sheet?.owner != .lead { + store.sheet = nil } } } @@ -175,12 +189,19 @@ private struct TaskPane: View { let accent = selected.map { taskPalette[$0.color] } ?? Palette.accent VStack(spacing: 0) { TaskBar() - TabStrip() - tabBody - .frame(maxWidth: .infinity, maxHeight: .infinity) + VStack(spacing: 0) { + TabStrip() + TaskTabBody(tab: store.primaryTab) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .overlay { + if let sheet = store.sheet, sheet.owner == .lead { + SheetHost(sheet: sheet) + } + } } .environment(\.paneAccent, accent) - .environment(\.inRightPane, true) + .environment(\.inTask, true) .onGeometryChange(for: CGRect.self) { g in g.frame(in: .global) } action: { frame in @@ -192,10 +213,14 @@ private struct TaskPane: View { overlays.paneFrame = frame } } +} - @ViewBuilder - private var tabBody: some View { - switch store.shownTab { +struct TaskTabBody: View { + let tab: TaskTab + @Environment(Store.self) private var store + + var body: some View { + switch tab { case .transcript: ChatView(kind: .task(store.selectedTaskId)) .id("\(store.selectedTaskId ?? "")/\(store.selectedTaskId.flatMap { store.pickedMember($0) } ?? "")") @@ -342,9 +367,12 @@ private struct TabStrip: View { } private func info() -> Info { - let tab = store.shownTab - let run = store.selectedTaskId.flatMap { store.findTask($0) }?.workflow != nil - let team = store.selectedTaskId.map { !store.team($0).isEmpty } ?? false + 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) + } + 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 @@ -389,14 +417,14 @@ private struct TabStrip: View { HStack(spacing: 4) { ForEach(info.order.filter { kept.contains($0) }, id: \.self) { t in PaneTabButton(label: tabLabels[t]!, active: t == info.tab) { - store.focusTab(t) + store.openTab(t) } } if !hidden.isEmpty { MoreButton { overlays.menu = OverlayMenu(anchor: moreFrame, accent: accent, entries: hidden.map { t in MenuEntry(id: t.rawValue, label: tabLabels[t]!) { - store.focusTab(t) + store.openTab(t) } }) } diff --git a/mac/Sources/Byre/Project.swift b/mac/Sources/Byre/Project.swift index a2b56a8..0228582 100644 --- a/mac/Sources/Byre/Project.swift +++ b/mac/Sources/Byre/Project.swift @@ -40,10 +40,10 @@ struct ProjectView: View { return .handled } .onKeyPress(.return) { - guard let id = store.selectedTaskId, store.focusedProject() != nil else { + guard store.selectedTaskId != nil, store.focusedProject() != nil else { return .ignored } - store.focusTask(id) + store.sheet = nil return .handled } .onAppear { @@ -94,7 +94,8 @@ struct ProjectView: View { } else { next = by > 0 ? 0 : rows.count - 1 } - store.focusTask(rows[next], .project) + store.sheet = Sheet(owner: .task(rows[next]), view: .tab(.project)) + store.focusTask(rows[next]) } private func refresh() { diff --git a/mac/Sources/Byre/Sheet.swift b/mac/Sources/Byre/Sheet.swift new file mode 100644 index 0000000..295fa2d --- /dev/null +++ b/mac/Sources/Byre/Sheet.swift @@ -0,0 +1,167 @@ +import AppKit +import SwiftUI + +enum SheetView { + case tab(TaskTab) + case image(NSImage) + case diagram(BoxLayout) +} + +struct Sheet { + enum Owner: Equatable { + case lead + case task(String?) + } + + let owner: Owner + let view: SheetView + + var tab: TaskTab? { + if case .tab(let t) = view { + return t + } + return nil + } +} + +private let stripWidth: CGFloat = 56 +private let corner: CGFloat = 8 + +struct SheetHost: View { + let sheet: Sheet + @Environment(Store.self) private var store + + var body: some View { + let overLeft = sheet.owner != .lead + HStack(spacing: 0) { + if overLeft { + strip + } + SheetPanel(sheet: sheet, overLeft: overLeft) + .onGeometryChange(for: CGRect.self) { g in + g.frame(in: .global) + } action: { frame in + store.paneFocus.setSheetFrame(frame, lead: !overLeft) + } + .onDisappear { + store.paneFocus.setSheetFrame(.zero, lead: !overLeft) + } + if !overLeft { + strip + } + } + } + + private var strip: some View { + Palette.bg.opacity(0.55) + .frame(width: stripWidth) + .contentShape(Rectangle()) + .onTapGesture { + store.sheet = nil + } + .help("Close") + } +} + +private struct SheetPanel: View { + let sheet: Sheet + let overLeft: Bool + @Environment(Store.self) private var store + + var body: some View { + let task = store.selectedTaskId.flatMap { store.findTask($0) } + let accent = overLeft ? task.map { taskPalette[$0.color] } ?? Palette.accent : Palette.accent + let clip = UnevenRoundedRectangle(topLeadingRadius: overLeft ? corner : 0, bottomLeadingRadius: overLeft ? corner : 0, + bottomTrailingRadius: overLeft ? 0 : corner, topTrailingRadius: overLeft ? 0 : corner) + VStack(spacing: 0) { + HStack(spacing: 8) { + Text(title(task)) + .font(Fonts.font(13, weight: 700)) + .foregroundStyle(accent) + .lineLimit(1) + .truncationMode(.tail) + Spacer(minLength: 0) + Button { + store.sheet = nil + } label: { + Text("✕") + .font(Fonts.font(13, weight: 700)) + .foregroundStyle(accent) + .padding(.horizontal, 6) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .help("Close (Esc)") + } + .padding(.horizontal, 12) + .padding(.vertical, 7) + .background(accent.opacity(0.12)) + .overlay(alignment: .bottom) { + Rectangle().fill(accent).frame(height: 1) + } + content + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .background(Palette.bg) + .clipShape(clip) + .overlay(SheetEdge(openTrailing: overLeft).stroke(accent, lineWidth: 1.5)) + .shadow(color: Palette.overlayShadow, radius: 14, x: overLeft ? -6 : 6) + .environment(\.paneAccent, accent) + .environment(\.inTask, overLeft) + } + + private func title(_ task: ByreTask?) -> String { + let label = switch sheet.view { + case .tab(let t): tabLabels[t]! + case .image: "Image" + case .diagram: "Diagram" + } + if !overLeft { + return "Lead · \(label)" + } + let project = store.focusedProject()?.name ?? "" + guard let task else { + return "\(project) · \(label)" + } + return "\(project) ▸ \(task.name) · \(label)" + } + + @ViewBuilder + private var content: some View { + switch sheet.view { + case .tab(let t): + TaskTabBody(tab: t) + case .image(let image): + ScrollView([.horizontal, .vertical]) { + Image(nsImage: image) + .resizable() + .scaledToFit() + .frame(maxWidth: image.size.width, maxHeight: image.size.height) + .padding(16) + } + .defaultScrollAnchor(.center) + case .diagram(let layout): + DiagramViewer(layout: layout) + .id(layout.id) + .padding(12) + } + } +} + +private struct SheetEdge: Shape { + let openTrailing: Bool + + func path(in r: CGRect) -> Path { + let closed = openTrailing ? r.minX : r.maxX + let open = openTrailing ? r.maxX : r.minX + let inward: CGFloat = openTrailing ? corner : -corner + var p = Path() + p.move(to: CGPoint(x: open, y: r.minY)) + p.addLine(to: CGPoint(x: closed + inward, y: r.minY)) + p.addArc(tangent1End: CGPoint(x: closed, y: r.minY), tangent2End: CGPoint(x: closed, y: r.minY + corner), radius: corner) + p.addLine(to: CGPoint(x: closed, y: r.maxY - corner)) + p.addArc(tangent1End: CGPoint(x: closed, y: r.maxY), tangent2End: CGPoint(x: closed + inward, y: r.maxY), radius: corner) + p.addLine(to: CGPoint(x: open, y: r.maxY)) + return p + } +} diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 41e8248..8eb3647 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -2,6 +2,10 @@ import Foundation import Observation import SwiftData +enum TaskTab: String { + case transcript, board, changes, files, terminal, workflow, project +} + let taskTabs: [TaskTab] = [.transcript, .board, .changes, .files, .terminal, .workflow, .project] enum JumpTarget: Equatable { @@ -65,7 +69,8 @@ final class Store { private(set) var selectedTaskId: String? var pinned: Int? var connected = false - private(set) var focus = Focus(tab: .transcript) + private(set) var focus = Focus() + var sheet: Sheet? var leadSessions: [AgentSession] = [] let leadFeed: Feed var jump: Jump? @@ -290,12 +295,12 @@ final class Store { func jumpTo(_ taskId: String, _ target: JumpTarget) { jumps += 1 jump = Jump(taskId: taskId, target: target, n: jumps) - focusTask(taskId, .transcript) + focusTask(taskId) } func showQuestion(_ taskId: String) { guard let item = inbox.filter({ $0.taskId == taskId }).min(by: { $0.createdAt < $1.createdAt }) else { - focusTask(taskId, .transcript) + focusTask(taskId) return } if let member = sessions[taskId]?.first(where: { $0.id == item.agentSessionId })?.member, !member.isEmpty { @@ -458,18 +463,37 @@ final class Store { projects.first { $0.id == focus.projectId } } - var shownTab: TaskTab { - let run = selectedTaskId.flatMap { findTask($0) }?.workflow != nil - if run { - return focus.tab == .project ? .project : .workflow + var primaryTab: TaskTab { + guard let id = selectedTaskId else { + return .project } - return focus.tab == .workflow ? .transcript : focus.tab + return findTask(id)?.workflow != nil ? .workflow : .transcript } - func setFocus(taskId: String?? = .none, projectId: String?? = .none, tab: TaskTab? = nil) async throws { + func openTab(_ tab: TaskTab) { + if tab == primaryTab || sheet?.tab == tab { + sheet = nil + return + } + sheet = Sheet(owner: .task(selectedTaskId), view: .tab(tab)) + } + + func openSheet(_ view: SheetView, fromTask: Bool) { + sheet = Sheet(owner: fromTask ? .task(selectedTaskId) : .lead, view: view) + } + + func closeSheet() -> Bool { + if sheet == nil { + return false + } + sheet = nil + return true + } + + func setFocus(taskId: String?? = .none, projectId: String?? = .none) async throws { let task = taskId ?? focus.taskId let project = task != nil ? findTask(task!)?.projectId : (projectId ?? focus.projectId) - var next = Focus(tab: tab ?? focus.tab) + var next = Focus() if let task, !task.isEmpty { next.taskId = task } @@ -485,21 +509,15 @@ final class Store { applyFocus(f) } - func focusTask(_ taskId: String, _ tab: TaskTab = .transcript) { + func focusTask(_ taskId: String) { spawn("set focus") { - try await self.setFocus(taskId: taskId, tab: tab) + try await self.setFocus(taskId: taskId) } } func focusProject(_ projectId: String) { spawn("set focus") { - try await self.setFocus(taskId: .some(nil), projectId: projectId, tab: .project) - } - } - - func focusTab(_ tab: TaskTab) { - spawn("set focus") { - try await self.setFocus(tab: tab) + try await self.setFocus(taskId: .some(nil), projectId: projectId) } } @@ -510,9 +528,12 @@ final class Store { let name = m?[3] ?? nil let tab = taskTabs.first { $0.rawValue == name } if kind == "task", let id { - if id != focus.taskId || (tab != nil && tab != focus.tab) { + if let tab, tab != .transcript, tab != .workflow { + sheet = Sheet(owner: .task(id), view: .tab(tab)) + } + if id != focus.taskId { spawn("set focus") { - try await self.setFocus(taskId: id, tab: tab) + try await self.setFocus(taskId: id) } } return @@ -633,6 +654,9 @@ 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 + } markVisited(selectedTaskId) markVisited(taskId) selectedTaskId = taskId diff --git a/mac/Sources/Byre/Switcher.swift b/mac/Sources/Byre/Switcher.swift index 662d85c..14aa77b 100644 --- a/mac/Sources/Byre/Switcher.swift +++ b/mac/Sources/Byre/Switcher.swift @@ -8,8 +8,6 @@ let goKeys: Set<String> = ["k", "n", "p", "l", "s", "/"] @MainActor func runGo(_ key: String, _ store: Store, _ overlays: Overlays) { - overlays.image = nil - overlays.diagram = nil switch key { case "k": overlays.openSwitcher(store) @@ -21,9 +19,12 @@ func runGo(_ key: String, _ store: Store, _ overlays: Overlays) { case "p": overlays.window?.makeFirstResponder(nil) overlays.projectFocus += 1 - store.focusTab(.project) + if let id = store.selectedTaskId { + store.sheet = Sheet(owner: .task(id), view: .tab(.project)) + } case "l": overlays.switcher?.close() + store.sheet = nil overlays.leftTab = .lead overlays.leadFocus += 1 case "s": @@ -50,7 +51,7 @@ func createTask(_ projectId: String, _ body: Components.Schemas.TaskCreate, _ st do { let task = try await store.api.client.createTask(path: .init(projectId: projectId), body: .json(body)).created.body.json store.composerFocus = task.id - store.focusTask(task.id, .transcript) + store.focusTask(task.id) } catch { overlays.showAlert(errorText(error)) } @@ -71,11 +72,11 @@ private let shortcuts: [(String, [(String, String)])] = [ ("⌘K", "Switch task, or create one"), ("⌘N", "New task in the current project"), ("⌘P", "Open the Project tab"), - ("⌘L", "Open the Lead tab and its composer"), + ("⌘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"), ("⌘/", "Show these shortcuts"), - ("Esc", "Close the switcher or a dialog"), + ("Esc", "Close the switcher, a dialog, or a view opened over a pane"), ]), ("Task switcher", [ ("Tab", "Switch between the text and the list"), @@ -298,7 +299,7 @@ final class SwitcherModel { store.showQuestion(taskId) } else { store.composerFocus = taskId - store.focusTask(taskId, .transcript) + store.focusTask(taskId) } } @@ -316,7 +317,7 @@ final class SwitcherModel { let ids = try await uploadImages(store.api, imgs) let task = try await store.api.client.createTask(path: .init(projectId: projectId), body: .json(.init(prompt: message.isEmpty ? nil : message, images: ids, model: store.defaultModel))).created.body.json store.composerFocus = task.id - store.focusTask(task.id, .transcript) + store.focusTask(task.id) } catch { overlays.openSwitcher(store) if let s = overlays.switcher {