Go to file
2026-07-29 09:24:20 +00:00
deploy Production deploy: rootDir fix, systemd unit, README, favicon 2026-07-28 14:32:31 +00:00
server Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
shared Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
web Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
.gitignore Server core: config, GitHub GraphQL polling, SQLite persistence, timeline ingestion 2026-07-28 14:21:25 +00:00
.mise.toml Server core: config, GitHub GraphQL polling, SQLite persistence, timeline ingestion 2026-07-28 14:21:25 +00:00
config.json Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
package.json Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
pnpm-lock.yaml Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
README.md Enumerate failing specs/E2E from CI artifacts; mute all github-actions comments 2026-07-29 09:24:20 +00:00
tsconfig.json Server core: config, GitHub GraphQL polling, SQLite persistence, timeline ingestion 2026-07-28 14:21:25 +00:00
tsconfig.server.json Server core: config, GitHub GraphQL polling, SQLite persistence, timeline ingestion 2026-07-28 14:21:25 +00:00

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 (PR updatedAt does 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 sync event 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=1 to 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.