feat(web,desktop): a turn's "Worked for" line hangs a card saying what it ran on, when, how it ended, and its tokens

Merged
DA
7d ago
+1243-109

What

Dean asked for useful details when hovering "Worked for X" on every tab. Hovering anywhere on the footer line under a finished turn ("Worked for 34s · Today, 2:32 PM"), whether on the duration or the timestamp, now opens a hover card. The hover target is only as wide as the text, so the empty space beside it does not open the card. It uses the shared Tooltip card mode (#2088), so it stays open while the pointer is on it and its text can be selected. The footer is still selectable text. The footer appears in three places, all through WorkedFor: Work tab turns (Luke's brain), a planning worker's subagent tab, and coding agent and coding-agent subagent tabs.

The card shows only what the turn's row actually recorded, with no placeholders:

SectionShows
HeadModel + effort via the shared ModelLabel ("Opus 5.5 High", "GPT-6.1 Sol Medium"). The harness mark appears where the catalog lists the model, same as subagent rows. Luke's own Work tab turns get no mark because they don't run in a harness. On the right, Stopped / Failed / Abandoned when the turn did not finish normally.
WhenStart → end in full, in the app's moment format ("Oct 12, 2026, 2:31 PM → 2:32 PM"). A same-day end shows only the time.
Tokens (under the card hairline)Input (input not served from cache) · Output · Cache read: muted labels on the left, values right-aligned in tabular figures. No dollar cost. Hidden when the row recorded no usage (older turns).

Left out on purpose:

  • Work done counts: the finished turn's fold header already counts its calls and subagents.
  • BYOK: nothing on a turn row records whose key it ran on.
  • Cache write: eve's step.completed reports it, but BrainRunUsage drops it before the turn row is written. Recording it is a separate small follow-up PR: an optional field summed into the existing usage jsonb, plus one more card row.

Data / wire

The turns row already records model, reasoning_effort, usage (input / output / cached / reasoning), status, and failure for brain turns and coder turns alike, so nothing new is recorded. Each new wire field is optional:

  • coding-agent-wire.ts: turnRecordFields = model, effort, tokens {input, output, cachedInput}, and ending (stopped | failed | abandoned). They are spread onto codingAgentMessageSchema, planWorkTurnSchema, and planWorkSessionSchema. The session's model key is unchanged.
  • hosted/turn-record.ts (new): turnRecordOf(row). catalogIdOf moved here from child-models.ts.
  • The server fills these fields from rows its reads already fetch: planWorkOf's turn, subagentRecordsByCall (was subagentModelsByCall), and the coder transcript's turnsNamed read. No new queries.
  • Goldens were regenerated with LUKE_UPDATE_FIXTURES=1. The only diff is the new optional keys in the four message and plan-work schemas.
  • A service older than this sends none of the new fields, so the card shows only the times.
  • Settled-turn refresh (Devin review): a turn that answers closes its journal before its row settles. That means a coding agent's page could read the journal without tokens or ending, and nothing would ever send it again. turnEnded now bumps the revision of an already-closed journal in place (reviseJournal; nothing else on the row changes), so a reader past that cursor gets it again with the settled record. The Work tab does not need this: its live follow recomputes the turn on every look.

Tests

  • working-time.test.ts (desktop, real components): hovering an agent turn's footer shows "Opus 5.5 High" with the Claude Code mark, the start → end span, and the three token rows; a turn with no usage shows no token section; stopped, failed, and abandoned turns say so; a Work tab turn shows "GPT-6.1 Sol" with no harness mark and "Stopped". Hovering the duration part and the timestamp part of the line opens the same card. The existing footer test now hovers to reach the card where it used to read title.
  • plan-work.test.ts (web, PGlite through the real relay): a finished planning turn carries its model, effort, and tokens, with no ending; cancelled and failed turns say stopped and failed; a running turn has a model but no tokens or ending yet; the stored plan work for a worker's session carries its effort, tokens, and ending.
  • coding-agents-app.test.ts: an agent's journal message carries model and effort while it is written, then tokens and stopped once the turn is cancelled. A journal read between MESSAGE_COMPLETED and TURN_ENDED is read again past that cursor, this time with its tokens.
  • hosted-store-writer.test.ts: the revision test now asserts that settling bumps the closed journal's revision (3 → 4).
  • turn-record.test.ts: abandoned versus failed; unreadable or missing usage gives no tokens; an empty effort gives none.

Screenshots (Chrome render of the real AgentTranscriptView / PlanWork, footer hovered)

LightDark
Agent tabagent lightagent dark
Work tab (stopped)work lightwork dark

Mac validation

CI could not verify the Mac UI: CI builds nothing for the Mac, and ./scripts/verify.sh was not run because this is a Linux cloud VM. LUKE-159 is the manual Mac pass. ./scripts/check.sh exits 0.

🤖 Generated with Claude Code