dashboard: generic <ToolCard> — header/pill/duration/caret, errors+approvals open, successes collapsed #960

Closed
opened 2026-05-08 12:14:17 +00:00 by claude-desktop · 3 comments
Collaborator

User story

As a frontend engineer, I want a single <ToolCard> primitive every tool widget composes into, so error/approval cards stay open by default while plain successes auto-collapse to a one-liner — matching the Vercel AI Elements Tool ergonomics.

Acceptance criteria

  • <ToolCard> in apps/web/src/components/agent/tool-card.tsx. Composition:
    • <ToolCardHeader> — tool icon (lucide; chosen by ToolKind), tool name, target chip (file path / URL / command head), <StatusPill> driven by the SSE state enum, live duration counter, caret toggle.
    • <ToolCardBody> — collapsible. Slots: inputView and outputView.
  • Default-collapsed when state === "output-available" and no error.
  • Default-open when state is output-error, approval-requested, or output-denied.
  • Streaming spinner inside the pill while state is input-streaming or input-available.
  • Honour prefers-reduced-motion — no caret rotation animation when set.
  • Tokyo Night palette: rounded-card, border border-border, shadow-none, hover state shadow-subtle. Status pill is rounded-pill.
  • No raw <button> for the caret — use existing <Button> primitive with tone="ghost".
  • Vitest + react-testing-library snapshot per state, plus a click-to-toggle interaction test.

Out of scope

  • Per-tool input/output renderers (one issue per ToolKind widget).
  • The streaming markdown text outside tool cards (Streamdown ticket).

Dependencies

  • Depends on previous ticket (state enum on wire).

References

## User story As a frontend engineer, I want a single `<ToolCard>` primitive every tool widget composes into, so error/approval cards stay open by default while plain successes auto-collapse to a one-liner — matching the Vercel AI Elements `Tool` ergonomics. ## Acceptance criteria - [ ] `<ToolCard>` in `apps/web/src/components/agent/tool-card.tsx`. Composition: - `<ToolCardHeader>` — tool icon (lucide; chosen by `ToolKind`), tool name, target chip (file path / URL / command head), `<StatusPill>` driven by the SSE `state` enum, live duration counter, caret toggle. - `<ToolCardBody>` — collapsible. Slots: `inputView` and `outputView`. - [ ] Default-collapsed when `state === "output-available"` and no error. - [ ] Default-open when `state` is `output-error`, `approval-requested`, or `output-denied`. - [ ] Streaming spinner inside the pill while `state` is `input-streaming` or `input-available`. - [ ] Honour `prefers-reduced-motion` — no caret rotation animation when set. - [ ] Tokyo Night palette: `rounded-card`, `border border-border`, `shadow-none`, hover state `shadow-subtle`. Status pill is `rounded-pill`. - [ ] No raw `<button>` for the caret — use existing `<Button>` primitive with `tone="ghost"`. - [ ] Vitest + react-testing-library snapshot per state, plus a click-to-toggle interaction test. ## Out of scope - Per-tool input/output renderers (one issue per `ToolKind` widget). - The streaming markdown text outside tool cards (Streamdown ticket). ## Dependencies - Depends on previous ticket (state enum on wire). ## References - AI Elements `<Tool>`: https://elements.ai-sdk.dev/components/tool — start by copying its JSX, repaint to Tokyo Night, replace icons with lucide. - Foundation primitives in `apps/web/CLAUDE.md`.
Collaborator

🤖 Auto-assigned to dev (heuristic: area:dashboard + body 1763 bytes (≤ 2 KB) — code role). Reply /unassign to reroute.

🤖 Auto-assigned to **dev** (heuristic: area:dashboard + body 1763 bytes (≤ 2 KB) — code role). Reply `/unassign` to reroute.
Collaborator

🧹 janitor: this ticket has been idle-assigned since 2026-05-08T18:26:12.000Z. Re-dispatching.

🧹 janitor: this ticket has been idle-assigned since 2026-05-08T18:26:12.000Z. Re-dispatching.
Collaborator

🦵 @charles kicked the queue — re-running implement on @dev.

🦵 @charles kicked the queue — re-running implement on @dev.
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#960
No description provided.