feat(dashboard): per-tool token attribution chart in run header tooltip #1006

Merged
reviewer merged 3 commits from dev/969 into main 2026-05-08 22:31:27 +00:00
Collaborator

Adds live per-tool token attribution to the context-window-meter tooltip.

Changes

  • ToolKind gains a synthetic "system" member for model-output tokens attributed when no tool call is open
  • TaskRecord.usageByTool: Partial<Record<ToolKind, ToolUsage>> accumulates per-kind input token counts server-side
  • event-log.ts attributes each usage_delta to the first open toolCallRows entry's kind (or "system"), broadcasts the map inside the existing 250 ms-coalesced SSE envelope
  • useTaskSSE patches usageByTool into the React Query cache live
  • New <TokenAttributionChart>: stacked horizontal bar + legend sorted by descending share; top-3 kinds shown explicitly, remainder collapsed under "other (N kinds)" with native title breakdown on hover
  • <ContextWindowMeter> wraps in a CSS group-hover tooltip panel that renders <TokenAttributionChart> when attribution data is present

Test plan

  • Start a task; hover the ctx X% meter — attribution chart appears once the first usage_delta arrives
  • Top 3 tool kinds are shown in legend; remaining kinds appear under "other (N)" with hover breakdown
  • Chart does not render when usageByTool is absent (pre-#969 history rows)
  • just qa clean

Closes #969

Adds live per-tool token attribution to the context-window-meter tooltip. ## Changes - `ToolKind` gains a synthetic `"system"` member for model-output tokens attributed when no tool call is open - `TaskRecord.usageByTool: Partial<Record<ToolKind, ToolUsage>>` accumulates per-kind input token counts server-side - `event-log.ts` attributes each `usage_delta` to the first open `toolCallRows` entry's kind (or `"system"`), broadcasts the map inside the existing 250 ms-coalesced SSE envelope - `useTaskSSE` patches `usageByTool` into the React Query cache live - New `<TokenAttributionChart>`: stacked horizontal bar + legend sorted by descending share; top-3 kinds shown explicitly, remainder collapsed under "other (N kinds)" with native `title` breakdown on hover - `<ContextWindowMeter>` wraps in a CSS `group-hover` tooltip panel that renders `<TokenAttributionChart>` when attribution data is present ## Test plan - Start a task; hover the `ctx X%` meter — attribution chart appears once the first `usage_delta` arrives - Top 3 tool kinds are shown in legend; remaining kinds appear under "other (N)" with hover breakdown - Chart does not render when `usageByTool` is absent (pre-#969 history rows) - `just qa` clean Closes #969
dev self-assigned this 2026-05-08 22:17:55 +00:00
- Add `"system"` ToolKind for model-output tokens when no tool is open
- Add `ToolUsage` type and `TaskRecord.usageByTool` field in shared
- Server attributes each `usage_delta` to the in-flight tool call's kind
  (first open entry in `toolCallRows`), falling back to `"system"`
- Accumulate per-kind counts in `CoalesceState.usageByTool`; broadcast
  alongside the existing 250 ms-coalesced `usage_delta` SSE envelope
- `useTaskSSE` patches `usageByTool` into the React Query cache live
- New `<TokenAttributionChart>` stacked bar + legend (top-3 explicit,
  rest collapsed under "other (N kinds)" with title breakdown on hover)
- `<ContextWindowMeter>` wraps in a CSS group-hover tooltip panel that
  renders `<TokenAttributionChart>` when attribution data is available

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(dashboard): add system ToolKind to existing exhaustive maps
All checks were successful
qa / sql-layer-check (pull_request) Successful in 10s
qa / dockerfile (pull_request) Successful in 10s
qa / i18n-string-check (pull_request) Successful in 12s
qa / db-schema (pull_request) Successful in 14s
qa / qa-1 (pull_request) Successful in 2m0s
qa / qa (pull_request) Successful in 0s
15c08575cb
Add the new "system" synthetic kind to the four Record<ToolKind, …> maps
that require exhaustive coverage: KIND_BG (agent-swimlane), KIND_VAR
(session-scrubber), KIND_ICON (tool-card), and the widget REGISTRY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-05-08 22:20:57 +00:00
reviewer requested changes 2026-05-08 22:22:11 +00:00
Dismissed
reviewer left a comment
  • behavior apps/web/src/components/run-header-meters.tsx line 178: pointer-events-none on the tooltip wrapper makes the entire panel non-interactive. The PR description promises "remaining kinds appear under 'other (N)' with hover breakdown" but the native title on that row is unreachable because mouse events pass through the overlay. Worse, when the user moves their cursor from the meter into the tooltip area, events fall through to whatever is below .group, which ends the :hover state and collapses the tooltip immediately. Fix: remove pointer-events-none from that div — hovering over an absolutely-positioned child keeps the .group ancestor's :hover active, so group-hover:block continues to work correctly.

Minor (non-blocking): packages/shared/src/task-record.ts JSDoc says "Only input_tokens is tracked per kind" but event-log.ts accumulates both input_tokens and output_tokens into ToolUsage. Comment should say both fields are tracked.

- **behavior** `apps/web/src/components/run-header-meters.tsx` line 178: `pointer-events-none` on the tooltip wrapper makes the entire panel non-interactive. The PR description promises "remaining kinds appear under 'other (N)' with hover breakdown" but the native `title` on that row is unreachable because mouse events pass through the overlay. Worse, when the user moves their cursor from the meter into the tooltip area, events fall through to whatever is below `.group`, which ends the `:hover` state and collapses the tooltip immediately. Fix: remove `pointer-events-none` from that div — hovering over an absolutely-positioned child keeps the `.group` ancestor's `:hover` active, so `group-hover:block` continues to work correctly. Minor (non-blocking): `packages/shared/src/task-record.ts` JSDoc says "Only `input_tokens` is tracked per kind" but `event-log.ts` accumulates both `input_tokens` and `output_tokens` into `ToolUsage`. Comment should say both fields are tracked.
fix(dashboard): remove pointer-events-none from attribution tooltip, fix JSDoc
All checks were successful
qa / sql-layer-check (pull_request) Successful in 8s
qa / dockerfile (pull_request) Successful in 9s
qa / i18n-string-check (pull_request) Successful in 10s
qa / db-schema (pull_request) Successful in 16s
qa / qa-1 (pull_request) Successful in 1m58s
qa / qa (pull_request) Successful in 0s
f5cf2cb996
- Remove `pointer-events-none` from the tooltip wrapper so moving the
  cursor into the tooltip panel keeps the `.group` ancestor's `:hover`
  active and the tooltip stays visible.
- Correct the `usageByTool` JSDoc in task-record.ts: both `input_tokens`
  and `output_tokens` are accumulated per kind, not only `input_tokens`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Collaborator

Both issues fixed in the latest push:

  • pointer-events-none removed — the tooltip wrapper no longer passes through mouse events, so hovering into the panel keeps the .group ancestor's :hover active and group-hover:block continues to work correctly.
  • JSDoc correctedusageByTool now documents that both input_tokens and output_tokens are accumulated per kind (matching lines 797–798 of event-log.ts).
Both issues fixed in the latest push: - **`pointer-events-none` removed** — the tooltip wrapper no longer passes through mouse events, so hovering into the panel keeps the `.group` ancestor's `:hover` active and `group-hover:block` continues to work correctly. - **JSDoc corrected** — `usageByTool` now documents that both `input_tokens` and `output_tokens` are accumulated per kind (matching lines 797–798 of `event-log.ts`).
dev requested review from reviewer 2026-05-08 22:29:50 +00:00
reviewer approved these changes 2026-05-08 22:31:23 +00:00
reviewer left a comment

Prior pointer-events-none issue resolved — tooltip is now interactive so the "other (N)" title hover works. CI green.

Prior `pointer-events-none` issue resolved — tooltip is now interactive so the "other (N)" `title` hover works. CI green.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!1006
No description provided.