Durations of running builds are ticked client-side, so a page left in the
background kept counting up a build that had long finished on the server —
on a phone the tab is frozen for an hour and comes back showing a build
"running" for an hour.
Resuming now re-arms the poll unconditionally instead of only when the page
was stopped: a frozen page may never deliver the hidden event, and its
throttled interval then fires whenever the browser feels like it. `pageshow`
and `focus` are listened to as well, because not every browser reports a
returning page as a visibility change; resume events arriving together are
collapsed into one fetch.
Verified in a browser: hidden for 11s over a 10s interval makes no request,
a lone focus event makes exactly one, and a second resume event right after
makes none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A branches.<name>.autoBuild.times entry is now either a plain HH:MM
string or an object with time, its own buildCommand, and a name, so a
nightly slot can run a fuller check than the on-commit builds. The
watcher passes the slot's command and name to the executor, persisted in
the build result — UI restarts, gittally retry, and the startup recovery
repeat a build with the command and name it originally ran under.
A named slot (e.g. master@nightly) gets its own pool: repository
grouping, retention count, branches-view row (sorted after its branch),
latest status, and permanent latest-green artifact link are keyed by the
build name, while origin lookups, gone-branch pruning, worktrees, and
Gitea links/statuses stay keyed by the real branch. Without a name,
slot builds share the branch's pool as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes TODO 2 of the security audit in docs/prs/2026-07-08-PR#000.
Every rendered page carried the live control token in a meta tag so that
gittally.js could send it, but no GET is authenticated — so `curl … |
grep gittally-control-token` handed the token to anyone, and read access
was effectively write access.
Reading stays fully public, which is a requirement rather than an
oversight: build states, logs and artifacts must be linkable from Gitea,
chats or tickets without a login. Only the distribution of the token
changed. The meta tag is gone; gittally.js keeps the token in
localStorage and asks for it once per browser, so knowing it requires
shell access to `.git/gittally/control-token` on the host. A token the
server rejects is dropped and asked for once more, so a rotated secret is
not a dead end. As a request header it stays inherently CSRF-safe.
The five branches of that flow (first use, reuse, stale token, cancelled
prompt, wrong token twice) were exercised against the real source with a
throwaway node harness; the UI test now asserts the token does not appear
in the rendered page. `docs/deployment.md` gained a "Control Token"
section on the public-read/token-protected-write split.
Co-Authored-By: Claude <noreply@anthropic.com>
A report directory holding a single page is now linked and served as a
directory, so Gradle's --profile report has a stable permanent URL although
its file name carries the build timestamp.
The permanent link moves to the build it resolves to — the branch's latest
green build — and appears on every build table instead of only the branches
view. The Current tab gave way to a link in the artifacts column, shown
while a build runs; /current itself stays routable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Current cell of CPU/RAM/disk used turns orange from 80% of the
total and red from 90%. UiFormats.utilizationClass and the mirrored
utilizationClass in gittally.js apply the same thresholds; unavailable
metrics (n/a) are never highlighted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BuildResult gains runningSince, set when a build leaves the queue; the
recorded duration now measures pure build time from that point, so build
runtimes can be tracked without queue wait. A build cancelled while still
queued records neither. The UI shows the live wait time in italics while
pending and switches to the real build time once the build runs; the Gitea
"after mm:ss" descriptions now also report pure build time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An in-progress build's artifact page only offers the build command and log —
the table now shows an hourglass instead of the document icon (server-rendered
rows and the JS-rendered rows alike) until the build finishes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 10s table poll rebuilt the rows with an empty duration cell for
running/pending builds (durationSeconds is null until a build finishes),
which the once-per-second ticker then filled back in — a visible flicker.
Rows and current-build cards now compute the elapsed time at render time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>