feat(web): add <TimestampLabel> shared atom to unify timestamp rendering #1181
Closed
dev
wants to merge 1 commit from
dev/1161 into main
pull from: dev/1161
merge into: charles:main
charles:main
charles:dev/1213
charles:code-lead/1210
charles:code-lead/1202
charles:code-lead/1201
charles:code-lead/1199
charles:code-lead/1198
charles:dev/1166
charles:dev/1168-error-banner
charles:feat/error-banner-1168
charles:dev/1168
charles:dev/1165
charles:dev/1169-board-redispatch
charles:feat/1169-board-card-redispatch
charles:code-lead/1164
charles:dev/1169
charles:dev/1170
charles:code-lead/1159
charles:code-lead/1150
charles:code-lead/1148
charles:dev/1149
charles:code-lead/1151
charles:dev/1152
charles:code-lead/1147
charles:code-lead/1139
charles:code-lead/1138
charles:code-lead/1137
charles:code-lead/1136
charles:code-lead/1131
charles:code-lead/1122
charles:code-lead/1121
charles:code-lead/1120
charles:code-lead/1119
charles:code-lead/1117
charles:chore/sync-pre-push-from-forge-base
charles:fix/flows-yaml-dispatch-identity
charles:feat/board-tap-to-assign
charles:dev/1107
charles:code-lead/1106
charles:code-lead/1108
charles:dev/1104
charles:code-lead/1103
charles:code-lead/1080
charles:dev/1087
charles:feat/flows-yaml-ci-events
charles:chore/board-drop-stalled-and-density-controls
charles:fix/flows-yaml-routes-always-register
charles:flows-yaml/api-defaults
charles:dev/1023
charles:fix/event-log-history-bleed
charles:fix/janitor-fix-ci-logs-and-cap
charles:dev/1022
charles:fix/board-card-provider
charles:code-lead/1036
charles:dev/1025
charles:code-lead/1020
charles:dev/1017
charles:code-lead/1026
charles:feat/web-shortcut-registry-1018
charles:dev/1015
charles:code-lead/1009
charles:code-lead/1008
charles:dev/975
charles:dev/969
charles:dev/973
charles:dev/967
charles:code-lead/968
charles:code-lead/953
charles:dev/970
charles:dev/976
charles:code-lead/966
charles:code-lead/956
charles:code-lead/951
charles:dev/962
charles:dev/963
charles:dev/977
charles:dev/955
charles:dev/983
charles:dev/961
charles:dev/974
charles:code-lead/950
charles:code-lead/939
charles:dev/941
charles:dev/940
charles:dev/937
charles:dev/938
charles:dev/936
charles:dev/935
charles:feat/web-i18n-fr-locale
charles:feat/spec-editor-ui-polish
charles:chore/drop-legacy-compat
charles:fix/skills-drop-preview-pane
charles:fix/882-skills-safety-rail
charles:dev/911
charles:dev/909
charles:dev/923
charles:dev/917
charles:dev/915
charles:feat/879-sr11-m2-drop-legacy-skill
charles:code-lead/873
charles:dev/881
charles:code-lead/869
charles:dev/867
charles:code-lead/845
charles:code-lead/843
charles:code-lead/844
charles:dev/837
charles:dev/861
charles:dev/849
charles:code-lead/837
charles:code-lead/842
charles:fix/dedup-rebase-inflight
charles:dev/838
charles:code-lead/847
charles:dev/833
charles:code-lead/848
charles:pr/838
charles:code-lead/841
charles:feat/settings-save-bar/836
charles:code-lead/840
charles:dev/846
charles:code-lead/839
charles:dev/832
charles:fix/board-sse-stale-cache
charles:dev/834
charles:dev/835
charles:feat/settings-breadcrumbs
charles:feat/forge-oauth-credentials
charles:refactor/service-config-consolidation
charles:feat/agent-tokens-to-secrets
charles:feat/gitlab-oauth-to-db
charles:feat/authelia-rip-and-voice-fixes
charles:fix/rebase-storm-and-dead-letter
charles:code-lead/797
charles:code-lead/796
charles:dev/811
charles:code-lead/798
charles:dev/810
charles:code-lead/795
charles:dev/808
charles:code-lead/794
charles:dev/805
charles:dev/802
charles:dev/803
charles:feat/avatar-menu-settings-entry
charles:feat/per-agent-token-tracking
charles:dev/793
charles:dev/747
charles:dev/752
charles:code-lead/790
charles:code-lead/759
charles:dev/756
charles:dev/760
charles:dev/741
charles:dev/767
charles:dev/740
charles:dev/709
charles:dev/644
charles:dev/637
charles:boss/614
charles:dev/600
charles:dev/611
charles:dev/585
charles:fix/login-bonus-fixes
charles:boss/544
charles:dev/542
charles:refactor/api-prefix-and-session-gate
charles:dev/489
charles:boss/531
charles:boss/518
charles:dev/499
charles:boss/516
charles:dev/530
charles:dev/517
charles:dev/519
charles:dev/515
charles:dev/522
charles:dev/503
charles:dev/471
charles:boss/329
charles:dev/417
charles:dev/418
charles:dev/402
charles:boss/327
charles:dev/334
charles:dev/332
charles:boss/326
charles:boss/325
charles:dev/331
charles:boss/324
charles:boss/323
charles:boss/322
charles:dev/294
charles:test/s11-task-analytics
charles:dev/262
charles:boss/270
charles:dev/268
charles:foreman/ui-consolidation-spec
charles:dev/234
charles:boss/196
charles:boss/176
charles:boss/164
charles:fix/124-session-persist-bind
charles:boss/52
charles:dev/87
charles:boss/73
charles:dev/77
charles:dev/81
charles:dev/82
charles:boss/79
charles:dev/42
charles:dev/35
charles:boss/7
No reviewers
Labels
Clear labels
area:agents
Agent types, pool scheduling, per-instance config
area:dashboard
Dashboard UI and observability surfaces
area:database
DB layer — schema, migrations, ORM, raw SQL
area:design
UI/UX mockup work — routes to designer agent
area:design-review
Design review dispatch — routes to design-reviewer agent
area:flows
Flow runner — YAML loader, executor, op registry, expression eval
area:infra
Deployment, isolation, containers, systemd units
area:meta
Tracking, scaffolding, project setup
area:security
Security — routes to reviewer-security (opus)
area:sessions
Session-id store, Claude SDK resume logic
area:webhook
Forgejo webhook routing and handlers
area:workdir
Clone cache, worktrees, git identity
security
Security-sensitive issue
type:bug
Bug
type:chore
Chore
type:meta
Tracking or decisions, not implementation work
type:user-story
User story
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
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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.
Dependencies
No dependencies set.
Reference
charles/agent-hooks!1181
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/1161"
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
apps/web/src/components/timestamp-label.tsx—<TimestampLabel ts={number} showRelative? />rendersHH:MM:SSvia the existingfmtTime()helper, with an ISO-8601titletooltip and an optional hover-revealed relative age suffix (14:23:01 · 3m ago) driven by a pure CSSgroup-hovertrick.fmtTime()inlib/format.tsis kept untouched for non-React callers.fmtTime(ev.ts)with<TimestampLabel ts={ev.ts} />.fmtDuration-based elapsed text with<TimestampLabel ts={task.started_at} showRelative />so task rows show when the task started with a hover relative hint.Pick<TaskRecord, ...>to includestarted_atand renders a<TimestampLabel ts={task.started_at} showRelative />chip alongside the context/cost meters.fmtTime(tick.ts)inScrubberPopoverwith<TimestampLabel ts={tick.ts} />.fmtRelative(e.at, now)with<TimestampLabel ts={e.at} showRelative />to adopt the unified format; the localfmtRelativeexport is kept because it is tested externally.Test plan
just qapasses (typecheck + Biome lint/format + full test suite)HH:MM:SSwith ISO tooltip on hoverHH:MM:SS · Xm agoon hoverHH:MM:SStimestampHH:MM:SS · Xm agoon hoverCode Review —
<TimestampLabel>shared atomCI ✅ green. The component itself is well-designed — semantic
<time>withdateTime+titleattributes, named Tailwind group (group/ts) prevents stray parent-hover triggers, and non-React callers are correctly left on rawfmtTime(). Two must-fix issues before merge, plus one UX concern.❌ Must fix 1 — No tests for the new component
apps/web/CLAUDE.mdis explicit: new shared components need vitest browser-mode tests. Atimestamp-label.test.tsxis straightforward:Please add this file before merging.
❌ Must fix 2 — Stale relative time (no live clock)
fmtAgo(ts)inside the component capturesDate.now()at render time. Once the component is mounted the hover-revealed suffix (· 3m ago) will never update — it silently goes stale. A user who opens the task list and hovers 10 minutes later still reads the stale value.The fix is a lightweight
useClocktick:The interval only fires when
showRelativeis true, and 30 s is fine —fmtAgoalready rounds to the nearest second/minute.(The previous
fmtRelative(e.at, now)ininstance-history-drawerhad the same static-nowissue, so this is also a net improvement for that call-site.)⚠️ Concern —
task-list.tsxloses elapsed duration for completed tasksBefore this PR, finished/cancelled/failed task rows showed how long the task ran (
dev · 2m 34s). After this PR they show when it started (dev · 14:23:01 · 3m ago). Those are different pieces of information and elapsed duration is often the more actionable one for completed work.If the intent of #1161 is to unify timestamps rather than change the semantic, consider keeping
fmtDurationfor the completed/cancelled/failed branches and only using<TimestampLabel showRelative>for therunningbranch where "when it started" is genuinely useful:If the switch to start-time display is deliberate (and tracked in #1161), please note it explicitly in the PR description — it's a visible UX change that reviewers shouldn't have to infer from the diff.
Minor —
ts === 0inconsistencyfmtTime(0)returns"—"(the!tsguard), butnew Date(0).toISOString()produces"1970-01-01T00:00:00.000Z"so the rendered text and thetitle/dateTimeattributes would disagree. Epoch-0 is practically impossible here, but the prop typets: numbertechnically allows it. Either tighten the guard inTimestampLabel(ts > 0) or document the assumption. Not a blocker.Summary
<time>, named group, ISO tooltipts === 0Fix the missing tests and add the 30-second clock tick, then this is good to go.
Closing in favour of #1184 which has a cleaner implementation (
classNameprop,<Tooltip>component, correct optionalstarted_at). Two regressions from #1184 vs this PR (<time>element, nullstarted_atguard in task-list) will be patched directly on #1184's branch before merge.Pull request closed