Give each task an assigned colour across the UI; lead links tasks
This commit is contained in:
@@ -814,6 +814,8 @@ export interface components {
|
||||
base_ref: string;
|
||||
/** @description The first of the task's block of ports (port_base to port_base+9), passed to its agent and terminal as PORT. */
|
||||
port_base: number;
|
||||
/** @description Index into the UI's task palette, assigned at creation: a colour no active task holds, the one given out longest ago. */
|
||||
color: number;
|
||||
/** @enum {string} */
|
||||
status: "active" | "merged" | "discarded";
|
||||
created_by: string;
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import { ago, errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { image, inlineMarkdown, markdown } from "./markdown.js";
|
||||
import { diagram } from "./mermaid.js";
|
||||
import { onEach, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
const checked = new Map<string, string[]>();
|
||||
const texts = new Map<string, string>();
|
||||
@@ -307,7 +308,7 @@ function attachments(list: Attachment[], taskId: string | undefined): HTMLElemen
|
||||
case "diagram":
|
||||
return h("li", {}, diagram(a.mermaid ?? ""), caption);
|
||||
case "task":
|
||||
return h("li", {}, h("a", { href: `#/task/${a.task_id}` }, "task ", a.task_id ?? ""), caption);
|
||||
return h("li", {}, h("a", { href: `#/task/${a.task_id}`, class: "task-link", style: taskStyle(a.task_id ?? "") }, "task ", a.task_id ?? ""), caption);
|
||||
case "snapshot":
|
||||
return h("li", {}, "snapshot ", h("code", {}, a.snapshot_id ?? ""), caption);
|
||||
case "url":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { h } from "./dom.js";
|
||||
import { diagram } from "./mermaid.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
type Block = HTMLElement;
|
||||
|
||||
@@ -310,6 +311,10 @@ function link(href: string, children: Node[]): Node {
|
||||
if (/^https?:\/\//i.test(href)) {
|
||||
return h("a", { href, target: "_blank", rel: "noopener noreferrer" }, ...children);
|
||||
}
|
||||
const task = /^#\/task\/(\w+)$/.exec(href);
|
||||
if (task) {
|
||||
return h("a", { href, class: "task-link", style: taskStyle(task[1]!) }, ...children);
|
||||
}
|
||||
if (href.startsWith("#/")) {
|
||||
return h("a", { href }, ...children);
|
||||
}
|
||||
|
||||
+3
-2
@@ -4,6 +4,7 @@ import { openSettings } from "./settings.js";
|
||||
import { activeTasks, currentSession, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { openStatus } from "./status.js";
|
||||
import { confirmMerge } from "./task.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
export function mountNavbar(root: HTMLElement): void {
|
||||
const status = h("button", { class: "nav-link" }, "Status");
|
||||
@@ -103,9 +104,9 @@ export function openSwitcher(): void {
|
||||
const sess = currentSession(task.id);
|
||||
const el = h(
|
||||
"li",
|
||||
{},
|
||||
{ style: taskStyle(task.id) },
|
||||
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), " ", h("span", { class: "task-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"),
|
||||
|
||||
+7
-3
@@ -8,8 +8,7 @@ import { openSwitcher } from "./navbar.js";
|
||||
import { findTask, focusedProject, focusTab, on, onEach, state, tabs } from "./state.js";
|
||||
import { mountPreview } from "./preview.js";
|
||||
import { mountProject } from "./project.js";
|
||||
import { mountTranscript } from "./task.js";
|
||||
import { mountTerminal } from "./terminal.js";
|
||||
import { mountTranscript } from "./task.js";import { mountTerminal } from "./terminal.js";
|
||||
|
||||
const views: Record<TaskTab, { label: string; mount: (root: HTMLElement) => () => void }> = {
|
||||
transcript: { label: "Transcript", mount: mountTranscript },
|
||||
@@ -32,12 +31,18 @@ export function taskPane(): HTMLElement {
|
||||
const body = h("div", { class: "pane-body" });
|
||||
const picker = h("button", { class: "task-picker", title: "Switch task (⌘K)" });
|
||||
picker.onclick = openSwitcher;
|
||||
const pane = h("section", { class: "pane task-pane", tabIndex: -1 }, strip, body);
|
||||
let shown: TaskTab | undefined;
|
||||
let dispose: () => void = () => {};
|
||||
|
||||
const renderPicker = () => {
|
||||
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
const project = focusedProject();
|
||||
if (task) {
|
||||
pane.style.setProperty("--task", `var(--task-${task.color})`);
|
||||
} else {
|
||||
pane.style.removeProperty("--task");
|
||||
}
|
||||
replace(picker, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
|
||||
};
|
||||
|
||||
@@ -76,7 +81,6 @@ export function taskPane(): HTMLElement {
|
||||
onEach(["projects", "tasks", "selection", "focus"], renderPicker);
|
||||
render();
|
||||
renderPicker();
|
||||
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
|
||||
trackPaneFocus(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { models } from "./task.js";
|
||||
import { currentSession, focusedProject, focusTask, on, onEach, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
type Todo = Schemas["Todo"];
|
||||
type TodoItem = Schemas["TodoItem"];
|
||||
@@ -118,7 +119,7 @@ function tasksBlock(active: Task[]): HTMLElement {
|
||||
const count = state.inbox.filter((item) => item.task_id === task.id).length;
|
||||
return h(
|
||||
"li",
|
||||
{ class: task.id === state.selectedTaskId ? "selected" : "" },
|
||||
{ class: task.id === state.selectedTaskId ? "selected" : "", style: taskStyle(task.id) },
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
@@ -129,7 +130,7 @@ function tasksBlock(active: Task[]): HTMLElement {
|
||||
},
|
||||
},
|
||||
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-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"),
|
||||
@@ -193,8 +194,8 @@ function todoBlock(project: Project, todo: Todo | string | undefined, active: Ta
|
||||
list.append(
|
||||
h(
|
||||
"li",
|
||||
{ class: task && task.id === state.selectedTaskId ? "selected" : "" },
|
||||
h("div", { class: "todo-head" }, h("span", { class: "todo-title" }, item.title), action),
|
||||
{ class: task && task.id === state.selectedTaskId ? "selected" : "", style: task ? taskStyle(task.id) : "" },
|
||||
h("div", { class: "todo-head" }, h("span", { class: task ? "todo-title task-name" : "todo-title" }, item.title), action),
|
||||
item.body && markdown(item.body, "md todo-body"),
|
||||
),
|
||||
);
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
import { get, type AgentProcess, type ByreEvent, type Status, type TurnRef, type Zombie } from "./api.js";
|
||||
import { ago, errorText, h, openPanel, replace } from "./dom.js";
|
||||
import { findTask, on, onEach, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
const refreshKinds = new Set(["session.created", "session.state", "task.created", "task.merged", "task.discarded", "project.created", "admin.health"]);
|
||||
|
||||
@@ -147,12 +148,12 @@ function turnList(turns: TurnRef[], kind: "running" | "queued", empty: string, p
|
||||
const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined;
|
||||
return h(
|
||||
"li",
|
||||
{},
|
||||
{ style: taskStyle(t.task_id) },
|
||||
h(
|
||||
"a",
|
||||
{ href: `#/task/${t.task_id}` },
|
||||
kind === "queued" ? h("span", { class: "queue-pos" }, `${i + 1}.`) : h("span", { class: "dot running", title: "running" }),
|
||||
h("span", { class: "task-title" }, project && h("span", { class: "project-name" }, `${project.name} ▸ `), task?.title ?? t.task_id),
|
||||
h("span", { class: "task-title" }, project && h("span", { class: "project-name" }, `${project.name} ▸ `), h("span", { class: "task-name" }, task?.title ?? t.task_id)),
|
||||
liveness(procs.find((p) => p.session_id === t.session_id)),
|
||||
h("span", { class: "task-state" }, t.vendor),
|
||||
),
|
||||
@@ -168,7 +169,7 @@ function taskLink(taskId: string | undefined): HTMLElement | string {
|
||||
}
|
||||
const task = findTask(taskId);
|
||||
const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined;
|
||||
return h("a", { href: `#/task/${taskId}` }, project ? `${project.name} ▸ ` : "", task?.title ?? taskId);
|
||||
return h("a", { href: `#/task/${taskId}`, class: "task-link", style: taskStyle(taskId) }, project ? `${project.name} ▸ ` : "", task?.title ?? taskId);
|
||||
}
|
||||
|
||||
function since(iso: string): string {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ago, errorText, h, replace, showAlert, showConfirm } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { previewURL } from "./preview.js";
|
||||
import { currentSession, findTask, focusTab, focusTask, onEach, pinSnapshot, setFocus, state, type Topic } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
export type Usage = { used: number; window: number; model: string };
|
||||
|
||||
@@ -987,6 +988,8 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
|
||||
"a",
|
||||
{
|
||||
href: `#/task/${src.id}`,
|
||||
class: "task-link",
|
||||
style: taskStyle(src.id),
|
||||
onclick: (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
focusTask(src.id);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { findTask } from "./state.js";
|
||||
|
||||
export function taskStyle(taskId: string): string {
|
||||
const task = findTask(taskId);
|
||||
return task ? `--task: var(--task-${task.color})` : "";
|
||||
}
|
||||
Reference in New Issue
Block a user