Files
werkator/docs/prs/2026-09-08-PR#37-ui-repositories-overview.md
af798850a5 ui: the Repositories page and the repository line (RFC 0001, second PR) (#37)
GET /api/repos answers every served repository with its newest build, running and
pending counts, the failed latest builds of the last seven days and the watcher's
problem, sorted newest build first. /repos renders the list with a summary line and
filters; the repository pages carry a line naming the current repository and the
conspicuous ones instead of the <select> switcher. werkator.js re-renders both from
the endpoint; the texts are shared with UiFormats word for word.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #37
2026-09-08 18:08:16 +02:00

9.1 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

An instance serves several repositories (ADR 0009), but the UI answered "is a build running anywhere, did one fail recently?" only one repository at a time: the switcher in the header listed names, and the answer took one page visit per repository. And the instance may soon serve more repositories than fit on one screen, so a strip of tiles or chips would not scale either. RFC 0001 decided a Repositories page — every served repository as a list, the one with the newest build first, so what matters is at the top and the long tail scrolls away — and a repository line on the repository pages that names only what is conspicuous.

Non-Goals

  • No new storage and no new data: everything on the page is derived from the latest build per name, the executor's transitions persisted in it, and the watcher's per-repository report.
  • No "last green" or "failing since" per repository; the seven-day window over the latest builds is what the data gives.
  • No search field and no date groups (decided 2026-09-08): the sort and four filters are the page.
  • No change for a single-repository instance beyond /repos leading to /: the page is not offered, the header does not name it, the wordmark keeps leading to the served repository.

The Scenarios

Feature: the instance at one glance

Background

  • An entry is one served repository as RepoOverviewDto describes it: its newest build (the latest build per name with the latest start), how many build names it has, how many are running and pending, its recently failed builds, and its watcher problem.
  • Recently failed is a failed latest build started within the last seven days (RepoOverviews.RECENT_FAILURE_WINDOW). Being the name's latest build, no later build of that name has succeeded — the condition asked for on 2026-09-08 holds by construction.
  • A conspicuous repository has a recently failed build or a watcher problem.

Scenario#37.01: GET /api/repos answers every served repository, newest build first

So that the page and the line have one source, and a script or a dashboard elsewhere can ask the same question.

  • Given an instance serving several repositories
  • When GET /api/repos is called
  • Then it answers one entry per served repository with its name, UI root, whether it is the current one, its build-name count, its newest build as a build result, its running and pending counts, its recently failed builds (name and start), and its watcher problem in the banner's words (origin unreachable, poll cycle failed) or null
    • and the entries are sorted by the newest build's start, newest first, repositories never built last
    • and the newest build carries the permanent link exactly when it is its name's latest green build
    • and with one served repository the endpoint answers a list of one.
Verified by

Scenario#37.02: the Repositories page

So that a reader sees every served repository, and what needs a look, without selecting one first.

  • Given an instance serving several repositories
  • When the reader opens /repos, the Repositories entry of the header, or the wordmark
  • Then the page lists every served repository in the endpoint's order, each entry with a status dot (the newest build's status, unknown when never built), the name linking to its Latest view, tags (6 branches, and only when they apply 1 running, 2 failed), the newest build — name, commit, status word — with a line saying what the status means in numbers (running for 2:14 · 1 pending, waiting 0:41 · 2 running, 4:51 · artifact, permalink, never built), its start time, and an attention column naming the recently failed builds with their dates and the watcher problem
    • and a summary line above (12 served · 1 running · 2 failed this week · 1 origin unreachable · newest build first, findings in clay) with the filters all, running, failed, unreachable
    • and werkator.js re-renders the list and the summary from GET /api/repos every ten seconds, with the timeout and the error badge every fetch has, keeping the reader's filter
    • and the page carries no repository crumb, no tabs and no repository line, being an instance page
  • Given an instance serving one repository
  • When the reader opens /repos
  • Then they land on /, the served repository's Latest view.
Verified by

Scenario#37.03: the repository line replaces the switcher

So that a failure elsewhere is visible from any repository page, and the header stays a header even with thirty repositories.

  • Given an instance serving several repositories
  • When a repository page renders (Latest, Branches, History, Current, an artifact page)
  • Then below the header a line names the current repository as a chip on the accent tint with its own finding (1 running), then every conspicuous repository as a clay-bordered chip with its finding (2 failed, unreachable, poll cycle failed), and on the right the link all 12 repositories → to the Repositories page
    • and a quiet repository is not in the line
    • and the <select> switcher is gone; the crumb shows the repository name as plain text
    • and werkator.js refreshes the line from GET /api/repos every ten seconds.
Verified by

The Solution

  • RepoOverviews (@Component) builds the entries from RepoRegistry, each repository's BuildResultRepository.latestPerName() and latestGreenFor(), Watcher.state().repositories, and RepoLinks; all(now) takes the clock as a parameter so the window is testable.
  • ReposApiController serves GET /api/repos.
  • UiController gains GET /repos (a redirect to / with one served repository) and, in baseModel, the brandUrl and — on repository pages of a multi-repository instance — the repoLine; the instance views are INSTANCE_VIEWS.
  • UiViews: RepoOverviewView, RepoSummaryView, RepoLineView/RepoChipView, and in UiFormats the shared texts count and repoStatusLine; RepoLinkView is gone with the switcher.
  • Templates: repos.html; fragments.html renders the line in viewHead, the Repositories navigation entry and the wordmark's target in siteHeader, and drops the <select>.
  • werkator.js: initRepoOverview (list, summary, filters) and initRepoLine, with repoStatusLine, countText, renderRepoEntry and renderRepoChip mirroring the Kotlin texts; initRepoSelect is gone.
  • werkator.css: the list, tags, chips, filters and line from the RFC's drafts; the crumb ellipsizes instead of breaking a long repository name on a phone.

Open Questions

  • Beyond a few dozen conspicuous repositories the line wraps; the RFC's +N more cap is not implemented until an instance gets there.

Additional Changes

  • AGENTS.md's multi-repository invariant and the architecture skill describe the overview and the line instead of the drop-down.

Prerequisite PRs

  • PR#35 — the design: the fragments and the stylesheet this PR extends.

Follow-up PRs

  • None planned; RFC 0001 is implemented with this PR.