summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-06 00:47:57 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-06 00:47:57 +0000
commitf626155f886eee39237724978bbfb2b1c9cc026c (patch)
tree5e874fe9dff1dcfebeeaff0b4b5d408b2a56b9d0
parent0e49f91c62ff9977c6a917a6f197adbd7876bbb9 (diff)
docs: add design spec for unified Tasks board
Adds a Tasks tab (Kanban board, 5 columns: Queue/Running/Ready/ Interrupted/Done) covering all tasks regardless of story association, with inline auto-tailing execution logs on every card. Mostly a revival/generalization of UI code removed in cc6b323, plus a generalization of the existing Running-tab log-stream mechanism to every column. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD
-rw-r--r--docs/superpowers/specs/2026-07-06-tasks-board-design.md108
1 files changed, 108 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-06-tasks-board-design.md b/docs/superpowers/specs/2026-07-06-tasks-board-design.md
new file mode 100644
index 0000000..50b6af8
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-06-tasks-board-design.md
@@ -0,0 +1,108 @@
+# Unified Tasks Board — Design Spec
+
+**Date:** 2026-07-06
+**Status:** Approved
+
+---
+
+## Goal
+
+The web UI's "Board" (Stories tab) only shows Stories — a Kanban of the `stories` table. Plain top-level tasks (e.g. anything submitted directly via the chatbot MCP `submit_task` tool, with no story association) have no visibility anywhere in the current nav: the old Queue/Interrupted/Ready/Running tabs that used to show them were removed in commit `cc6b323` (2026-07-05), in favor of a 4-tab nav (Stories/Stats/Drops/Settings).
+
+Add a single new "Tasks" tab: a Kanban board of all tasks (story-linked or not), grouped by state into columns, so a task's whole lifecycle — created → queued → running → ready/interrupted → done — is visible at a glance, with live/replayed execution logs inline on every card without needing to click in.
+
+---
+
+## Context
+
+Investigation while root-causing an unrelated bug (chatbot-submitted tasks being auto-cancelled — since fixed) surfaced that a large amount of UI infrastructure for this already exists in `web/app.js`, just disconnected from the current nav after `cc6b323`:
+
+- `createTaskCard(task)` — full-featured task card: name, state badge, priority, created-at, project, description, error message (for failed states), checker-report flag, changestats/deployment badges, and state-appropriate action buttons (Resume/Restart/Cancel/Accept/Reject).
+- `openTaskPanel(taskId)` → `renderTaskPanel(task, executions)` — side-panel task detail view: summary, Q&A history, overview, agent config, and (implicitly, via execution rows) access to full log history.
+- `renderRunningView(tasks)` / `startRunningLogStream(taskId, logArea)` — the old Running tab's per-card live log tailing via SSE, with elapsed timer, exec ID, and a Cancel button.
+- `openLogViewer(execId, containerEl)` — a fuller log viewer, same SSE mechanism.
+- `STORY_COLUMNS` / `columnForStatus` / `groupStoriesByColumn` — the exact column-grouping pattern to mirror for tasks (see `renderStoriesBoard`).
+
+None of this was deleted; only the nav buttons, panel containers (`index.html`), and the `renderActiveTab` switch cases dispatching to them were removed. CSS (`.stories-board`, `.stories-column*`) is untouched and generic enough to reuse for the new board without duplication.
+
+**Also discovered:** `web/api/executions/{id}/logs/stream` (`internal/api/logs.go`, `handleStreamLogs`) already handles both live and terminal executions transparently — for a `RUNNING` execution it live-tails (polling every 250ms); for any terminal execution it replays the whole stored log once, then immediately emits `done`. This means the exact same log-streaming code path works, unmodified, for a currently-running task's card and for a long-finished one's card — no separate "static tail" fetch mechanism is needed.
+
+---
+
+## Design
+
+### 1. Nav & entry point
+
+One new top-level tab, added to the existing 4 (Stories/Stats/Drops/Settings) — 5 total:
+
+```html
+<button class="tab" data-tab="tasks" title="Tasks">📝</button>
+...
+<div data-panel="tasks" hidden>
+ <div class="tasks-board"></div>
+</div>
+```
+
+Default landing tab remains Stories (unchanged from current behavior — confirmed with user, not reverting to the old `'queue'` default).
+
+### 2. Column model
+
+New pure functions in `app.js`, structurally identical to `STORY_COLUMNS`/`columnForStatus`/`groupStoriesByColumn`:
+
+```js
+export const TASK_COLUMNS = [
+ { key: 'queue', label: 'Queue', states: ['PENDING', 'QUEUED'] },
+ { key: 'running', label: 'Running', states: ['RUNNING', 'BLOCKED'] },
+ { key: 'ready', label: 'Ready', states: ['READY'] },
+ { key: 'interrupted', label: 'Interrupted', states: ['FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED'] },
+ { key: 'done', label: 'Done', states: ['COMPLETED'] },
+];
+
+export function columnForTaskState(state) { /* find matching column, fallback 'queue' */ }
+export function groupTasksByColumn(tasks) { /* { [columnKey]: task[] }, sorted per-column (see below) */ }
+```
+
+Sort direction is per-column, matching the semantics the old (removed) panels already used — not a uniform order:
+- **Queue**: oldest-first (ascending) — FIFO; matches old `renderQueuePanel`.
+- **Ready**: oldest-first (ascending) — the longest-waiting-for-review item surfaces first, so nothing sits forgotten; matches old `renderReadyPanel`.
+- **Interrupted**: newest-first (descending) — the most recent failure is the most urgent to look at; matches old `renderInterruptedPanel`.
+- **Done**: newest-first (descending) — the most recently completed task is most relevant; matches old `renderReadyPanel`'s completed-history sub-section.
+- **Running**: newest-first (descending) — the old `renderRunningView` applied no explicit sort (rarely more than a couple of cards, bounded by `max_concurrent`), so this is a new-but-inconsequential default rather than a faithful restoration.
+
+This is a clean, mutually-exclusive partition of all 10 task states (`internal/task` state machine). Design decisions, confirmed with user:
+- **BLOCKED → Running**: not an error state — the task is actively in-flight, just paused on a subtask or an `ask_user` answer. Grouping it with Running keeps "things currently in motion" together.
+- **TIMED_OUT / BUDGET_EXCEEDED → Interrupted**: both are abnormal stops needing a human decision (resume, bump budget), same bucket as FAILED/CANCELLED — all four already have Resume/Restart buttons on their cards via `createTaskCard`.
+
+Unlike the Stories board, no drag-and-drop: a task's state is driven entirely by the executor/state machine, not manually reassignable by dragging a card to a new column.
+
+### 3. Rendering
+
+`renderTasksBoard(tasks, container)` mirrors `renderStoriesBoard` (same `.tasks-board`/`.tasks-column`/`.tasks-column-header`/`.tasks-column-count`/`.tasks-column-list` CSS classes as `.stories-board` etc. — visually identical column chrome, no CSS duplication needed since the existing rules are generic flex-column layout, not story-specific).
+
+Each column's task list renders via the existing `renderTasksIntoContainer()` helper (non-destructive: reuses/updates existing card DOM nodes rather than a full teardown-and-rebuild on every poll tick, exactly as the old Queue/Ready/Interrupted panels already did).
+
+### 4. Inline log tail on every card (no click required)
+
+Every card, in every column, gets an inline log area wired to `/api/executions/{id}/logs/stream` for that task's most recent execution (fetched via the existing `/api/executions?task_id=X&limit=1` call already used by `startRunningLogStream`):
+
+- **Running column**: reuses the existing larger, prominent log panel from the old `renderRunningView` card design (elapsed timer, agent/model, exec ID, live-tailing log, Cancel button) — this is a true live stream, since the execution is actively producing output.
+- **Queue column**: no execution exists yet for PENDING/QUEUED tasks (no `executions` row until dispatch) — shows a plain "Waiting to start…" placeholder instead of an empty log box. This is a natural consequence of the data model, not a gap to build around.
+- **Ready / Interrupted / Done columns**: a smaller, compact log excerpt (a handful of lines, fixed small max-height, scrollable) using the *same* SSE mechanism — since the execution is terminal, the endpoint replays the full stored log once and closes immediately, so this reads as "last lines of what happened," not a live stream, with no separate fetch path needed.
+
+**Rerender/lifecycle trade-off (the one real engineering wrinkle):** `renderTasksIntoContainer`'s existing HTML-diff-and-replace strategy would constantly detect a log-populated card as "changed" (since log lines keep appending) and repeatedly tear down/rebuild it, closing and reopening the stream every poll tick. Fix: the log area is a stable child element *excluded* from the HTML-diff comparison — built once per task ID and left alone across re-renders — with stream lifecycle tracked in a small map (`taskId → EventSource`), generalizing the existing `runningViewLogSources` map (currently Running-tab-only) to cover every column. A stream is opened once per visible task needing one, left open/replaying, and closed only when that task's card is no longer on the board or state changes require a fresh execution's log.
+
+Total concurrent open SSE connections is naturally bounded: Queue opens none, Ready/Interrupted/Done connections close almost immediately after replay, and truly-live connections are capped by `max_concurrent` in `claudomator.toml` (currently 3) since that's the ceiling on simultaneously-RUNNING tasks regardless of board size.
+
+### 5. Out of scope
+
+- Budget/Roles stay exactly where they are (merged into Settings, per `cc6b323`) — not reviving those as separate tabs.
+- No drag-and-drop / manual state reassignment on the Tasks board.
+- No changes to the Stories board, its columns, or its drag-and-drop.
+
+---
+
+## Testing
+
+- New `web/test/tasks-board.test.mjs`, mirroring `web/test/stories-board.test.mjs`'s structure: column-mapping completeness (every task state maps to exactly one column), fallback-state behavior, grouping/sorting correctness.
+- Fix 2 pre-existing failing tests in `web/test/tab-persistence.test.mjs` (stale assertions that the default tab is `'queue'`, left broken since `cc6b323` changed the default to `'stories'` without updating this test) — update assertions to `'stories'`, matching actual current behavior. Drive-by fix since it's directly adjacent to the nav change this spec makes.
+- No Go-side changes are anticipated (both REST endpoints this design relies on — `/api/tasks`, `/api/executions?task_id=`, `/api/executions/{id}/logs/stream` — already exist and are already exercised by existing Go tests); confirm with `go test ./...` after implementation regardless, as a safety check.