Skip to content

2026.09.13

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 07 Sep 14:16
· 17 commits to main since this release

Fixed

  • 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
    as expired, 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/time returns Studio's clock, and the client
    measures its own offset NTP-style with round-trip bracketing — the same
    estimator and the same constants as ClockOffsetRegistry, 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 SSE ping already
    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 comparing Date.now() against
    performance.now(), the client-side MonotonicClockWatch, and discards the
    estimate rather than slowly unlearning it.

    web/src/app/time.ts states the rule for the whole frontend: durations and
    expiry against a server timestamp use serverNow(); a browser-stamped instant
    entering such a comparison (TanStack's dataUpdatedAt is the only one) is
    normalised with toServerMs() 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.