feat(tasks): adopt canonical task statuses

This commit is contained in:
2026-05-26 01:32:50 +02:00
parent da5aee6e38
commit 38835e9e79
61 changed files with 692 additions and 342 deletions

View File

@@ -141,11 +141,14 @@ const _STATUS_STYLE = {
archived: "background:#e2e3e5;color:#383d41",
open: "background:#dbeafe;color:#1e40af",
in_progress: "background:#fef3c7;color:#92400e",
wait: "background:#fef3c7;color:#92400e",
progress: "background:#ede9fe;color:#5b21b6",
addressed: "background:#dcfce7;color:#166534",
deferred: "background:#f1f5f9;color:#64748b",
wont_fix: "background:#f3f4f6;color:#9ca3af",
todo: "background:#f1f5f9;color:#475569",
done: "background:#dcfce7;color:#166534",
cancel: "background:#f3f4f6;color:#9ca3af",
cancelled: "background:#f3f4f6;color:#9ca3af",
resolved: "background:#dcfce7;color:#166534",
superseded: "background:#e2e3e5;color:#383d41",
@@ -226,8 +229,8 @@ function _buildBody(entity, type) {
if (entity.tasks_total !== undefined) {
els.push(_divider(),
tf("Tasks", `${entity.tasks_done ?? 0} done / ${entity.tasks_total} total` +
(entity.tasks_in_progress > 0 ? ` · ${entity.tasks_in_progress} in progress` : "") +
(entity.tasks_blocked > 0 ? ` · ${entity.tasks_blocked} blocked` : ""))
(entity.tasks_progress > 0 ? ` · ${entity.tasks_progress} progress` : "") +
(entity.tasks_wait > 0 ? ` · ${entity.tasks_wait} wait` : ""))
);
}
if (entity.depends_on?.length) {

View File

@@ -153,7 +153,7 @@ export const FIELD_HELP = {
},
status: {
label: "Status",
description: "Current lifecycle state: todo, in_progress, blocked, done, or cancelled.",
description: "Current lifecycle state. Tasks use wait, todo, progress, done, or cancel.",
doc: "/docs/workstream-lifecycle",
},
topic_id: {
@@ -182,7 +182,7 @@ export const FIELD_HELP = {
},
needs_human: {
label: "Needs Human",
description: "True if the task is blocked waiting for human input or approval.",
description: "True if the task is waiting for human input or approval.",
doc: "/interventions",
},
intervention_note: {

View File

@@ -3,7 +3,7 @@ import {WORKSTREAM_STATUSES} from "./workplan-status.js";
const STYLE_ID = "status-control-styles";
export const TASK_STATUSES = ["todo", "in_progress", "blocked", "done", "cancelled"];
export const TASK_STATUSES = ["wait", "todo", "progress", "done", "cancel"];
export {WORKSTREAM_STATUSES};
function ensureStyles() {
@@ -138,9 +138,9 @@ export function statusControl({
if (nextStatus === currentStatus) return;
let blockingReason = null;
if (type === "task" && nextStatus === "blocked") {
if (type === "task" && nextStatus === "wait") {
const existingReason = entity?.blocking_reason ?? "";
const reason = existingReason || window.prompt("Blocking reason required for blocked tasks:");
const reason = existingReason || window.prompt("Reason this task is waiting:");
if (!reason) {
select.value = currentStatus;
setMessage("unchanged");

View File

@@ -33,7 +33,7 @@ export function isOpenWorkstream(status) {
export function isStalledWorkstream(w, staleDays = 7) {
const staleAt = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000);
const openTasks = (w.todo ?? 0) + (w.in_progress ?? 0) + (w.blocked ?? 0);
const openTasks = (w.todo ?? 0) + (w.progress ?? 0) + (w.wait ?? 0);
return ["active", "blocked"].includes(normalizeWorkstreamStatus(w.status))
&& new Date(w.updated_at) < staleAt
&& (w.done ?? 0) > 0

View File

@@ -29,11 +29,12 @@ except urllib.error.URLError as e:
"archived": 0,
"total": 0,
},
"tasks": {"todo": 0, "in_progress": 0, "blocked": 0, "done": 0, "cancelled": 0, "total": 0},
"tasks": {"wait": 0, "todo": 0, "progress": 0, "done": 0, "cancel": 0, "total": 0},
"decisions": {"open": 0, "resolved": 0, "escalated": 0, "superseded": 0, "total": 0},
},
"topics": [],
"blocking_decisions": [],
"waiting_tasks": [],
"blocked_tasks": [],
"recent_progress": [],
"open_workstreams": [],

View File

@@ -145,8 +145,8 @@ Notifications appear in the [Inbox](/inbox) page and are queryable via
A request can optionally link to a **blocking task** via `blocking_task_id`.
When the request reaches `completed`, the system automatically patches that
task from `blocked` → `todo` and clears its `blocking_reason`. This means
blocked work resumes without manual intervention.
task from `wait` → `todo` and clears its `blocking_reason`. This means
waiting work resumes without manual intervention.
---

View File

@@ -241,7 +241,7 @@ TOC sidebar as a persistent KPI card.
### Multi-mode workstream chart
The Overview page renders a horizontal stacked bar chart using `@observablehq/plot`
showing task counts (done / in progress / blocked / todo) per workstream.
showing task counts (done / progress / wait / todo) per workstream.
A `<select>` dropdown switches between:
- **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived

View File

@@ -129,8 +129,8 @@ The session orientation protocol (every repo's CLAUDE.md) surfaces todos from
two sources:
**Internal todos** (Step 2 of orientation) — workplan files in `workplans/`
whose stored workstation/status label is `active`, with tasks in `todo` or
`in_progress`.
whose stored workstation/status label is `active`, with tasks in `wait`,
`todo`, or `progress`.
**Ecosystem todos targeting this repo** (Step 1 of orientation) —
`get_state_summary()` returns all open tasks across all workstreams. The session

View File

@@ -10,7 +10,7 @@ The Interventions page lists every task that has been flagged `needs_human=true`
## What is a human intervention?
A human intervention is a task that an agent has determined cannot proceed without direct human action — for example, approving a financial decision, confirming a legal commitment, or resolving a sensitive ambiguity. Flagging a task does not change its work status; the task continues to be `todo`, `in_progress`, or `blocked` while awaiting attention.
A human intervention is a task that an agent has determined cannot proceed without direct human action — for example, approving a financial decision, confirming a legal commitment, or resolving a sensitive ambiguity. Flagging a task does not change its work status; the task continues to be `wait`, `todo`, or `progress` while awaiting attention.
---
@@ -26,7 +26,7 @@ A human intervention is a task that an agent has determined cannot proceed witho
## Open section
Tasks are sorted by priority (critical → high → medium → low), then by status (blocked → in_progress → todo). Each card shows:
Tasks are sorted by priority (critical → high → medium → low), then by status (wait → progress → todo). Each card shows:
| Element | Meaning |
|---|---|
@@ -43,7 +43,7 @@ Tasks are sorted by priority (critical → high → medium → low), then by sta
Click **Mark done** on any open card. An inline form appears requiring a **resolution comment** — a short note describing what was done. The comment is mandatory; clicking **Confirm** without entering text highlights the field in red and does nothing.
Once confirmed, the API call sets `status = done`, `needs_human = false`, and replaces the action note with your resolution comment. The card moves to the **Completed / Cancelled** section on the next poll.
Once confirmed, the API call sets `status = done`, `needs_human = false`, and replaces the action note with your resolution comment. The card moves to the **Completed / Canceled** section on the next poll.
Click **Cancel** to dismiss the form without making changes.

View File

@@ -20,8 +20,8 @@ by repository. Each bar is broken into four task-status segments:
| Colour | Segment |
|--------|---------|
| green | done |
| blue | in progress |
| orange-red | blocked |
| purple | progress |
| orange | wait |
| light grey | todo |
The left axis shows the `domain / repository` label once per repository group.

View File

@@ -42,7 +42,7 @@ These types are used by the State Hub's built-in write operations:
| `workstream_status_changed` | Workstream moved between canonical lifecycle states |
| `workstation_advanced` | Flow-aware movement via `advance_workstation()` succeeded |
| `task_created` | A new task was added to a workstream |
| `task_status_changed` | Task moved to todo / in_progress / blocked / done / cancelled |
| `task_status_changed` | Task moved to wait / todo / progress / done / cancel |
| `decision_recorded` | A decision (pending or made) was recorded |
| `decision_resolved` | A pending decision was resolved |

View File

@@ -127,7 +127,7 @@ priority: medium
| Field | Values | Description |
|-------|--------|-------------|
| `id` | string | Unique task identifier |
| `status` | `todo` \| `in_progress` \| `done` | Task state |
| `status` | `wait` \| `todo` \| `progress` \| `done` \| `cancel` | Task state |
| `priority` | `high` \| `medium` \| `low` | Execution order hint |
---
@@ -137,8 +137,8 @@ priority: medium
As Claude completes tasks it edits the workplan file directly:
```
status: todo → status: in_progress (when starting)
status: in_progress → status: done (when verified complete)
status: todo → status: progress (when starting)
status: progress → status: done (when verified complete)
```
When every task is `done`, Claude also updates the frontmatter:

View File

@@ -5,7 +5,7 @@ title: Tasks — Reference
# Tasks — Reference
The Tasks page shows all tasks across every workstream and domain, with live
filtering, a workstation distribution chart, and a blocked-tasks highlight
filtering, a workstation distribution chart, and a waiting-tasks highlight
section.
---
@@ -17,11 +17,11 @@ compatibility.
| Workstation | Meaning |
|--------|---------|
| **wait** | Waiting on another actor, event, decision, input, or condition |
| **todo** | Not yet started |
| **in_progress** | Actively being worked on |
| **blocked** | Cannot proceed — has a blocking reason |
| **progress** | Actively being worked on |
| **done** | Completed |
| **cancelled** | Dropped; not counted toward totals |
| **cancel** | Stopped; not counted toward totals |
---
@@ -56,37 +56,37 @@ per stored workstation/status label, colour-coded:
| Colour | Status |
|--------|--------|
| orange | wait |
| grey-blue | todo |
| blue | in_progress |
| red | blocked |
| purple | progress |
| green | done |
| light grey | cancelled |
| light grey | cancel |
---
## Blocked Tasks section
## Waiting Tasks section
Shows cards for every task currently in the `blocked` workstation within the
Shows cards for every task currently in the `wait` workstation within the
active filter. Each card displays:
- Priority badge and status
- Domain and workstream context
- Task title
- Blocking reason (amber background)
- Wait reason (amber background)
---
## KPI sidebar card
Shows four counts for the unfiltered dataset: open (todo + in_progress +
blocked), blocked, in progress, done, and a done-% of total.
Shows counts for the unfiltered dataset: open (`wait` + `todo` + `progress`),
waiting, progress, done, and a done-% of total.
---
## Sorting
Tasks are sorted by status (blocked first, then in_progress, todo, done,
cancelled) then by priority (critical → high → medium → low) within each
Tasks are sorted by status (wait first, then progress, todo, done, cancel) then
by priority (critical → high → medium → low) within each
status group.
---

View File

@@ -18,7 +18,7 @@ boundary rule and routing workflows.
### Internal
Open tasks (`todo`, `in_progress`, `blocked`) in **custodian domain workstreams**
Open tasks (`wait`, `todo`, `progress`) in **custodian domain workstreams**
whose title does not contain a `[repo:]` routing prefix.
These are tasks this agent is directly responsible for and can address within

View File

@@ -57,12 +57,12 @@ const pageState = (async function*() {
const counts = {};
for (const t of taskList) {
const wid = t.workstream_id;
if (!counts[wid]) counts[wid] = {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0};
if (!counts[wid]) counts[wid] = {done: 0, progress: 0, wait: 0, todo: 0, total: 0};
counts[wid].total++;
if (t.status === "done") counts[wid].done++;
else if (t.status === "in_progress") counts[wid].in_progress++;
else if (t.status === "blocked") counts[wid].blocked++;
else if (t.status === "todo") counts[wid].todo++;
if (t.status === "done") counts[wid].done++;
else if (t.status === "progress") counts[wid].progress++;
else if (t.status === "wait") counts[wid].wait++;
else if (t.status === "todo") counts[wid].todo++;
}
wsAll = wsList.map(w => {
const repo = repoMap[w.repo_id];
@@ -78,7 +78,7 @@ const pageState = (async function*() {
workplan_archived: workplan.archived ?? false,
health_labels: workplan.health_labels ?? [],
href: `./workstreams/${w.id}`,
...(counts[w.id] ?? {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0}),
...(counts[w.id] ?? {done: 0, progress: 0, wait: 0, todo: 0, total: 0}),
};
});
} catch (e) {
@@ -233,13 +233,13 @@ for (const w of chartWs) {
_seen.add(group);
}
const statusOrder = ["done", "in progress", "blocked", "todo"];
const statusColors = ["#4caf50", "steelblue", "#ff7043", "#e0e0e0"];
const statusOrder = ["done", "progress", "wait", "todo"];
const statusColors = ["#4caf50", "#8b5cf6", "#f59e0b", "#e0e0e0"];
const _taskRows = chartWs.flatMap(w => [
{id: w.id, title: w.title, status: "done", count: w.done ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
{id: w.id, title: w.title, status: "in progress", count: w.in_progress ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
{id: w.id, title: w.title, status: "blocked", count: w.blocked ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
{id: w.id, title: w.title, status: "progress", count: w.progress ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
{id: w.id, title: w.title, status: "wait", count: w.wait ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
{id: w.id, title: w.title, status: "todo", count: w.todo ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href},
]).filter(d => d.count > 0);
@@ -370,7 +370,7 @@ display(html`<div class="grid grid-cols-3" style="gap:1rem;margin-bottom:1.5rem"
## Status
```js
const blockedTasks = summary.blocked_tasks ?? [];
const waitingTasks = summary.waiting_tasks ?? summary.blocked_tasks ?? [];
const wsById = Object.fromEntries((summary.open_workstreams ?? []).map(w => [w.id, w]));
const todayCount = (summary.recent_progress ?? []).filter(e =>
e.created_at?.startsWith(new Date().toISOString().slice(0, 10))).length;
@@ -388,9 +388,9 @@ const statusEl = html`<div>
<p class="big-num">${decCount}</p>
<small>${decisions.escalated ?? 0} escalated</small>
</a>
<div class="card card-link ${blockedTasks.length > 0 ? 'warn' : ''}" data-toggle="blocked-panel">
<h3>Blocked Tasks</h3>
<p class="big-num">${blockedTasks.length}</p>
<div class="card card-link ${waitingTasks.length > 0 ? 'warn' : ''}" data-toggle="waiting-panel">
<h3>Waiting Tasks</h3>
<p class="big-num">${waitingTasks.length}</p>
<small>of ${tasks.total ?? 0} total · click to expand</small>
</div>
<a class="card card-link" href="#recent-activity">
@@ -400,10 +400,10 @@ const statusEl = html`<div>
</a>
</div>
<div id="blocked-panel" style="display:none;margin-bottom:1rem">
${blockedTasks.length === 0
? html`<p class="dim" style="padding:0.5rem 0">No tasks currently blocked.</p>`
: html`<div class="bt-list">${blockedTasks.map(t => {
<div id="waiting-panel" style="display:none;margin-bottom:1rem">
${waitingTasks.length === 0
? html`<p class="dim" style="padding:0.5rem 0">No tasks currently waiting.</p>`
: html`<div class="bt-list">${waitingTasks.map(t => {
const wsName = wsById[t.workstream_id]?.title ?? t.workstream_id?.slice(0,8) ?? "—";
return html`<div class="bt-row">
<div class="bt-meta">${wsName}</div>
@@ -415,11 +415,11 @@ const statusEl = html`<div>
</div>
</div>`;
statusEl.querySelector('[data-toggle="blocked-panel"]').addEventListener('click', () => {
const panel = statusEl.querySelector('#blocked-panel');
statusEl.querySelector('[data-toggle="waiting-panel"]').addEventListener('click', () => {
const panel = statusEl.querySelector('#waiting-panel');
const isOpen = panel.style.display !== 'none';
panel.style.display = isOpen ? 'none' : 'block';
statusEl.querySelector('[data-toggle="blocked-panel"] small').textContent =
statusEl.querySelector('[data-toggle="waiting-panel"] small').textContent =
isOpen ? `of ${tasks.total ?? 0} total · click to expand` : `of ${tasks.total ?? 0} total · click to collapse`;
});

View File

@@ -51,7 +51,7 @@ const _ts = interventionState.ts;
```
```js
const OPEN_STATUSES = new Set(["todo", "in_progress", "blocked"]);
const OPEN_STATUSES = new Set(["wait", "todo", "progress"]);
// open = currently flagged for human action
// closed = previously flagged (intervention_note records the resolution comment)
const open = tasks.filter(t => t.needs_human === true);
@@ -125,7 +125,7 @@ Tasks flagged `needs_human=true` — actions only a human can take.
import {openActionConfirm} from "./components/action-confirm.js";
const PRIORITY_ORDER = {critical: 0, high: 1, medium: 2, low: 3};
const STATUS_ORDER = {blocked: 0, in_progress: 1, todo: 2};
const STATUS_ORDER = {wait: 0, progress: 1, todo: 2};
function sortTasks(arr) {
return [...arr].sort((a, b) => {
@@ -217,11 +217,11 @@ if (closed.length === 0) {
.task-priority-medium { background: #dbeafe; color: #1e40af; }
.task-priority-low { background: #f1f5f9; color: #475569; }
.task-status-chip { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 10px; font-size: 0.7rem; font-weight: 500; }
.status-chip-blocked { background: #fee2e2; color: #991b1b; }
.status-chip-in_progress { background: #dbeafe; color: #1e40af; }
.status-chip-wait { background: #fef3c7; color: #92400e; }
.status-chip-progress { background: #ede9fe; color: #5b21b6; }
.status-chip-todo { background: #f1f5f9; color: #475569; }
.status-chip-done { background: #dcfce7; color: #166534; }
.status-chip-cancelled { background: #f1f5f9; color: #64748b; }
.status-chip-cancel { background: #f1f5f9; color: #64748b; }
.task-context { color: var(--theme-foreground-muted, #666); }
.task-ws-name { font-style: italic; }
.dim { color: gray; font-style: italic; }

View File

@@ -50,7 +50,7 @@ const _ts = taskState.ts;
```js
import {MultiSelect} from "./components/multiselect.js";
const STATUSES = ["todo", "in_progress", "blocked", "done", "cancelled"];
const STATUSES = ["wait", "todo", "progress", "done", "cancel"];
const PRIORITIES = ["critical", "high", "medium", "low"];
const _domainsResp = await fetch(`${API}/domains/?status=active`).catch(() => null);
const DOMAINS = _domainsResp?.ok
@@ -95,11 +95,11 @@ import {openEntityModal, buildEntityTable} from "./components/entity-modal.js";
import {statusControl, TASK_STATUSES} from "./components/status-control.js";
// ── KPI sidebar card ─────────────────────────────────────────────────────────
const _open = data.filter(t => ["todo", "in_progress", "blocked"].includes(t.status));
const _blocked = data.filter(t => t.status === "blocked");
const _inProg = data.filter(t => t.status === "in_progress");
const _open = data.filter(t => ["wait", "todo", "progress"].includes(t.status));
const _waiting = data.filter(t => t.status === "wait");
const _inProg = data.filter(t => t.status === "progress");
const _done = data.filter(t => t.status === "done");
const _total = data.filter(t => t.status !== "cancelled").length;
const _total = data.filter(t => t.status !== "cancel").length;
const _donePct = _total > 0 ? Math.round(_done.length / _total * 100) : 0;
const _kpiBox = html`<div class="kpi-infobox">
@@ -111,13 +111,13 @@ const _kpiBox = html`<div class="kpi-infobox">
</div>
</div>
<div class="kpi-row">
<span class="kpi-row-label">blocked</span>
<span class="kpi-row-label">waiting</span>
<div class="kpi-row-right">
<div class="kpi-row-value" style="color:${_blocked.length > 0 ? '#dc2626' : 'inherit'}">${_blocked.length}</div>
<div class="kpi-row-value" style="color:${_waiting.length > 0 ? '#d97706' : 'inherit'}">${_waiting.length}</div>
</div>
</div>
<div class="kpi-row">
<span class="kpi-row-label">in progress</span>
<span class="kpi-row-label">progress</span>
<div class="kpi-row-right">
<div class="kpi-row-value">${_inProg.length}</div>
</div>
@@ -154,11 +154,11 @@ injectTocTop("live-indicator", _liveEl);
import * as Plot from "npm:@observablehq/plot";
const STATUS_COLOR = {
wait: "#f59e0b",
todo: "#94a3b8",
in_progress: "#3b82f6",
blocked: "#ef4444",
progress: "#8b5cf6",
done: "#22c55e",
cancelled: "#cbd5e1",
cancel: "#cbd5e1",
};
const byStatus = STATUSES
@@ -178,16 +178,16 @@ display(byStatus.length === 0
);
```
## Blocked Tasks
## Waiting Tasks
```js
const _blockedInFilter = filtered.filter(t => t.status === "blocked");
const _waitingInFilter = filtered.filter(t => t.status === "wait");
if (_blockedInFilter.length === 0) {
display(html`<p class="dim">No blocked tasks in current filter. ✓</p>`);
if (_waitingInFilter.length === 0) {
display(html`<p class="dim">No waiting tasks in current filter. ✓</p>`);
} else {
display(html`<div class="task-blocked-list">${_blockedInFilter.map(t => html`
<div class="task-blocked-item entity-row" onclick=${() => openEntityModal(t, "task")}>
display(html`<div class="task-waiting-list">${_waitingInFilter.map(t => html`
<div class="task-waiting-item entity-row" onclick=${() => openEntityModal(t, "task")}>
<div class="task-item-header">
<span class="task-badge task-priority-${t.priority}">${t.priority}</span>
<span class="task-context">${t.domain}</span>
@@ -196,7 +196,7 @@ if (_blockedInFilter.length === 0) {
${t.assignee ? html`<span class="task-assignee">@${t.assignee}</span>` : ""}
</div>
<div class="task-title">${t.title}</div>
${t.blocking_reason ? html`<div class="task-blocking-reason">⊘ ${t.blocking_reason}</div>` : ""}
${t.blocking_reason ? html`<div class="task-wait-reason">⊘ ${t.blocking_reason}</div>` : ""}
</div>
`)}</div>`);
}
@@ -209,7 +209,7 @@ display(_filtersForm);
display(html`<p><strong>${filtered.length}</strong> tasks shown.</p>`);
const PRIORITY_ORDER = {critical: 0, high: 1, medium: 2, low: 3};
const STATUS_ORDER = {blocked: 0, in_progress: 1, todo: 2, done: 3, cancelled: 4};
const STATUS_ORDER = {wait: 0, progress: 1, todo: 2, done: 3, cancel: 4};
const sorted = [...filtered].sort((a, b) => {
const sd = (STATUS_ORDER[a.status] ?? 9) - (STATUS_ORDER[b.status] ?? 9);
@@ -246,9 +246,9 @@ display(buildEntityTable(
/* ── Filters ──────────────────────────────────────────────────────────────── */
/* ── Blocked task cards ───────────────────────────────────────────────────── */
.task-blocked-list { display: flex; flex-direction: column; gap: 0.5rem; }
.task-blocked-item { border-left: 3px solid #ef4444; border-radius: 0 6px 6px 0; background: var(--theme-background-alt); padding: 0.65rem 0.9rem; }
/* ── Waiting task cards ───────────────────────────────────────────────────── */
.task-waiting-list { display: flex; flex-direction: column; gap: 0.5rem; }
.task-waiting-item { border-left: 3px solid #f59e0b; border-radius: 0 6px 6px 0; background: var(--theme-background-alt); padding: 0.65rem 0.9rem; }
.task-item-header { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-bottom: 0.3rem; font-size: 0.75rem; }
.task-badge { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 10px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.task-priority-critical { background: #fee2e2; color: #991b1b; }
@@ -260,7 +260,7 @@ display(buildEntityTable(
.task-due { color: #dc2626; font-weight: 600; }
.task-assignee { color: var(--theme-foreground-muted, #888); }
.task-title { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.15rem; }
.task-blocking-reason { font-size: 0.8rem; color: #b45309; background: #fef3c7; border-radius: 4px; padding: 0.2rem 0.5rem; margin-top: 0.25rem; }
.task-wait-reason { font-size: 0.8rem; color: #b45309; background: #fef3c7; border-radius: 4px; padding: 0.2rem 0.5rem; margin-top: 0.25rem; }
/* ── Utility ──────────────────────────────────────────────────────────────── */
.dim { color: gray; font-style: italic; }

View File

@@ -60,7 +60,7 @@ const _ts = todoState.ts;
```js
// ── Classify tasks ────────────────────────────────────────────────────────────
const OPEN_STATUSES = new Set(["todo", "in_progress", "blocked"]);
const OPEN_STATUSES = new Set(["wait", "todo", "progress"]);
// Internal: custodian domain, open, no [repo:] routing prefix
const internal = tasks.filter(t =>
@@ -141,7 +141,7 @@ without a cross-repo routing prefix.
```js
const PRIORITY_ORDER = {critical: 0, high: 1, medium: 2, low: 3};
const STATUS_ORDER = {blocked: 0, in_progress: 1, todo: 2};
const STATUS_ORDER = {wait: 0, progress: 1, todo: 2};
function sortTasks(arr) {
return [...arr].sort((a, b) => {
@@ -249,8 +249,8 @@ if (improvements.length === 0) {
/* ── Task list ────────────────────────────────────────────────────────────── */
.task-list { display: flex; flex-direction: column; gap: 0.5rem; }
.task-item { border-left: 3px solid var(--theme-foreground-faint, #ccc); border-radius: 0 6px 6px 0; background: var(--theme-background-alt); padding: 0.65rem 0.9rem; }
.task-item.status-blocked { border-left-color: #ef4444; }
.task-item.status-in_progress { border-left-color: #3b82f6; }
.task-item.status-wait { border-left-color: #f59e0b; }
.task-item.status-progress { border-left-color: #8b5cf6; }
.task-item.status-todo { border-left-color: #94a3b8; }
.task-item-header { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-bottom: 0.3rem; font-size: 0.75rem; }
.task-badge { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 10px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
@@ -259,8 +259,8 @@ if (improvements.length === 0) {
.task-priority-medium { background: #dbeafe; color: #1e40af; }
.task-priority-low { background: #f1f5f9; color: #475569; }
.task-status-chip { display: inline-block; padding: 0.1rem 0.45rem; border-radius: 10px; font-size: 0.7rem; font-weight: 500; }
.status-chip-blocked { background: #fee2e2; color: #991b1b; }
.status-chip-in_progress { background: #dbeafe; color: #1e40af; }
.status-chip-wait { background: #fef3c7; color: #92400e; }
.status-chip-progress { background: #ede9fe; color: #5b21b6; }
.status-chip-todo { background: #f1f5f9; color: #475569; }
.task-context { color: var(--theme-foreground-muted, #666); }
.task-ws-name { font-style: italic; }

View File

@@ -44,7 +44,7 @@ if (raw.error) {
<div><span>Tasks</span><strong>${taskRows.length}</strong></div>
</div>`);
const statusOrder = {blocked: 0, in_progress: 1, todo: 2, done: 3, cancelled: 4};
const statusOrder = {wait: 0, progress: 1, todo: 2, done: 3, cancel: 4};
const sortedTasks = [...taskRows].sort((a, b) => {
const statusCompare = (statusOrder[a.status] ?? 9) - (statusOrder[b.status] ?? 9);
if (statusCompare !== 0) return statusCompare;
@@ -131,8 +131,8 @@ if (raw.error) {
white-space: nowrap;
}
.task-status-done { background: #e8f5e9; color: #1b5e20; }
.task-status-in_progress { background: #e3f2fd; color: #0d47a1; }
.task-status-blocked { background: #fff3e0; color: #bf360c; }
.task-status-progress { background: #ede9fe; color: #5b21b6; }
.task-status-wait { background: #fff3e0; color: #bf360c; }
.task-status-todo { background: #f1f5f9; color: #334155; }
.task-status-cancelled { background: #f3f4f6; color: #6b7280; }
.task-status-cancel { background: #f3f4f6; color: #6b7280; }
</style>