Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgBEW6qAwgn2fcQbA2f4ZQ |
||
|---|---|---|
| deploy | ||
| server | ||
| shared | ||
| web | ||
| .gitignore | ||
| .mise.toml | ||
| config.json | ||
| package.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.server.json | ||
pr-monitor
Single-user dashboard for tracking PRs across multiple GitHub repos: CI checks segmented into lint / specs / e2e / other (every check visible, with per-chip dropdowns), review state derived from review-thread resolution, and a live unified timeline of all comments and reviews with persistent read/unread state.
How it works
- Node/TypeScript server polls the GitHub GraphQL API every 60s using a two-tier
query: a cheap
search()list query whose fields double as change signals (PRupdatedAtdoes not change when checks or thread resolution change), then batched detail queries only for changed PRs. - State is cached in SQLite (
data/pr-monitor.sqlite,node:sqlite) so the UI loads instantly on restart; timeline events and read/unread survive restarts. - The browser gets a
syncevent over SSE each poll and refetches via TanStack Query. React 19 + Vite SPA, light/dark/system theme, responsive (two-column desktop, tabbed mobile).
Config
config.json — repos, the involves:<user> filter flag, poll interval, port,
and per-repo check-name → category mappings (exact names or * globs, e.g.
"Preview E2E *"). Unmatched checks land in other; nothing is ever hidden.
Auth: GITHUB_TOKEN env var, falling back to gh auth token.
timeline.mute hides timeline events by actor (optionally kind / body substring)
at read time — data is kept, so unmuting is retroactive.
failureReports (per repo) maps failing check names to workflow artifacts that
contain structured results: rspec parser reads RSpec JSON-formatter output
(rspec_results_*.json), playwright reads the data/*.md failure attachments
inside Playwright HTML-report artifacts. Failing specs are stored (capped at
1000 per report), summarized on PR cards, and enumerated in the per-PR focus
view grouped by file. Reports re-fetch only when the workflow run id changes.
Commands
pnpm dev # server on :4000 (tsx watch)
pnpm dev:web # vite dev server on :5173, proxies /api
pnpm sync:once # one poll, prints a PR/category table (smoke test)
pnpm build # vite build + tsc server build
pnpm start # run the production build
Deploy
systemd user unit (survives reboots via linger):
pnpm build
cp deploy/pr-monitor.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now pr-monitor
loginctl enable-linger $USER
Serves the built SPA + API on http://localhost:4000.
Notes
- Own activity appears in the timeline but is never marked unread; the first-ever
sync marks the historical backfill as read (
BACKFILL_UNREAD=1to override). - Events are append-only: comments later deleted on GitHub remain in the timeline.
- Polling stretches to 5 min automatically when the GraphQL rate budget dips below 500 points.