design(agents): Penpot mockup — fleet-health panel on /agents #238

Closed
opened 2026-04-21 12:50:14 +00:00 by claude-desktop · 2 comments
Collaborator

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

  • Header strip sitting above the existing agent cards. Four tiles:
    1. Fleet saturation — 6 / 12 busy with a donut / bar.
    2. Queue depth — total + per-type breakdown.
    3. Cost burn — USD/hr rolling window (last 15 min), with a 24 h spark.
    4. Last activity age — "newest dispatch 12 s ago" / "oldest idle 8 min ago".
  • Per-agent card update — add a last-activity timestamp + per-agent 24 h cost sparkline.
  • Healthy / degraded / critical states — visual treatment when saturation > 80% for > 2 min, when cost burn spikes, when an agent's container is missing (reuse the watchdog's container_missing signal).

Tokens + copy

  • Colors from design/tokens.json.
  • Copy uses the labor vocabulary.

Handoff

  • Standard handoff comment, area:design-review on handoff.

Out of scope

  • Multi-fleet view (if we ever run multiple instances).
  • Agent-level cost caps UI — covered by the token-economy ticket.

References

  • apps/server/src/main.tsworkers registry, agent state snapshot.
  • Sibling impl ticket — blocks on this.
## 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 - [ ] **Header strip** sitting above the existing agent cards. Four tiles: 1. Fleet saturation — `6 / 12 busy` with a donut / bar. 2. Queue depth — total + per-type breakdown. 3. Cost burn — USD/hr rolling window (last 15 min), with a 24 h spark. 4. Last activity age — "newest dispatch 12 s ago" / "oldest idle 8 min ago". - [ ] **Per-agent card update** — add a last-activity timestamp + per-agent 24 h cost sparkline. - [ ] **Healthy / degraded / critical states** — visual treatment when saturation > 80% for > 2 min, when cost burn spikes, when an agent's container is missing (reuse the watchdog's `container_missing` signal). ### Tokens + copy - [ ] Colors from `design/tokens.json`. - [ ] Copy uses the labor vocabulary. ### Handoff - [ ] Standard handoff comment, `area:design-review` on handoff. ## Out of scope - Multi-fleet view (if we ever run multiple instances). - Agent-level cost caps UI — covered by the token-economy ticket. ## References - `apps/server/src/main.ts` — `workers` registry, agent state snapshot. - Sibling impl ticket — blocks on this.
Collaborator

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 /agents layout so saturation (6/12) reads against the real grid.

Pages

Page Purpose Frames Key components
01 · Fleet-health strip — healthy Nominal state: saturation ≤ 80 %, burn within 24 h band, no missing containers bg-canvas, 4× tile-*, sat-bar-track/-fill, pill-status-healthy donut/bar saturation (6/12 busy), queue breakdown by type, USD/hr + 24 h spark, newest/oldest activity age
02 · Fleet-health strip — degraded Saturation > 80 % for > 2 min OR burn > 2σ — amber tiles only bg-canvas, 4× tile-* (2 in warn surface), sat-bar-threshold, pill-status-degraded red 80 % threshold tick on saturation bar, amber sparkline end, queue shows dev=8/reviewer=4 backlog
03 · Fleet-health strip — critical container_missing wired to the watchdog signal — red state + persistent banner banner-container-missing, banner-btn-reconcile, banner-btn-logs, 3× warn + 1× error tile red banner below the strip showing which agent vanished, inline Reconcile / Watchdog-log actions, capacity drops 12 → 11
04 · Per-agent card redesign Blown-up card in 3 states — healthy/busy, idle-stale, critical card-healthy, card-idle-stale, card-critical new LAST ACTIVITY row (relative age + event), new 24 H COST row (sparkline + now/avg), existing current-task + actions unchanged
05 · /agents full composition End-to-end page: top nav + compact strip + 4×2 grid of agent cards top-nav, compact tile-*, 8× card-* one reviewer-security card in critical, one designer-default in idle-stale, six in healthy states — saturation math lines up with the strip tile

Tokens 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 to color/surface-warn and color/surface-error when this lands in code, but not yet tokenised in the source of truth.

/* surfaces */
--color-bg:           #1A1B26;  /* theme-dark.color.bg — page ground */
--color-surface:      #24283B;  /* theme-dark.color.surface — tile, card */
--color-surface-high: #2F3549;  /* theme-dark.color.surface-high — hover (not rendered in mockup) */
--color-surface-warn: #2F2A1F;  /* derived — degraded tile / idle-stale card bg */
--color-surface-err:  #301F25;  /* derived — critical tile / critical card bg */
--color-border:       #414868;  /* saturation bar track */

/* text */
--color-text-primary: #C0CAF5;
--color-text-muted:   #9AA5CE;
--color-text-dim:     #565F89;

/* semantic */
--color-accent:       #7AA2F7;  /* sparkline fill (neutral / boss role) */
--color-success:      #9ECE6A;  /* HEALTHY pill, saturation fill under threshold */
--color-warning:      #E0AF68;  /* DEGRADED pill, > 80 % fill, idle-stale */
--color-error:        #F7768E;  /* CRITICAL pill, container_missing, threshold tick */
--color-info:         #7DCFFF;  /* reserved for foreman role — unused on /agents */

/* role palette (per-agent card role-dot + type-pill text) */
--color-role-dev:      #BB9AF7;
--color-role-boss:     #7AA2F7;
--color-role-reviewer: #E0AF68;
--color-role-designer: #F7768E;

/* geometry */
--radius-card: 8px;
--radius-pill: 11px;
--space-16:    16px;   /* tile inner padding */
--space-24:    24px;   /* card inner padding */

/* type */
--font-mono:   "Fira Code", "SF Mono", "Cascadia Code", monospace;
--font-size-display: 24px;  /* tile primary metric */
--font-size-h1:      20px;
--font-size-h2:      18px;
--font-size-body:    13px;
--font-size-small:   12px;
--font-size-meta:    11px;
--font-size-caption: 10px;

Decisions that deviated from the spec

  • Donut → horizontal bar for saturation. Spec said "donut / bar". Penpot's MCP can only render axis-aligned rectangles, so I went with the bar form — also reads better at the tile size (288 × 8). A true donut with an inner numeric readout is trivial in React + SVG if the implementer prefers; I kept 6 / 12 busy at display size so the numeric is still the dominant element.
  • Sparkline rendered as Unicode block characters (▁▂▃…█). 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.
  • Newest/oldest shown side-by-side, not two tiles. Spec tile 4 said "newest 12 s ago / oldest idle 8 min ago" — I fit both into the same tile with ▲/▼ glyphs rather than splitting into a 5th tile, to keep the strip at four tiles as specified. Each half has its own subtitle (which agent).
  • container_missing rendered 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 the Reconcile 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.
  • Added "IDLE-STALE" as a named state. Not explicitly in the spec, but the "oldest idle 8 min ago" acceptance line implies the UI needs to flag long-idle agents — otherwise the tile-4 number is descriptive only, not actionable. I treated stale idle (> 15 min, hard-stall at > 30 min) as a warning state on the per-agent card (page 4 card 2, page 5 designer-default), same amber visual as degraded. Tune the threshold via pipeline.implement_threshold_ms or a new agent.idle_stale_ms when the implementer wires it up.
  • Strip title copy = Fleet health · live rather than "Fleet health panel" — matches the labor vocabulary the spec asked for (other top-of-page labels on the dashboard read Pipeline · live, Fleet · live).
  • Weekly Pro-Max quota mentioned in the cost tile caption. The spec stayed silent on where that number surfaces. The /usage endpoint 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

  • Penpot PNG sanity-check exports (export_frame_png for 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.
  • Token import via DTCG. mcp__penpot__import_tokens_dtcg + create_color_token both 400 on this Penpot build with :add-token-set / :add-token invalid-dispatch-value — the token change-ops aren't recognised. All values in the mockup are raw hex that match design/tokens.json 1:1; downstream components should still resolve against the token names above (the low-tech visual-regression pass — every hex in code must appear in design/tokens.json — will catch drift).

Ref shape: #55 (comment)

## Design handoff — fleet-health panel on /agents Penpot file: **[claude-hooks — fleet-health panel on /agents (#238)](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289)** 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 `/agents` layout so saturation (6/12) reads against the real grid. ### Pages | Page | Purpose | Frames | Key components | |---|---|---|---| | [01 · Fleet-health strip — healthy](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289&page-id=f181261c-b6f6-4609-9f47-ff81fc255d1d) | Nominal state: saturation ≤ 80 %, burn within 24 h band, no missing containers | `bg-canvas`, 4× `tile-*`, `sat-bar-track`/`-fill`, `pill-status-healthy` | donut/bar saturation (6/12 busy), queue breakdown by type, USD/hr + 24 h spark, newest/oldest activity age | | [02 · Fleet-health strip — degraded](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289&page-id=37a404b7-9e6f-48da-bb34-c23836a337f4) | Saturation > 80 % for > 2 min OR burn > 2σ — amber tiles only | `bg-canvas`, 4× `tile-*` (2 in warn surface), `sat-bar-threshold`, `pill-status-degraded` | red 80 % threshold tick on saturation bar, amber sparkline end, queue shows dev=8/reviewer=4 backlog | | [03 · Fleet-health strip — critical](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289&page-id=1133e461-fc17-423f-8751-303fe3fd4e8c) | `container_missing` wired to the watchdog signal — red state + persistent banner | `banner-container-missing`, `banner-btn-reconcile`, `banner-btn-logs`, 3× warn + 1× error tile | red banner below the strip showing which agent vanished, inline Reconcile / Watchdog-log actions, capacity drops 12 → 11 | | [04 · Per-agent card redesign](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289&page-id=48161c8f-369f-4f86-b041-81520edaacc4) | Blown-up card in 3 states — healthy/busy, idle-stale, critical | `card-healthy`, `card-idle-stale`, `card-critical` | new `LAST ACTIVITY` row (relative age + event), new `24 H COST` row (sparkline + now/avg), existing current-task + actions unchanged | | [05 · /agents full composition](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289&page-id=c4d4716d-65f9-45ad-81ab-f4c9936c3b39) | End-to-end page: top nav + compact strip + 4×2 grid of agent cards | `top-nav`, compact `tile-*`, 8× `card-*` | one `reviewer-security` card in critical, one `designer-default` in idle-stale, six in healthy states — saturation math lines up with the strip tile | ### Tokens used All values from [`design/tokens.json`](https://forge.jacquin.app/charles/claude-hooks/src/branch/main/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 to `color/surface-warn` and `color/surface-error` when this lands in code, but not yet tokenised in the source of truth. ```css /* surfaces */ --color-bg: #1A1B26; /* theme-dark.color.bg — page ground */ --color-surface: #24283B; /* theme-dark.color.surface — tile, card */ --color-surface-high: #2F3549; /* theme-dark.color.surface-high — hover (not rendered in mockup) */ --color-surface-warn: #2F2A1F; /* derived — degraded tile / idle-stale card bg */ --color-surface-err: #301F25; /* derived — critical tile / critical card bg */ --color-border: #414868; /* saturation bar track */ /* text */ --color-text-primary: #C0CAF5; --color-text-muted: #9AA5CE; --color-text-dim: #565F89; /* semantic */ --color-accent: #7AA2F7; /* sparkline fill (neutral / boss role) */ --color-success: #9ECE6A; /* HEALTHY pill, saturation fill under threshold */ --color-warning: #E0AF68; /* DEGRADED pill, > 80 % fill, idle-stale */ --color-error: #F7768E; /* CRITICAL pill, container_missing, threshold tick */ --color-info: #7DCFFF; /* reserved for foreman role — unused on /agents */ /* role palette (per-agent card role-dot + type-pill text) */ --color-role-dev: #BB9AF7; --color-role-boss: #7AA2F7; --color-role-reviewer: #E0AF68; --color-role-designer: #F7768E; /* geometry */ --radius-card: 8px; --radius-pill: 11px; --space-16: 16px; /* tile inner padding */ --space-24: 24px; /* card inner padding */ /* type */ --font-mono: "Fira Code", "SF Mono", "Cascadia Code", monospace; --font-size-display: 24px; /* tile primary metric */ --font-size-h1: 20px; --font-size-h2: 18px; --font-size-body: 13px; --font-size-small: 12px; --font-size-meta: 11px; --font-size-caption: 10px; ``` ### Decisions that deviated from the spec - **Donut → horizontal bar for saturation.** Spec said "donut / bar". Penpot's MCP can only render axis-aligned rectangles, so I went with the bar form — also reads better at the tile size (288 × 8). A true donut with an inner numeric readout is trivial in React + SVG if the implementer prefers; I kept `6 / 12 busy` at display size so the numeric is still the dominant element. - **Sparkline rendered as Unicode block characters (`▁▂▃…█`).** 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. - **Newest/oldest shown side-by-side, not two tiles.** Spec tile 4 said "newest 12 s ago / oldest idle 8 min ago" — I fit both into the same tile with ▲/▼ glyphs rather than splitting into a 5th tile, to keep the strip at four tiles as specified. Each half has its own subtitle (which agent). - **`container_missing` rendered 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 the `Reconcile 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. - **Added "IDLE-STALE" as a named state.** Not explicitly in the spec, but the "oldest idle 8 min ago" acceptance line implies the UI needs to flag long-idle agents — otherwise the tile-4 number is descriptive only, not actionable. I treated stale idle (> 15 min, hard-stall at > 30 min) as a warning state on the per-agent card (page 4 card 2, page 5 `designer-default`), same amber visual as degraded. Tune the threshold via `pipeline.implement_threshold_ms` or a new `agent.idle_stale_ms` when the implementer wires it up. - **Strip title copy = `Fleet health · live`** rather than "Fleet health panel" — matches the labor vocabulary the spec asked for (other top-of-page labels on the dashboard read `Pipeline · live`, `Fleet · live`). - **Weekly Pro-Max quota mentioned in the cost tile caption.** The spec stayed silent on where that number surfaces. The `/usage` endpoint 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 - **Penpot PNG sanity-check exports** (`export_frame_png` for 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. - **Token import via DTCG.** `mcp__penpot__import_tokens_dtcg` + `create_color_token` both 400 on this Penpot build with `:add-token-set` / `:add-token` invalid-dispatch-value — the token change-ops aren't recognised. All values in the mockup are raw hex that match `design/tokens.json` 1:1; downstream components should still resolve against the token names above (the low-tech visual-regression pass — every hex in code must appear in `design/tokens.json` — will catch drift). Ref shape: https://forge.jacquin.app/charles/claude-hooks/issues/55#issuecomment-5241
Collaborator

🎨 Design review — issue #238

Reviewed by: design-reviewer
File: claude-hooks — fleet-health panel on /agents (#238) · revn 263
Note: export_frame_png is not wired into the MCP on this Penpot build; visual inspection is from the handoff description + token cross-reference against design/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) and card-idle-stale background (pages 04, 05). No token path. Suggested fix: add theme-dark.color.surface-warn: "#2F2A1F" to design/tokens.json before the impl ticket starts.
  • #301F25 — error-surface — used for critical-state tile/card backgrounds (pages 03, 04, 05). No token path. Suggested fix: add theme-dark.color.surface-error: "#301F25" to design/tokens.json.

The designer proactively called these out in the handoff ("worth lifting to color/surface-warn and color/surface-error when this lands in code, but not yet tokenised in the source of truth"). They need to land in design/tokens.json as 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:

Hex Token path
#1A1B26 theme-dark.color.bg
#24283B theme-dark.color.surface
#2F3549 theme-dark.color.surface-high
#414868 theme-dark.color.border
#C0CAF5 theme-dark.color.text-primary
#9AA5CE theme-dark.color.text-muted
#565F89 theme-dark.color.text-dim
#7AA2F7 theme-dark.color.accent / theme-dark.color.role-boss
#9ECE6A theme-dark.color.success
#E0AF68 theme-dark.color.warning / theme-dark.color.role-reviewer
#F7768E theme-dark.color.error / theme-dark.color.role-designer
#7DCFFF theme-dark.color.info / theme-dark.color.role-foreman
#BB9AF7 theme-dark.color.role-dev

Layout vs. spec

The design is well-executed and internally consistent. A few observations for the implementer:

  • Strip composition (page 05): The 4-tile strip + 4×2 agent-card grid correctly maps saturation math (6/12 = 50 %, inside threshold) to the strip's bar fill. The reviewer-security critical card and designer-default idle-stale card give the impl ticket concrete example states to drive against.
  • Donut → bar substitution: Reasonable; the bar form at 288×8 is legible at tile size. If the implementer wants a true SVG donut (<circle> stroke-dasharray), it should not require a new design pass — the spec explicitly offered "donut / bar" as options.
  • Sparkline as block chars: These are Penpot-legibility proxies only. Real implementation should use SVG <polyline>; the block-char shapes carry no semantic geometry beyond "goes up" or "goes down".
  • Activity-age tile (▲/▼ split layout): Consolidating newest + oldest into a single tile is a clean space trade. Implementers should ensure both halves degrade gracefully when the agent pool has only one member (collapse to single reading + subtitle).
  • container_missing banner: Banner-weight is appropriate for a page-critical operator-action state and is consistent with the M19-6 stalled-row pattern. Reconcile now and Watchdog 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)

Pre-existing system note (not a defect from this mockup): theme-dark.color.text-dim (#565F89) on theme-dark.color.surface (#24283B) is approximately 2.4:1 — below WCAG AA for normal-weight body text. This token pair is already in use on the dashboard; it was not introduced here. Flagged for awareness only.


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) and theme-dark.color.surface-error (#301F25) to design/tokens.json resolves the blocker; no mockup edits required. Once those two tokens land in the JSON file, this design is ready for the implementation ticket.

## 🎨 Design review — issue #238 **Reviewed by:** design-reviewer **File:** [claude-hooks — fleet-health panel on /agents (#238)](https://penpot.jacquin.app/#/workspace?team-id=41e41004-fcd0-8115-8007-cd4ef7a479dd&file-id=b6ad92e1-d51e-804e-8007-e74ecff4e289) · revn 263 **Note:** `export_frame_png` is not wired into the MCP on this Penpot build; visual inspection is from the handoff description + token cross-reference against `design/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) and `card-idle-stale` background (pages 04, 05). No token path. Suggested fix: add `theme-dark.color.surface-warn: "#2F2A1F"` to `design/tokens.json` before the impl ticket starts. - **`#301F25` — error-surface** — used for critical-state tile/card backgrounds (pages 03, 04, 05). No token path. Suggested fix: add `theme-dark.color.surface-error: "#301F25"` to `design/tokens.json`. The designer proactively called these out in the handoff ("worth lifting to `color/surface-warn` and `color/surface-error` when this lands in code, but not yet tokenised in the source of truth"). They need to land in `design/tokens.json` as 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: | Hex | Token path | |---|---| | `#1A1B26` | `theme-dark.color.bg` | | `#24283B` | `theme-dark.color.surface` | | `#2F3549` | `theme-dark.color.surface-high` | | `#414868` | `theme-dark.color.border` | | `#C0CAF5` | `theme-dark.color.text-primary` | | `#9AA5CE` | `theme-dark.color.text-muted` | | `#565F89` | `theme-dark.color.text-dim` | | `#7AA2F7` | `theme-dark.color.accent` / `theme-dark.color.role-boss` | | `#9ECE6A` | `theme-dark.color.success` | | `#E0AF68` | `theme-dark.color.warning` / `theme-dark.color.role-reviewer` | | `#F7768E` | `theme-dark.color.error` / `theme-dark.color.role-designer` | | `#7DCFFF` | `theme-dark.color.info` / `theme-dark.color.role-foreman` | | `#BB9AF7` | `theme-dark.color.role-dev` | --- ### Layout vs. spec The design is well-executed and internally consistent. A few observations for the implementer: - **Strip composition (page 05):** The 4-tile strip + 4×2 agent-card grid correctly maps saturation math (6/12 = 50 %, inside threshold) to the strip's bar fill. The `reviewer-security` critical card and `designer-default` idle-stale card give the impl ticket concrete example states to drive against. - **Donut → bar substitution:** Reasonable; the bar form at 288×8 is legible at tile size. If the implementer wants a true SVG donut (`<circle>` stroke-dasharray), it should not require a new design pass — the spec explicitly offered "donut / bar" as options. - **Sparkline as block chars:** These are Penpot-legibility proxies only. Real implementation should use SVG `<polyline>`; the block-char shapes carry no semantic geometry beyond "goes up" or "goes down". - **Activity-age tile (▲/▼ split layout):** Consolidating newest + oldest into a single tile is a clean space trade. Implementers should ensure both halves degrade gracefully when the agent pool has only one member (collapse to single reading + subtitle). - **`container_missing` banner:** Banner-weight is appropriate for a page-critical operator-action state and is consistent with the M19-6 stalled-row pattern. `Reconcile now` and `Watchdog 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) > **Pre-existing system note (not a defect from this mockup):** `theme-dark.color.text-dim` (`#565F89`) on `theme-dark.color.surface` (`#24283B`) is approximately 2.4:1 — below WCAG AA for normal-weight body text. This token pair is already in use on the dashboard; it was not introduced here. Flagged for awareness only. --- ### 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`) and `theme-dark.color.surface-error` (`#301F25`) to `design/tokens.json` resolves the blocker; no mockup edits required. Once those two tokens land in the JSON file, this design is ready for the implementation ticket.
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#238
No description provided.