Skip to content

feat(status-bar): trace branch PR and CI status - #653

Merged
Harry19081 merged 1 commit into
developfrom
feat/status-bar-branch-ci
Aug 2, 2026
Merged

feat(status-bar): trace branch PR and CI status#653
Harry19081 merged 1 commit into
developfrom
feat/status-bar-branch-ci

Conversation

@sudomaggie

Copy link
Copy Markdown
Collaborator

Summary

Adds a branch CI menu to the workstation status bar, sitting between the branch
button and the git sync control. It shows the pull request open for the current
branch and the state of every check on its head commit, grouped failed /
running / skipped / passed, with a link out to each check's details.

The menu reuses the ports menu's trigger and portalled panel so the two
neighbours in the bar read as one control family.

Problem

Tracing a branch's CI meant leaving the app for GitHub, or opening the PR
sidebar panel and switching to its Checks tab. GitHub Desktop surfaces this in
its own status bar; we had the data (github_get_checks, already wired for the
PR detail panel) but no ambient place to read it.

The naive way to add it — a fixed-interval poller — is the wrong trade. Most of
the time a branch's checks have already finished and nothing can change until
the next push, so a periodic timer spends GitHub API budget to re-confirm a
static answer.

Solution

The menu. CiStatusMenu renders only when the branch has an open PR. The
trigger shows a state icon plus #650, and reported/total while checks are
still running. The panel header carries the PR link, the rolled-up status and a
refresh button; rows show the check name, elapsed time only while running, and
a details link. The reporting app (GitHub Actions) is kept out of the label —
it is the same for nearly every row — and appears in the hover title instead.

Bounded polling. useBranchPullRequestStatus gains an opt-in poll mode.
The schedule lives in nextBranchCiPollDelayMs:

Situation Cadence
Checks running 15s → 30s → 60s, capped at 60s
PR open, no checks reported yet 30s × 3, then stop
Every check has a verdict no timer
No PR, or checks unreadable no timer
Window hidden timer cleared, resumes on return

A new head SHA resets the backoff, so a push is picked up at the fast interval
instead of inheriting a cooled-off one. Opening the menu or hitting refresh
forces a read past the 45s cache TTL. A typical 10-minute CI run costs ~11
reads and then goes quiet; a settled branch costs zero.

The focused-chat rail keeps polling off and shares the same TTL cache and
in-flight coalescing, so mounting both surfaces does not fetch twice.

Shared derivation. Check-state mapping moves to
services/git/ciCheckState.ts, which PrChecksTab now imports instead of
re-deriving it. "Settled" is computed structurally — every run reported —
rather than reading summary.state, because the server-side roll-up
short-circuits to failure while other jobs are still in flight, and the rows
should stay live until they actually finish.

Validation / Test plan

  • src/services/git/ciCheckState.test.ts (new) — conclusion mapping across all
    failing/neutral conclusions, run-before-status flattening, app name kept
    unmerged, state counts, and the settle rule including the
    decided-failure-with-a-job-still-running case.
  • src/services/git/branchPullRequestStatus.test.ts — poll schedule: stops with
    no PR / unreadable / settled checks, backs off and caps while running, and the
    bounded grace period for a PR with no checks yet.
  • src/hooks/git/useBranchPullRequestStatus.test.ts — re-reads while checks run
    and stops once they settle; never schedules a poll when poll is not
    requested. Existing visibility-gating and TTL cases still pass unchanged.
  • npx vitest run src/services/git src/hooks/git → 16 files, 81 tests passing.
  • tsc --noEmit and eslint clean over the touched files.
  • Manual: verified against a real PR — settled state renders with no background
    fetches, opening the menu forces a refresh, details links open externally.

Potential risks

  • API budget. Polling adds 3 REST calls per read (find PR → get PR → get
    checks) while checks are running. Bounded as above, well inside the 5000/hr
    authenticated limit, but it is new traffic that did not exist before.
  • DROPDOWN_CLASSES.searchContainer. Its class list was extracted into a
    shared PANEL_HEADER_ROW constant that panelHeaderRow also points at. The
    emitted string is identical, so existing consumers are unaffected — but it is
    now a shared definition, and changing it moves both.
  • PrChecksTab refactor. It now imports checkRunState /
    statusContextState rather than defining them. Behaviour is unchanged; the
    sidebar still shows the app-qualified check name, only the status-bar menu
    drops the prefix.
  • i18n coverage. New workstation.ci.* keys were added to en and zh
    only, matching the recent two-locale convention. Other locales fall back to
    English until translated.

Adds a CI menu to the workstation status bar, between the branch and the
git sync control: the pull request open for the current branch plus the
state of every check on its head commit, grouped failed / running /
skipped / passed. It reuses the ports menu's trigger and portalled panel
so the two neighbours read as one control family.

Polling is bounded rather than periodic. `useBranchPullRequestStatus`
gains an opt-in `poll` mode whose schedule comes from
`nextBranchCiPollDelayMs`: 15s backing off to a 60s cap while checks run,
three 30s retries when a PR has not reported any checks yet, and no timer
at all once every check has a verdict, when there is no PR, or when the
window is hidden. A new head SHA restarts the backoff so a push is picked
up quickly. Opening the menu or the refresh button forces a read past the
45s cache TTL. The focused-chat rail keeps polling off and shares the same
cache and in-flight coalescing, so nothing is fetched twice.

Check-state derivation moves to `services/git/ciCheckState`, which
`PrChecksTab` now shares instead of re-deriving it. "Settled" is
structural (every run reported) rather than reading the server roll-up,
which short-circuits to failure while other jobs are still in flight.

Pre-commit hook ran. Total eslint: 0, total circular: 0
@Harry19081
Harry19081 merged commit 44f3673 into develop Aug 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants