Give each task an assigned colour across the UI; lead links tasks

This commit is contained in:
Ian Gulliver
2026-10-15 13:30:09 -07:00
parent 74ce1dd6ec
commit 696385fced
32 changed files with 375 additions and 193 deletions
+2 -1
View File
@@ -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":
+1 -1
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -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);
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -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))) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -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;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -3
View File
@@ -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;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -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));
+1 -1
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -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);
+1 -1
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -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
+1
View File
@@ -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"}
+2
View File
@@ -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
View File
@@ -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":
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+6
View File
@@ -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
View File
@@ -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;