SQLite datetime('now') returns 'YYYY-MM-DD HH:MM:SS' with no timezone
marker. JS was treating this as local time, so timestamps showed the
correct UTC digits but with the local TZ abbreviation attached (e.g.
'7:15 PM EDT' when the real local time was '3:15 PM EDT').
Add parseUtc() which appends 'Z' before parsing any string that has no
existing timezone marker, ensuring JS always treats them as UTC before
the display-timezone conversion is applied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Display section to the settings modal with a timezone dropdown.
Selection is persisted to localStorage and applied to all timestamps via
fmtDate (date-only) and fmtDateFull (date + time + TZ abbreviation, e.g.
"Mar 28, 2026, 2:48 PM EDT"). Changing the timezone live-re-renders the
current page. Defaults to UTC.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$GITEA_SHA is unset on Gitea runners — the nav showed "dev-" with an
empty SHA. git rev-parse --short HEAD works regardless of runner env vars.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Production images continue to display the semver (v1.x.x). Dev images
built by CI now receive BUILD_VERSION=dev-<7-char-sha> via a Docker ARG,
and app.js skips the v prefix for non-semver strings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.badge lacked text-align: center. Inside the card's flex-end right
column, badge text was left-justified within each pill, making state
labels (deployed / testing / degraded) appear skewed to the left.
TDD: CSS regression test added to tests/helpers.test.js — reads
css/app.css directly and asserts the rule is present, so this
cannot regress silently in future.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>