A small native macOS app for tracking your own open pull requests (GitHub) and changes (Gerrit), built per the design spec in "PR Tracker - Design Spec" (Grouped Inbox direction). Three operations: add a PR/CL by pasting its URL, see everything grouped by what it's waiting on, and mark one change as depending on another.
Built with SwiftUI via Swift Package Manager — no Xcode project required.
- macOS 14+
- Swift toolchain (Command Line Tools are enough)
ghCLI, authenticated (gh auth login) — the app usesgh auth tokenfor GitHub API access- For Gerrit, nothing extra on public hosts. Private hosts (or private
changes) are read with the credentials in
~/.gitcookies, the same filegit-codereviewandgit cluse (Gerrit → Settings → HTTP Credentials).
./build.sh
open "build/PR Tracker.app"(swift run also works for quick iteration, without the app bundle niceties.)
- Add a PR — copy a PR link on GitHub (
github.com/owner/repo/pull/123) or a change link on any Gerrit host (host/c/project/+/123, a trailing patchset number is fine), then press ⌘V anywhere in the app (or click the paste field). A popover previews the fetched change; one click adds it to the right group. - Groups — every PR lands in exactly one derived state: Waiting on you
(changes requested, unresolved comments, failing checks, or draft), Waiting
on review, Waiting on CI, Blocked (user-set dependency), or Merged. The
sidebar filters by group or by repository. For Gerrit changes the attention
set decides "Waiting on you" vs "Waiting on review"; CI comes from the
host's verification label (
Verified,LUCI-TryBot-Result,Presubmit-*-Verified), withCommit-Queue+1 shown as running. - Dependencies — right-click a PR → "Blocked by" → pick another tracked PR. The blocked PR nests under its blocker with a connector; when the blocker merges it returns to its derived state automatically. Cycles are disabled at pick time. Gerrit stacks are linked automatically: each change nests under the change it's rebased on whenever that one is tracked too (re-derived on every refresh, so rebases follow), and the add popover offers to add the whole stack at once. Setting or clearing "Blocked by" by hand on a Gerrit change turns the automatic link off for that change.
- Other row actions — double-click (or right-click) to open in the browser, Copy URL, Mark as Merged, Remove from Tracker.
- Refresh — every tracked PR is re-fetched every 5 minutes, on app activation, via the toolbar refresh button, or with ⌘R. States are always derived, never hand-set (except dependency, mark-as-merged, and remove).
- Merged/closed PRs are kept for reference for 3 days, then pruned.
Resources/AppIcon.icns is generated on first build by
Tools/make-icon.swift (headless AppKit drawing + iconutil). Delete the
.icns and rebuild to regenerate it.
Tracked PRs persist to
~/Library/Application Support/PRTracker/prs.json.
PRTRACKER_SNAPSHOT=/tmp/shot.png writes a PNG of the app's windows a few
seconds after launch (no screen-recording permission needed);
PRTRACKER_LIGHT=1 forces light appearance; PRTRACKER_PASTE=<url>[,<url>…]
simulates the paste-to-add flow for each URL, and PRTRACKER_AUTOADD=1
confirms each preview; PRTRACKER_DATA_DIR=<dir> redirects the data file.
Useful for headless UI verification.