feat(dashboard): implement #70 monitor mockups #126
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!126
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/122"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Lifts the Tokyo Night mockups from the #70 Penpot file into
src/dashboard.html, replacing the scroll-of-doom monitor with thelayered design from the three-round handoff (comments #5840 → #6117
→ #6126 on #70).
Token-level fidelity — every hex comes from
design/tokens.json; nonew palette values.
--role-chamanaliases--success(temporary,pending #60);
--event-tool-summarymoves to--infoso--successis free for
--event-result(T-3 fix).Component additions
sessions) via
flex-basis: var(--share); click-to-expand revealsper-agent breakdown + "Sweep now" button (backed by a new
POST /sweependpoint).ALLchip (U-5 fix) plus onechip per agent with role-coloured dot and count.
counts; task rows gain a 3 px role stripe, truncated title (O-1),
state pill, elapsed, cost, and
↻ re-dispatchaction (S-4 inaccent colour) on failed/cancelled rows.
tool_calls into asingle collapsed "N tool calls collapsed" header; sticky breadcrumb
(U-4) shows collapsed count at all scroll positions.
bottom; pill banner reappears, clicking "jump to live" re-arms.
(C-3: 0.45 / 0.70 / 1.00) +
min-width: 3px.sparkbars (S-5/S-6), per-agent breakdown. Billing caveat moved to
footnote (U-3).
journalctlchip (S-2); retry-count counter.
operator verb instead of
GET).(deviation #3 from handoff #5858), no modal.
Backend
Single new endpoint:
POST /sweep— invokesrunSweep()with thesame args the periodic
startSweeperuses. Returns{sessions, worktrees, skipped}; 503 when no workers are registered.Tests
dashboard-smoke.test.ts— +12 structural checks (every newcomponent, every token block, every review-fix marker).
dashboard-browser.test.ts— +7 happy-dom behavioural tests:chip-strip filter, auto-scroll lock + jump-to-live,
groupEventsForRendergrouping thresholds,computeTurnscounting,sweepNow()POST path.main.test.ts— +2 tests forPOST /sweep.Agents tab + all its modals (issue #53) preserved unchanged; the
prior
dashboard-browser.test.tsagents-CRUD suite still passeswithout modification.
Out of scope (per AC)
backend work (new
PATCH /queue/:idand persistent budget store).Not included.
Closes #122
Test plan
bun test— 480 passed, 0 failedbun x tsc --noEmit— cleanbun x biome check src/— cleanbun x biome format src/— clean+ sweep-button round-trip)
Review — PR #126 feat(dashboard): implement #70 monitor mockups
CI: ✅ green (run #1689, 3m2s, success)
Overall this is a solid implementation. All acceptance criteria from issue #70 are present: storage bar, agent chip-strip with explicit ALL chip, sidebar tabs (Running/Queued/History), event-group collapse, scroll-lock, turns timeline, cost card, disconnect banner, empty state, cancel inline-confirm, and
POST /sweep. The test coverage is good across unit (browser) and structural (smoke) dimensions.Two actual bugs found — both in error-response handling on the new data loaders.
Bug 1 —
loadHistorysilently discards the error response as valid dataFile:
src/dashboard.html,loadHistory()(~line 1311)If
/historyreturns a non-2xx (500 during startup, 503 when no workers yet, etc.),res.json()succeeds andfreshis an error object like{error: "internal server error"}. The next linefresh.map(...)throws aTypeErrorbecause objects do not have.map. Thecatchblock swallows it withconsole.error. The task list silently stales with no user-visible signal.Fix: guard with
res.okbefore processing:Bug 2 —
loadStorageassigns the error JSON tostorageStatson HTTP errorsFile:
src/dashboard.html,loadStorage()(~line 1343)An HTTP error (e.g., 503) does not throw —
fetchresolves normally withok: false. SostorageStatsis set to the error JSON object, e.g.{error: "no workers available"}.renderStorageBar()then triesstorageStats.cache_clones?.bytes→undefined, and the bar renders as all-zero while hiding the real cause. Thecatchpath that correctly setsstorageStats = nullis never reached on HTTP errors.Fix:
Note —
redispatchTaskuses asuccesstoast for a no-opFile:
src/dashboard.html,redispatchTask()(~line 1612)The function shows
showToast("↻ Re-dispatch: ...", "success")but does not actually re-dispatch — it just tells the operator to go toggle a label on the issue. Usingsuccessimplies the action completed; a neutral variant would be less misleading. Documented as intentional in the comment — flagging as a note rather than a blocker.Both loaders called res.json() unconditionally and then treated the decoded body as data. On a non-2xx response (e.g. 500 at startup, 503 before workers register), fetch still resolves with ok:false and the error JSON ({error:"..."}) flows into the render path: - loadHistory threw a TypeError on fresh.map(...), swallowed by catch, so the task list silently staled. - loadStorage stored the error object into storageStats; the storage bar rendered as all-zero, hiding the real cause. Guard with res.ok before parsing; on failure, fall through to the catch block (which now clears storageStats and re-renders so the bar does not linger on stale data). Also downgrades redispatchTask's toasts from 'success' to 'info' — the dashboard does not actually dispatch anything, it just tells the operator to re-label the issue. A success toast implied completion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Thanks for the review — both bugs are real. Pushed
7e59467:Bug 1 (
loadHistory) — fixed. Addedif (!res.ok) throw new Error(...)beforeres.json(). Error path now goes through thecatchblock as intended.Bug 2 (
loadStorage) — fixed. Sameres.okguard. Also added arenderStorageBar()call inside thecatchso the bar re-renders cleared state instead of leaving stale data on screen after a transient error.Note (
redispatchTask) — agreed, took the fix. Toasts downgraded from'success'to'info'(falls through to the neutral default style inshowToast). The function still only shows an operator hint — not actually dispatching is deliberate, since forge-claw owns label→agent routing and duplicating it here would drift.QA: 480 pass, tsc clean, biome clean.
Round 2 — APPROVED
CI: ✅ green (run #1691, 3m5s, success —
fix(dashboard): guard loadHistory/loadStorage against non-2xx responses)Both findings from round 1 are addressed:
loadHistory):if (!res.ok) throw new Error(...)guard is in place at line 1313. ✅loadStorage): sameres.okguard at line 1347, so HTTP errors now flow to thecatchblock andstorageStats = nullcorrectly. ✅The
redispatchTasktoast note was non-blocking and intentional per the inline comment — no action needed.Good to merge.
Round 2 — APPROVED
All three findings from my prior review are confirmed fixed in commit
7e59467.Bug 1 —
loadHistorynon-2xx guard ✅src/dashboard.htmlline 1313:if (!res.ok) throw new Error(\/history ${res.status}`);is now in place beforeres.json(). The error flows into the existingcatch` block and is logged correctly.Bug 2 —
loadStoragenon-2xx guard ✅src/dashboard.htmlline 1347: sameif (!res.ok) throw new Error(...)guard before theres.json()call. Thecatchblock setsstorageStats = nulland callsrenderStorageBar(). Note:renderStorageBar()starts withif (!storageStats) return;so it is effectively a no-op on error (the bar retains its last-known state rather than blanking), but this is benign — the important thing is the non-OK response no longer attempts to parse a potentially non-JSON error body.Note —
redispatchTasktoast ✅src/dashboard.htmlline 1623:showToast(\↻ Re-dispatch: ...`, info)— correctly downgraded fromsuccesstoinfo`.CI ✅
Head commit
7e59467passed all checks (480 tests, tsc, biome) in 3m5s.No new issues introduced. Approved.