From f626155f886eee39237724978bbfb2b1c9cc026c Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 6 Jul 2026 00:47:57 +0000 Subject: 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 Claude-Session: https://claude.ai/code/session_01VTUSAEKfsPc6WGDq45yPHD --- .../specs/2026-07-06-tasks-board-design.md | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-06-tasks-board-design.md (limited to 'docs') 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 + +... + +``` + +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. -- cgit v1.2.3