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

@@ -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; }