2026.09.13
Pre-releaseFixed
-
Take the clock from Studio, and let the operator pick the timezone (6a970c39)
Every duration, expiry check and metric window in the UI was computed against
the browser's clock. An operator whose workstation ran four minutes fast saw
every request-reply flow age inflated by four minutes, a live API key rendered
asexpired, and an empty metrics chart with nothing saying why — the window it
asked the server for had not happened yet.Studio already refuses to trust a broker's clock (ADR-0053). The browser is one
more foreign clock, and it was the only one still taken on faith.What changes for you:
- Ages, staleness labels and token expiry now follow Studio's clock, not the
machine the browser runs on. On a correctly-synchronised workstation nothing
moves; on a skewed one, the numbers become right. - The metrics window and the queue drawer's chart are quantised on the server's
clock, so a skewed workstation no longer requests a range the server has no
samples for. - Timestamps now render in your own timezone by default, detected from the
browser, where they were previously always UTC. Settings → Display picks a
different one, including UTC to match container and broker logs. The choice is
per-browser, needs no permission, and is never reset once made. - Every absolute timestamp now names the offset it is written in (
Z,+03:30)
and is rendered to whole seconds, where before some carried milliseconds and
some did not. - The metric charts previously drew their axis in the browser's local zone while
every table beside them was UTC, with nothing disclosing the difference. Both
now follow the chosen zone.
How it works: a new
GET /api/v1/timereturns Studio's clock, and the client
measures its own offset NTP-style with round-trip bracketing — the same
estimator and the same constants asClockOffsetRegistry, so the two halves of
the system agree about method. A reading is only allowed to teach the estimate
when its round trip is at or near the best seen; an offset inside its own error
bar is held at zero rather than correcting by noise. The SSEpingalready
carried the server's clock and the client discarded it; it is now a drift
detector that asks for a real probe, never a measurement of its own, since a
one-way frame cannot measure its own latency. A wall-clock step — a laptop
waking, an NTP correction — is caught by comparingDate.now()against
performance.now(), the client-sideMonotonicClockWatch, and discards the
estimate rather than slowly unlearning it.web/src/app/time.tsstates the rule for the whole frontend: durations and
expiry against a server timestamp useserverNow(); a browser-stamped instant
entering such a comparison (TanStack'sdataUpdatedAtis the only one) is
normalised withtoServerMs()at the boundary; monotonic work keeps using
performance.now(). Three duplicate "ago" formatters and seven copies of the
absolute-timestamp formatter are folded into it.Known and deliberately out of scope: the queue detail drawer's chart window is
memoised on the range spec alone, so it is fixed at first render and does not
advance while the drawer stays open. That predates this change. - Ages, staleness labels and token expiry now follow Studio's clock, not the