feat(web): add <TimestampLabel> shared atom to unify timestamp rendering #1184
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1184
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/1161-timestamp-label"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #1161.
Summary
<TimestampLabel ts={number} showRelative? />incomponents/timestamp-label.tsxHH:MM:SSlocal time via the existingfmtTime()helper<Tooltip>on hovershowRelativeis set the tooltip appends a relative age suffix e.g. "14:23:01 · 3m ago" viafmtAgo()fmtTime()inlib/format.tskept unchanged (non-React callers)Adoption
event-log.tsxfmtTime(ev.ts)span →<TimestampLabel ts={ev.ts} />task-list.tsx<TimestampLabel ts={task.started_at} showRelative />run-header-meters.tsxstarted_atprop; renders<TimestampLabel>chip in metrics barsession-scrubber.tsxfmtTime(tick.ts)in hover popover →<TimestampLabel ts={tick.ts} />roster/instance-history-drawer.tsxfmtRelative()call →<TimestampLabel ts={e.at} showRelative />; exportedfmtRelativekept for existing testsTest plan
just qapasses (typecheck + lint + format + tests)HH:MM:SS; hovering shows ISO-8601 tooltipHH:MM:SSwith ISO tooltip on hoverReview —
<TimestampLabel>shared atom (PR #1184)Note: There is an earlier competing implementation for the same issue in PR #1181 (CSS
group-hover/<time>element approach). Both are open. One of them needs to be closed before the other merges. This review is for #1184.The idea is sound and the adoption across five components is consistent. Three issues need addressing before merge.
🔴 Critical — tooltip content is wrong when
showRelative=trueWhen
showRelative=truethe tooltip shows e.g."14:23:01 · 3m ago". The problem:14:23:01is already the label text visible without hovering. The hover tooltip repeats the absolute time and adds a relative suffix — but the full ISO-8601 datetime is never reachable whenshowRelative=true. Users who hover to get a precise, copy-pasteable timestamp for debugging get nothing useful.Fix — always expose ISO-8601, append relative when
showRelative:This also makes both modes symmetric: the tooltip is always the authoritative absolute value.
🔴 Should use
<time dateTime={iso}>not<span>PR #1181 (the other open attempt) uses
<time dateTime={iso} title={iso}>. That is the correct semantic element for machine-readable datetime — screen readers announce it with the display text, parsers index it, and native browser tooltips expose the ISO value for free even without JS-powered tooltips.<span>is a regression. The<Tooltip>content rendered via Base UI portals is typically not exposed to ARIA unless explicitly wired, so assistive technology may get no datetime information at all.<time>is an inline element; it slots into the Tooltip trigger naturally via Base UI'srenderprop mechanism (same as<span>).🟡
task-list.tsx— silently drops the "running…" status textBefore this PR, task rows showed:
dev · 5m 23s(duration)dev · running…dev · —After this PR, all states show
dev · 14:23:01 · 3m ago(start time + relative age). The "running…" indicator is gone with no discussion. The duration on finished tasks is also gone — replaced by the start time, which is less scannable for "how long did this take?"If the intent is to replace elapsed-duration display with start-time display, that should be called out explicitly in the PR summary as a deliberate UX tradeoff. Right now it reads as an accidental side-effect of the refactor. Consider at minimum keeping "running…" for the active-task state.
🟡 No tests — all test-plan items unchecked
PR #1182 (
<TurnMeter>) shipped withturn-meter.test.tsx(13 tests covering all size variants, thenull/undefined/numbercost branches, and color-tier escalation).<TimestampLabel>is simpler but the tooltip-content logic, theshowRelativebranch, and thefmtTime(0) → "—"edge case should have coverage. A basic Vitest browser-mode test file would suffice.🟢 What's good
classNameprop is the right ergonomic call — it removes the wrapper<span>at most callsites (event-log, session-scrubber) and keeps JSX clean.run-header-meters.tsxaddsstarted_atas optional (started_at?: numberin the intersection) rather than required — backward-compatible, good.== null(catches bothnullandundefined) — correct.delay={300}is a deliberate, sensible reduction from the 500 ms default for info-dense rows.fmtAgo/fmtTimesplit (React callers vs. non-React callers) is correctly documented and respected.Review —
<TimestampLabel>shared atomThe new component itself is clean — semantic
<time dateTime>, locale-aware viafmtTime(), tooltip on hover. Two functional issues need fixing before merge, plus one missing test.❌ 1.
task-list.tsxremoves elapsed duration for completed tasks (regression)File:
apps/web/src/components/task-list.tsxThe
elapsedvariable was the primary at-a-glance metric for history-tab rows:Replacing it wholesale with
<TimestampLabel ts={task.started_at} showRelative />means a completed task now shows"dev · 14:23:01"(when it started) instead of"dev · 2m 45s"(how long it took). For the history tab, elapsed duration is the more useful signal — start time doesn't tell you whether the task ran for 30 seconds or 3 hours.Running and queued rows are arguably improved by the change. Please restore elapsed duration for completed tasks. The simplest fix keeps both signals — elapsed for
finished_at != null, start-time label otherwise:❌ 2.
showRelativetooltip drops the ISO-8601 stringFile:
apps/web/src/components/timestamp-label.tsx, line 25When
showRelative=truethe tooltip shows"14:23:01 · 3m ago"— the ISO timestamp is gone. The top-of-file docstring even promises "full ISO-8601 datetime shown in a<Tooltip>on hover" for all modes. A user hovering on a start-time chip in the run-header to copy the exact timestamp gets short-changed.Prefer:
Or if you intentionally want the short clock in the tooltip:
Either way, ISO should always be present — update the JSDoc to match whatever you decide.
⚠️ 3. No test for the new component
<TimestampLabel>is a shared atom that will be reached from multiple callsites. The codebase uses Vitest browser mode for component tests; a minimal test would cover the two render paths:<TimestampLabel ts={ts} />— body rendersHH:MM:SS, tooltip contains the ISO string.<TimestampLabel ts={ts} showRelative />— tooltip contains the relative suffix.The existing
task-list.test.tsxdoesn't assert on.ch-durationcontent, so nothing will catch a future regression on this component. Please addapps/web/src/components/timestamp-label.test.tsx.🔍 Minor / non-blocking
delay={300}on the Tooltip — the design-system default is 500 ms (tooltip.tsxline 60). Using 300 ms is fine for dense timestamp displays but worth a one-line comment so the next reader knows it's intentional, not an off-by-one from the constant.fmtTime(0)edge case —fmtTime(0)returns"—"(!0is truthy), butnew Date(0).toISOString()produces the epoch string, so<time dateTime="1970-01-01T00:00:00.000Z">—</time>is technically inconsistent. Practicallyts=0shouldn't appear in real data, but if you want to be defensive, guard both:const iso = ts ? new Date(ts).toISOString() : "";.Summary: fix the
task-listregression (#1) and the tooltip ISO omission (#2), add a component test (#3), then this is good to merge.37fb6de36f4f70ba4dd4