Give each task an assigned colour across the UI; lead links tasks
This commit is contained in:
Vendored
+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();
|
||||
const texts = new Map();
|
||||
export function mountInbox(root) {
|
||||
@@ -260,7 +261,7 @@ function attachments(list, taskId) {
|
||||
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":
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+5
@@ -1,5 +1,6 @@
|
||||
import { h } from "./dom.js";
|
||||
import { diagram } from "./mermaid.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
const choiceMarker = /^(\([ *]\)|\[[ xX]\])\s+(\S.*?)\s*$/;
|
||||
const detailsOpen = /^\s*<details>\s*(?:<summary>(.*?)<\/summary>)?\s*$/i;
|
||||
const summaryLine = /^\s*<summary>(.*?)<\/summary>\s*$/i;
|
||||
@@ -270,6 +271,10 @@ function link(href, children) {
|
||||
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);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+2
-1
@@ -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) {
|
||||
const status = h("button", { class: "nav-link" }, "Status");
|
||||
status.onclick = openStatus;
|
||||
@@ -92,7 +93,7 @@ export function openSwitcher() {
|
||||
.sort((a, b) => Number(b.count > 0) - Number(a.count > 0) || a.visited - b.visited);
|
||||
rows = matches.map(({ project, task, count }) => {
|
||||
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), 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"));
|
||||
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), " ", 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"));
|
||||
return { el, pick: () => go(task.id) };
|
||||
});
|
||||
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
+7
-1
@@ -29,11 +29,18 @@ export function taskPane() {
|
||||
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;
|
||||
let dispose = () => { };
|
||||
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"), " ▾");
|
||||
};
|
||||
const render = () => {
|
||||
@@ -60,7 +67,6 @@ export function taskPane() {
|
||||
onEach(["projects", "tasks", "selection", "focus"], renderPicker);
|
||||
render();
|
||||
renderPicker();
|
||||
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
|
||||
trackPaneFocus(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
-3
@@ -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";
|
||||
const refreshKinds = new Set(["task.merged", "project.created"]);
|
||||
export function mountProject(root) {
|
||||
const todos = new Map();
|
||||
@@ -93,13 +94,13 @@ function tasksBlock(active) {
|
||||
return h("ul", { class: "task-list" }, ...active.map((task) => {
|
||||
const sess = currentSession(task.id);
|
||||
const count = state.inbox.filter((item) => item.task_id === task.id).length;
|
||||
return h("li", { class: task.id === state.selectedTaskId ? "selected" : "" }, h("a", {
|
||||
return h("li", { class: task.id === state.selectedTaskId ? "selected" : "", style: taskStyle(task.id) }, h("a", {
|
||||
href: `#/task/${task.id}`,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
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), 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: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), 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")));
|
||||
}));
|
||||
}
|
||||
function todoBlock(project, todo, active, starting, start) {
|
||||
@@ -146,7 +147,7 @@ function todoBlock(project, todo, active, starting, start) {
|
||||
button.onclick = () => void start(project, item);
|
||||
action = button;
|
||||
}
|
||||
list.append(h("li", { class: task && task.id === state.selectedTaskId ? "selected" : "" }, h("div", { class: "todo-head" }, h("span", { class: "todo-title" }, item.title), action), item.body && markdown(item.body, "md todo-body")));
|
||||
list.append(h("li", { 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")));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+3
-2
@@ -1,6 +1,7 @@
|
||||
import { get } 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"]);
|
||||
export function openStatus() {
|
||||
openPanel("Status", mountStatus);
|
||||
@@ -99,7 +100,7 @@ function turnList(turns, kind, empty, procs) {
|
||||
return h("ul", { class: "task-list" }, ...turns.map((t, i) => {
|
||||
const task = findTask(t.task_id);
|
||||
const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined;
|
||||
return h("li", {}, 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), liveness(procs.find((p) => p.session_id === t.session_id)), h("span", { class: "task-state" }, t.vendor)));
|
||||
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} ▸ `), 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)));
|
||||
}), turns.length === 0 && h("li", { class: "empty-row" }, empty));
|
||||
}
|
||||
function taskLink(taskId) {
|
||||
@@ -108,7 +109,7 @@ function taskLink(taskId) {
|
||||
}
|
||||
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) {
|
||||
const seconds = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 1000));
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -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 } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
export const models = ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-fable-5-1"];
|
||||
const quietReply = "NO_REPLY";
|
||||
const taskSource = {
|
||||
@@ -831,6 +832,8 @@ function renderEvent(ev, ctx) {
|
||||
const link = src?.status === "active"
|
||||
? h("a", {
|
||||
href: `#/task/${src.id}`,
|
||||
class: "task-link",
|
||||
style: taskStyle(src.id),
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
focusTask(src.id);
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { findTask } from "./state.js";
|
||||
export function taskStyle(taskId) {
|
||||
const task = findTask(taskId);
|
||||
return task ? `--task: var(--task-${task.color})` : "";
|
||||
}
|
||||
//# sourceMappingURL=taskcolor.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"taskcolor.js","sourceRoot":"","sources":["../src/taskcolor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC,CAAC,CAAC,sBAAsB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC"}
|
||||
Reference in New Issue
Block a user