ui(relTime): tick relative timestamps client-side so long-open tabs don't freeze
CI / Test (rest) (pull_request) Successful in 9s
CI / Test (store) (pull_request) Successful in 6s
CI / Build (windows/amd64) (pull_request) Successful in 8s
CI / Build (linux/amd64) (pull_request) Successful in 7s
CI / Lint (pull_request) Successful in 19s
CI / Build (linux/arm64) (pull_request) Successful in 7s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m26s
CI / Test (server-http) (pull_request) Successful in 2m34s

formatRelTime now wraps its label in <time data-rel-ts=...>, and
both layouts include a small ticker that re-renders every 30s.
Without this, a job-detail page rendered an hour ago kept showing
'2h ago' when the wall-clock truth was '3h ago'.
This commit is contained in:
2026-05-10 07:37:03 +01:00
parent f4db0b17e8
commit 28ef9750d3
4 changed files with 130 additions and 5 deletions
+22 -5
View File
@@ -221,23 +221,40 @@ func formatBytes(n int64) template.HTML {
// "in 5m"-style. Accepts *time.Time or time.Time so templates can
// pass either without fighting Go's lack of an address-of operator.
// Anything else returns "—".
func formatRelTime(v any) string {
//
// The output is wrapped in a <time data-rel-ts="..."> element so a
// small client-side ticker (see base.html) can refresh the label
// without a full page reload — otherwise a long-open tab shows
// timestamps frozen at render time.
func formatRelTime(v any) template.HTML {
var t time.Time
switch x := v.(type) {
case time.Time:
t = x
case *time.Time:
if x == nil {
return "—"
return template.HTML("—")
}
t = *x
default:
return "—"
return template.HTML("—")
}
if t.IsZero() {
return "—"
return template.HTML("—")
}
d := time.Since(t)
label := relTimeLabel(time.Since(t))
return template.HTML(fmt.Sprintf(
`<time data-rel-ts="%s" title="%s">%s</time>`,
t.UTC().Format(time.RFC3339Nano),
t.UTC().Format("2006-01-02 15:04:05 UTC"),
label,
))
}
// relTimeLabel turns a duration-since-now into the short human label
// used by formatRelTime (and mirrored verbatim by the JS ticker, so
// keep the two in sync if you change the buckets).
func relTimeLabel(d time.Duration) string {
suffix := "ago"
if d < 0 {
d = -d