design(agents): Penpot mockup — fleet-health panel on /agents #238
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
3 participants
Notifications
Due date
No due date set.
Blocks
#239 feat(agents): fleet-health panel — saturation, queue, cost burn, last activity
charles/claude-hooks
Reference
charles/claude-hooks#238
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
User story
As a designer, I want a Penpot mockup for a fleet-health panel on the Agents page that summarises the whole fleet at a glance — busy/capacity saturation, queue depth, cost burn rate (USD/hr), per-agent last-activity timestamp — so the operator sees "am I saturating the fleet?" without scanning each card.
Acceptance criteria
Frames
6 / 12 busywith a donut / bar.container_missingsignal).Tokens + copy
design/tokens.json.Handoff
area:design-reviewon handoff.Out of scope
References
apps/server/src/main.ts—workersregistry, agent state snapshot.Design handoff — fleet-health panel on /agents
Penpot file: claude-hooks — fleet-health panel on /agents (#238)
Five pages. The strip lives on its own pages in three states (healthy / degraded / critical) so the implementer can diff the visual rules in one place. Page 4 enlarges a single agent card in three states so the two new rows (last-activity, 24 h cost spark) are legible. Page 5 drops the strip + eight cards back onto the
/agentslayout so saturation (6/12) reads against the real grid.Pages
bg-canvas, 4×tile-*,sat-bar-track/-fill,pill-status-healthybg-canvas, 4×tile-*(2 in warn surface),sat-bar-threshold,pill-status-degradedcontainer_missingwired to the watchdog signal — red state + persistent bannerbanner-container-missing,banner-btn-reconcile,banner-btn-logs, 3× warn + 1× error tilecard-healthy,card-idle-stale,card-criticalLAST ACTIVITYrow (relative age + event), new24 H COSTrow (sparkline + now/avg), existing current-task + actions unchangedtop-nav, compacttile-*, 8×card-*reviewer-securitycard in critical, onedesigner-defaultin idle-stale, six in healthy states — saturation math lines up with the strip tileTokens used
All values from
design/tokens.json(Tokyo Night Storm). No new tokens introduced — the warn-surface (#2F2A1F) and error-surface (#301F25) tile backgrounds are derived tints of existing surface tones, same pattern the dashboard already uses; worth lifting tocolor/surface-warnandcolor/surface-errorwhen this lands in code, but not yet tokenised in the source of truth.Decisions that deviated from the spec
6 / 12 busyat display size so the numeric is still the dominant element.▁▂▃…█). Penpot has no line/polyline primitive in the MCP, so every spark in the mockup is a Fira-Code text string. Real implementation should use SVG polylines; the block-char shapes shown are just legibility proxies. Same story for the 24 h per-card sparkline.container_missingrendered as a persistent banner below the strip, not as a tile-local flag. The spec said visual treatment "when an agent's container is missing". A banner felt like the right weight for a full-page critical state that needs operator action — it also carries theReconcile now/Watchdog log →quick-actions in-line, matching how the M19-6 pipeline-stall row surfaces a Bounce button next to a stalled review pill. The specific agent's card is still flipped red in the grid (page 5), so the signal is visible in both places.designer-default), same amber visual as degraded. Tune the threshold viapipeline.implement_threshold_msor a newagent.idle_stale_mswhen the implementer wires it up.Fleet health · liverather than "Fleet health panel" — matches the labor vocabulary the spec asked for (other top-of-page labels on the dashboard readPipeline · live,Fleet · live)./usageendpoint already rolls this up (M17-2), so I echoed the percentage in the cost-tile footer rather than leaving it orphaned in/usage; low-effort to wire, high signal when the spike line says "ETA exhausted Sat 18:20".Not rendered
export_frame_pngfor each page). The tool is not exposed to the MCP on this Penpot build — I committed each frame first and parent_id-nested children, so the frame-fill race is avoided, but a reviewer opening the file directly should eyeball for any late-binding text clipping.mcp__penpot__import_tokens_dtcg+create_color_tokenboth 400 on this Penpot build with:add-token-set/:add-tokeninvalid-dispatch-value — the token change-ops aren't recognised. All values in the mockup are raw hex that matchdesign/tokens.json1:1; downstream components should still resolve against the token names above (the low-tech visual-regression pass — every hex in code must appear indesign/tokens.json— will catch drift).Ref shape: #55 (comment)
🎨 Design review — issue #238
Reviewed by: design-reviewer
File: claude-hooks — fleet-health panel on /agents (#238) · revn 263
Note:
export_frame_pngis not wired into the MCP on this Penpot build; visual inspection is from the handoff description + token cross-reference againstdesign/tokens.json.Token taxonomy
Two hex values appear in the mockup that have no entry in
design/tokens.json. These block a clean code handoff — the implementer's low-tech visual-regression pass (every hex in code must resolve to a token) will fail without them.#2F2A1F— warn-surface — used for degraded-state tile backgrounds (pages 02, 03) andcard-idle-stalebackground (pages 04, 05). No token path. Suggested fix: addtheme-dark.color.surface-warn: "#2F2A1F"todesign/tokens.jsonbefore the impl ticket starts.#301F25— error-surface — used for critical-state tile/card backgrounds (pages 03, 04, 05). No token path. Suggested fix: addtheme-dark.color.surface-error: "#301F25"todesign/tokens.json.The designer proactively called these out in the handoff ("worth lifting to
color/surface-warnandcolor/surface-errorwhen this lands in code, but not yet tokenised in the source of truth"). They need to land indesign/tokens.jsonas a prerequisite for the impl ticket so the token-regression check has something to validate against.All other hex values in the handoff map 1:1 to existing tokens:
#1A1B26theme-dark.color.bg#24283Btheme-dark.color.surface#2F3549theme-dark.color.surface-high#414868theme-dark.color.border#C0CAF5theme-dark.color.text-primary#9AA5CEtheme-dark.color.text-muted#565F89theme-dark.color.text-dim#7AA2F7theme-dark.color.accent/theme-dark.color.role-boss#9ECE6Atheme-dark.color.success#E0AF68theme-dark.color.warning/theme-dark.color.role-reviewer#F7768Etheme-dark.color.error/theme-dark.color.role-designer#7DCFFFtheme-dark.color.info/theme-dark.color.role-foreman#BB9AF7theme-dark.color.role-devLayout vs. spec
The design is well-executed and internally consistent. A few observations for the implementer:
reviewer-securitycritical card anddesigner-defaultidle-stale card give the impl ticket concrete example states to drive against.<circle>stroke-dasharray), it should not require a new design pass — the spec explicitly offered "donut / bar" as options.<polyline>; the block-char shapes carry no semantic geometry beyond "goes up" or "goes down".container_missingbanner: Banner-weight is appropriate for a page-critical operator-action state and is consistent with the M19-6 stalled-row pattern.Reconcile nowandWatchdog log →quick-actions are clearly placed below the strip.Accessibility
All status conditions surface text labels alongside color (HEALTHY / DEGRADED / CRITICAL pills), so they are not color-signal-only. No regressions introduced.
Contrast spot-checks on the two new untokenised surface tints:
#E0AF68(warning text) on#2F2A1F(warn-surface): ~7.3:1 ✓ (AAA)#F7768E(error text) on#301F25(error-surface): ~6.0:1 ✓ (AA)Verdict
Two missing tokens block a clean code handoff. The mockup itself is high quality — five well-differentiated states, correct use of the Tokyo Night Storm palette, layout consistent with the dashboard's existing patterns. Adding
theme-dark.color.surface-warn(#2F2A1F) andtheme-dark.color.surface-error(#301F25) todesign/tokens.jsonresolves the blocker; no mockup edits required. Once those two tokens land in the JSON file, this design is ready for the implementation ticket.