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"}
|
||||
@@ -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})` : "";
|
||||
}
|
||||
+51
-2
@@ -49,6 +49,18 @@
|
||||
--warn: #855600;
|
||||
--warn-tint: #fdf3dc;
|
||||
--ok: #1a7f37;
|
||||
--task-0: #a4273f;
|
||||
--task-1: #9f3501;
|
||||
--task-2: #376e00;
|
||||
--task-3: #027048;
|
||||
--task-4: #026d64;
|
||||
--task-5: #046a77;
|
||||
--task-6: #06658e;
|
||||
--task-7: #1f58b6;
|
||||
--task-8: #5849b2;
|
||||
--task-9: #793c9f;
|
||||
--task-10: #942e79;
|
||||
--task: var(--accent);
|
||||
--shadow: 0 8px 28px rgb(16 24 40 / 0.18);
|
||||
--font: "Atkinson Hyperlegible Mono", ui-monospace, Menlo, monospace;
|
||||
font: 13px/1.55 var(--font);
|
||||
@@ -69,6 +81,17 @@
|
||||
--warn: #f2c66d;
|
||||
--warn-tint: #352a14;
|
||||
--ok: #7fd88f;
|
||||
--task-0: #fe9fa6;
|
||||
--task-1: #ffa383;
|
||||
--task-2: #99d079;
|
||||
--task-3: #68d7a1;
|
||||
--task-4: #37d8c9;
|
||||
--task-5: #2fd4ec;
|
||||
--task-6: #69caff;
|
||||
--task-7: #98bffe;
|
||||
--task-8: #b6b4fe;
|
||||
--task-9: #d7a6fc;
|
||||
--task-10: #f59cd8;
|
||||
--shadow: 0 8px 28px rgb(0 0 0 / 0.5);
|
||||
}
|
||||
}
|
||||
@@ -282,6 +305,24 @@ textarea {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.task-pane > .pane-tabs {
|
||||
background: color-mix(in srgb, var(--task) 12%, var(--bg));
|
||||
}
|
||||
|
||||
.task-pane > .pane-tabs .task-picker {
|
||||
color: var(--task);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.task-pane > .pane-tabs button.active {
|
||||
border-bottom-color: var(--task);
|
||||
}
|
||||
|
||||
.task-name,
|
||||
a.task-link {
|
||||
color: var(--task);
|
||||
}
|
||||
|
||||
.pane-tabs button:hover:not(:disabled),
|
||||
.range-tabs button:hover:not(:disabled),
|
||||
.modes button:hover:not(:disabled) {
|
||||
@@ -534,7 +575,7 @@ button.compact.danger {
|
||||
}
|
||||
|
||||
.transcript.running {
|
||||
border-color: var(--accent);
|
||||
border-color: var(--task);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
@@ -543,7 +584,7 @@ button.compact.danger {
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
margin: 5px 1px;
|
||||
background: var(--accent);
|
||||
background: var(--task);
|
||||
-webkit-mask: var(--frames) 0 0 / 50px 10px no-repeat;
|
||||
mask: var(--frames) 0 0 / 50px 10px no-repeat;
|
||||
animation: braille 0.8s steps(10) infinite;
|
||||
@@ -901,6 +942,14 @@ details.note .md {
|
||||
height: calc(4.5lh + 14px);
|
||||
}
|
||||
|
||||
.task-pane .compose textarea {
|
||||
border-color: var(--task);
|
||||
}
|
||||
|
||||
.task-pane .compose textarea:focus {
|
||||
box-shadow: 0 0 0 1px var(--task);
|
||||
}
|
||||
|
||||
.compose-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user