Show "merge held" on queued tasks in both task lists
This commit is contained in:
+2
-2
@@ -122,7 +122,7 @@ Agent activity becomes events with `agent_session_id` set: `user.message`, `agen
|
|||||||
|
|
||||||
Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and Merge prompts, Compact's handoff request and the continuation's handoff) show collapsed like a tool call, labelled `byre` with the first line as the brief; expanding it shows the full text. The transcript view has an accent border while the session is `running`, and a braille spinner on its last line, cycling ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ every 0.8s. It is one element with one CSS animation: the ten frames are drawn side by side in an inline SVG used as the `.spinner` mask over an accent background, and the `braille` keyframes in `web/style.css` step the mask position through them. Orion freezes animated GIFs and per-dot `step-end` opacity animations partway through a turn, so it stays a single element with a single animation. Consecutive tool calls (with any thinking between them) collapse into one group whose summary counts calls per tool and file, and file paths show relative to the worktree. A Claude Code subagent call (the `Agent` or `Task` tool) stands on its own, collapsed, labelled with its `subagent_type` and `description`; expanding it shows the prompt and the call's result. Its summary carries a status from the `agent.vendor` `system` events whose `tool_use_id` names it (`task_started`: running, or running in the background; `task_progress`: its `description`; `task_notification`: its `status`, red unless `completed`). The subagent's own messages, thinking and tool calls (events whose `parent_tool_use_id` names the call) stream inline with the main agent's, in arrival order, each marked `↳ <subagent_type>` with the description as its tooltip; its messages also get an accent rule down their left edge. A run of tool calls groups only calls from the same agent, so a call from another agent starts a new group. A subagent's messages never end the main agent's live message, count as a turn's final message or hide a quiet turn. Claude Code streams a background subagent's activity, but for a foreground one only its prompt, so a foreground subagent shows only as its call, its status and its result. A turn's final message that is at least half visible in the transcript while the page is visible dismisses its `report` inbox item, as the viewing user. Selecting a task that has never had a session focuses the composer. In the composer, Enter sends and Shift-Enter adds a newline; unsent text is kept per task in memory, so it survives switching tabs or tasks but not a page reload. The composer has no `@`-file mentions or path picker: text reaches the agent exactly as typed (stream-json doesn't expand `@path` the way the interactive CLI does), and the agent reads any file the message names with its own tools. The composer is the same whether or not the task has a live session: Send posts to the session's messages, or starts a session with the text when there is none or the last one completed. A sent message shows at once as a "sending…" bubble, replaced by the server's `user.message` event with the same text when it arrives, and removed (text and images back in the composer) if the request fails.
|
Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and Merge prompts, Compact's handoff request and the continuation's handoff) show collapsed like a tool call, labelled `byre` with the first line as the brief; expanding it shows the full text. The transcript view has an accent border while the session is `running`, and a braille spinner on its last line, cycling ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ every 0.8s. It is one element with one CSS animation: the ten frames are drawn side by side in an inline SVG used as the `.spinner` mask over an accent background, and the `braille` keyframes in `web/style.css` step the mask position through them. Orion freezes animated GIFs and per-dot `step-end` opacity animations partway through a turn, so it stays a single element with a single animation. Consecutive tool calls (with any thinking between them) collapse into one group whose summary counts calls per tool and file, and file paths show relative to the worktree. A Claude Code subagent call (the `Agent` or `Task` tool) stands on its own, collapsed, labelled with its `subagent_type` and `description`; expanding it shows the prompt and the call's result. Its summary carries a status from the `agent.vendor` `system` events whose `tool_use_id` names it (`task_started`: running, or running in the background; `task_progress`: its `description`; `task_notification`: its `status`, red unless `completed`). The subagent's own messages, thinking and tool calls (events whose `parent_tool_use_id` names the call) stream inline with the main agent's, in arrival order, each marked `↳ <subagent_type>` with the description as its tooltip; its messages also get an accent rule down their left edge. A run of tool calls groups only calls from the same agent, so a call from another agent starts a new group. A subagent's messages never end the main agent's live message, count as a turn's final message or hide a quiet turn. Claude Code streams a background subagent's activity, but for a foreground one only its prompt, so a foreground subagent shows only as its call, its status and its result. A turn's final message that is at least half visible in the transcript while the page is visible dismisses its `report` inbox item, as the viewing user. Selecting a task that has never had a session focuses the composer. In the composer, Enter sends and Shift-Enter adds a newline; unsent text is kept per task in memory, so it survives switching tabs or tasks but not a page reload. The composer has no `@`-file mentions or path picker: text reaches the agent exactly as typed (stream-json doesn't expand `@path` the way the interactive CLI does), and the agent reads any file the message names with its own tools. The composer is the same whether or not the task has a live session: Send posts to the session's messages, or starts a session with the text when there is none or the last one completed. A sent message shows at once as a "sending…" bubble, replaced by the server's `user.message` event with the same text when it arrives, and removed (text and images back in the composer) if the request fails.
|
||||||
|
|
||||||
The task switcher (⌘K, or the project ▸ task picker at the right end of the right pane's tab strip) lists active tasks with their session state and open inbox item count, live while it is open. Tasks with inbox items come first; within each group, the task least recently visited in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, 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. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Picking a task points the right pane at it on the Transcript tab (see "Lead agent").
|
The task switcher (⌘K, or the project ▸ task picker at the right end of the right pane's tab strip) lists active tasks with their session state and open inbox item count, and **merge held** on a task with `merge_queued_at`, live while it is open. Tasks with inbox items come first; within each group, the task least recently visited in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, 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. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Picking a task points the right pane at it on the Transcript tab (see "Lead agent").
|
||||||
|
|
||||||
## Lead agent
|
## Lead agent
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ A project's `TODO.md` is read from the root of its default branch in the project
|
|||||||
|
|
||||||
The title is the item's identity. `POST /api/projects/{id}/todo/tasks` with a `title` re-reads `TODO.md`, refuses an unknown title (404) or one an active task already has (409), creates a task from the default branch with that title and `todo_item` (the `tasks.todo_item` column, empty for other tasks), and starts its session as the requesting user with `todoPrompt` (`internal/agents/tasks.go`): the item as written, and an instruction to remove it from `TODO.md` in the worktree when the work is done. The agent's guidance names the item too (`taskGuidance`, see "Agent sessions"), so the instruction survives compaction and resumes. The agent removes the item on its own branch, so the removal lands on the default branch in the same squash commit as the work.
|
The title is the item's identity. `POST /api/projects/{id}/todo/tasks` with a `title` re-reads `TODO.md`, refuses an unknown title (404) or one an active task already has (409), creates a task from the default branch with that title and `todo_item` (the `tasks.todo_item` column, empty for other tasks), and starts its session as the requesting user with `todoPrompt` (`internal/agents/tasks.go`): the item as written, and an instruction to remove it from `TODO.md` in the worktree when the work is done. The agent's guidance names the item too (`taskGuidance`, see "Agent sessions"), so the instruction survives compaction and resumes. The agent removes the item on its own branch, so the removal lands on the default branch in the same squash commit as the work.
|
||||||
|
|
||||||
The **Project** tab is the dashboard of the right pane's project (the focused task's, or the project focused with no task). Under **Tasks** it lists the project's active tasks in the server's order (newest first), each with its session state and open inbox item count, as links that point the right pane at the task's transcript; the focused task is highlighted. Under **TODO** it lists the `TODO.md` items, grouped by section, with each body rendered as Markdown. An item with an active task whose `todo_item` equals its title shows that task's session state as a link to the task instead of a **Start** button. **Start** posts the item with the composer's default model and points the right pane at the new task's transcript. The task list follows byre's live state; the tab fetches the project's `TODO.md` when it opens, when the right pane moves to another project, when the project list changes, when the SSE stream reconnects, and on `task.merged`; a change pushed to the default branch outside byre shows the next time one of those happens.
|
The **Project** tab is the dashboard of the right pane's project (the focused task's, or the project focused with no task). Under **Tasks** it lists the project's active tasks in the server's order (newest first), each with its session state, open inbox item count and **merge held** when it has `merge_queued_at`, as links that point the right pane at the task's transcript; the focused task is highlighted. Under **TODO** it lists the `TODO.md` items, grouped by section, with each body rendered as Markdown. An item with an active task whose `todo_item` equals its title shows that task's session state as a link to the task instead of a **Start** button. **Start** posts the item with the composer's default model and points the right pane at the new task's transcript. The task list follows byre's live state; the tab fetches the project's `TODO.md` when it opens, when the right pane moves to another project, when the project list changes, when the SSE stream reconnects, and on `task.merged`; a change pushed to the default branch outside byre shows the next time one of those happens.
|
||||||
|
|
||||||
## Inbox
|
## Inbox
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -92,7 +92,7 @@ export function openSwitcher() {
|
|||||||
.sort((a, b) => Number(b.count > 0) - Number(a.count > 0) || a.visited - b.visited);
|
.sort((a, b) => Number(b.count > 0) - Number(a.count > 0) || a.visited - b.visited);
|
||||||
rows = matches.map(({ project, task, count }) => {
|
rows = matches.map(({ project, task, count }) => {
|
||||||
const sess = currentSession(task.id);
|
const sess = currentSession(task.id);
|
||||||
const el = h("li", {}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, project.name), " ", task.title), count > 0 && h("span", { class: "badge", title: "inbox items" }, count), h("span", { class: "task-state" }, sess?.state ?? "no agent"));
|
const el = h("li", {}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, project.name), " ", task.title), count > 0 && h("span", { class: "badge", title: "inbox items" }, count), task.merge_queued_at !== undefined && h("span", { class: "task-state warn", title: "Waiting for the merge in progress in this project to finish" }, "merge held"), h("span", { class: "task-state" }, sess?.state ?? "no agent"));
|
||||||
return { el, pick: () => go(task.id) };
|
return { el, pick: () => go(task.id) };
|
||||||
});
|
});
|
||||||
for (const p of state.projects.filter((p) => `${p.name} project`.toLowerCase().includes(q))) {
|
for (const p of state.projects.filter((p) => `${p.name} project`.toLowerCase().includes(q))) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -99,7 +99,7 @@ function tasksBlock(active) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
focusTask(task.id);
|
focusTask(task.id);
|
||||||
},
|
},
|
||||||
}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), h("span", { class: "task-title" }, task.title), count > 0 && h("span", { class: "badge", title: "inbox items" }, count), h("span", { class: "task-state" }, sess?.state ?? "no agent")));
|
}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), h("span", { class: "task-title" }, task.title), count > 0 && h("span", { class: "badge", title: "inbox items" }, count), task.merge_queued_at !== undefined && h("span", { class: "task-state warn", title: "Waiting for the merge in progress in this project to finish" }, "merge held"), h("span", { class: "task-state" }, sess?.state ?? "no agent")));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
function todoBlock(project, todo, active, starting, start) {
|
function todoBlock(project, todo, active, starting, start) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -107,6 +107,7 @@ export function openSwitcher(): void {
|
|||||||
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
||||||
h("span", { class: "task-title" }, h("span", { class: "project-name" }, project.name), " ", task.title),
|
h("span", { class: "task-title" }, h("span", { class: "project-name" }, project.name), " ", task.title),
|
||||||
count > 0 && h("span", { class: "badge", title: "inbox items" }, count),
|
count > 0 && h("span", { class: "badge", title: "inbox items" }, count),
|
||||||
|
task.merge_queued_at !== undefined && h("span", { class: "task-state warn", title: "Waiting for the merge in progress in this project to finish" }, "merge held"),
|
||||||
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
||||||
);
|
);
|
||||||
return { el, pick: () => go(task.id) };
|
return { el, pick: () => go(task.id) };
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ function tasksBlock(active: Task[]): HTMLElement {
|
|||||||
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
||||||
h("span", { class: "task-title" }, task.title),
|
h("span", { class: "task-title" }, task.title),
|
||||||
count > 0 && h("span", { class: "badge", title: "inbox items" }, count),
|
count > 0 && h("span", { class: "badge", title: "inbox items" }, count),
|
||||||
|
task.merge_queued_at !== undefined && h("span", { class: "task-state warn", title: "Waiting for the merge in progress in this project to finish" }, "merge held"),
|
||||||
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
h("span", { class: "task-state" }, sess?.state ?? "no agent"),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-1
@@ -1718,7 +1718,8 @@ button.danger-primary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rl-status.warn,
|
.rl-status.warn,
|
||||||
.rl-windows .warn {
|
.rl-windows .warn,
|
||||||
|
.task-state.warn {
|
||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user