6.0 KiB
6.0 KiB
id, title, status, wave, depends_on, plan, spec
| id | title | status | wave | depends_on | plan | spec | |
|---|---|---|---|---|---|---|---|
| 02-message-actions-ui | Render duration in message action row | done | 2 |
|
plan.md | ../../specs/ui/requirements/prompt-turn-duration.md |
Task 02: Render duration in message action row
- Acceptance:
MessageActionsinapps/web/components/task/chat/messages/message-actions.tsxrenders, for a user prompt whose turn is completed, adata-testid="message-turn-duration"span:IconHourglass(h-3 w-3,aria-hidden) +formatPromptDuration(seconds, { s/m/h })with the existingtask:durationUnitSeconds|Minutes|Hourskeys, styled like the row's other meta (inline-flex items-center gap-1 text-[10px] text-muted-foreground/60 font-mono) PLUSwhitespace-nowrap shrink-0so a multi-unit duration (e.g.5m 23s) never wraps or compresses on narrow widths, placed after<MessageMetaInfo>. The render guard MUST bedurationSeconds !== null— never a truthiness check like{durationSeconds && …}, which would silently omit the required0s(0 is falsy).- No duration renders for running turns, agent messages, or messages without a resolvable completed turn (no placeholder).
- Component tests in
apps/web/components/task/chat/messages/message-actions.test.tsxcover: completed user prompt shows hourglass + formatted duration; running turn shows none; agent message shows none; AND a completed turn whosecompleted_atis before or within <1s ofcreated_atrenders the span with the localized0stext (catches a truthiness-guard regression that drops 0). Assert the hourglass by scoping to the duration element —getByTestId("message-turn-duration").querySelector("svg")witharia-hiddenandh-3 w-3— never a barecontainer.querySelector("svg")(the row already renders other SVGs). - A DETERMINISTIC multi-unit case: a user message whose turn completed 323 seconds after
created_atrenders the5m 23stext (localized units) and the span carries thewhitespace-nowrapandshrink-0classes — this is the ONLY deterministic no-wrap proof, because E2E wall-clock durations are uncontrolled and normally sub-minute.
- Verification:
cd apps/web && pnpm vitest run components/task/chat/messages/message-actions.test.tsx lib/prompt-history.test.ts cd apps/web && pnpm exec eslint components/task/chat/messages/message-actions.tsx components/task/chat/messages/message-actions.test.tsx lib/prompt-history.ts lib/prompt-history.test.ts cd apps/web && pnpm run typecheck - Files likely touched:
apps/web/components/task/chat/messages/message-actions.tsxapps/web/components/task/chat/messages/message-actions.test.tsx
- Dependencies: task 01 (helper must exist).
- Parallelism: sequential.
- Inputs:
- Spec:
docs/specs/ui/requirements/prompt-turn-duration.md—WhatandScenarios. - Plan:
docs/plans/prompt-turn-duration/plan.md— Frontend "Component", Tests table. - Patterns:
PromptDurationinapps/web/components/task/prompt-history-panel-content.tsx(hourglass +formatPromptDurationwitht()unit keys); existingMessageMetaInfostyling inmessage-actions.tsx. - The store already resolves
turnviauseMessageTurnAndUsage. To seed test turns, capture the store withuseAppStoreApi()(from@/components/state-provider;useAppStoreis a hook withoutgetState) inside a seeding wrapper component rendered withinStateProvider(or viarenderHook), then callapi.getState().addTurn(turn)insideact().addTurnis a ROOT-level store action (seeapps/web/lib/state/slices/session/turn-actions.tsand its use inturn-actions.test.ts) — neverturns.addTurn. TheassistantMessage()helper in the test file isauthor_type: "agent"; add a user-message factory withturn_id. - Mobile parity (required, not optional): the duration is one more inline-flex span in the existing action row — it must not change the row's composition in a way that clips or wraps on narrow widths, must not add a scroll container, and must keep the row reachable for coarse pointers at every width. Fine-pointer layouts retain the existing
group-hover/focus-withinreveal. Run/mobile-paritybefore finishing and record the rendered mobile verification in## Results; the mobile E2E scenario lives in task-03.
- Spec:
- Output contract: summary; files changed; tests run with counts; eslint/typecheck results; blockers/risks; update task status and
plan.mdcheckbox.
Results
- TDD red:
cd apps/web && pnpm vitest run components/task/chat/messages/message-actions.test.tsx lib/prompt-history.test.tsfailed as expected: completed and0sduration spans were absent. - Verification:
cd apps/web && pnpm vitest run components/task/chat/messages/message-actions.test.tsx lib/prompt-history.test.tspassed: 2 files, 50 tests. - Verification:
cd apps/web && pnpm exec eslint components/task/chat/messages/message-actions.tsx components/task/chat/messages/message-actions.test.tsx lib/prompt-history.ts lib/prompt-history.test.tspassed with no warnings. - Verification:
cd apps/web && pnpm run typecheckpassed. - Mobile parity: content-only inline addition to the existing action row; nearest mobile exemplar is
mobile-prompt-history-panel.spec.ts. The existing below-smopacity-100reveal, scroll owner, and touch behavior are unchanged. Rendered Pixel 5 verification passed withcd apps/web && pnpm e2e:raw tests/task/mobile-prompt-turn-duration.spec.ts --project=mobile-chrome: the duration was visible, opaque, unwrapped, and contained in its action row. - Generated artifacts: release-notes/changelog pretypecheck output only; no generated source artifacts to remove.
- Fixup verification:
cd apps/web && pnpm vitest run components/task/chat/messages/message-actions.test.tsx lib/prompt-history.test.ts— passed: 2 files, 51 tests. The new regression confirms the action row stays visible for coarse pointers at tablet widths. - Fixup verification: focused ESLint,
cd apps/web && pnpm run typecheck, andcd apps/web && pnpm run i18n:check— passed.