|
4 | 4 |
|
5 | 5 | # GitHub Tracker |
6 | 6 |
|
7 | | -Dashboard SPA tracking GitHub issues, PRs, and GHA workflow runs across multiple repos/orgs. Built with SolidJS on Cloudflare Workers. |
| 7 | +A dashboard for tracking GitHub issues, PRs, and Actions workflow runs across many repos and orgs. Built with SolidJS, deployed on Cloudflare Workers. |
| 8 | + |
| 9 | +**Live demo:** https://gh.gordoncode.dev |
| 10 | + |
| 11 | + |
8 | 12 |
|
9 | 13 | ## Features |
10 | 14 |
|
11 | | -- **Issues Tab** — Open issues where you're the creator, assignee, or mentioned. Sortable, filterable, paginated. Dependency Dashboard issues hidden by default (toggleable). |
12 | | -- **Pull Requests Tab** — Open PRs with CI check status indicators (green/yellow/red dots). Draft badges, reviewer names. |
13 | | -- **Actions Tab** — GHA workflow runs grouped by repo and workflow. Accordion collapse, PR run toggle. |
14 | | -- **Onboarding Wizard** — Single-step repo selection with search filtering and bulk select. |
15 | | -- **PAT Authentication** — Optional Personal Access Token login as alternative to OAuth. Client-side format validation, detailed token creation instructions for classic and fine-grained PATs. |
16 | | -- **Settings Page** — Refresh interval, notification preferences, theme (light/dark/system), density, GitHub Actions limits. Shows current auth method and hides OAuth-specific options for PAT users. |
17 | | -- **Desktop Notifications** — New item alerts with per-type toggles and batching. |
18 | | -- **Ignore System** — Hide specific items with an "N ignored" badge and unignore popover. |
19 | | -- **Dark Mode** — System-aware with flash prevention via inline script + CSP SHA-256 hash. |
20 | | -- **ETag Caching** — Conditional requests (304s are free against GitHub's rate limit). |
21 | | -- **Auto-refresh** — Background polling keeps data fresh even in hidden tabs (requires notifications scope for efficient 304 change detection); hot poll pauses to save API budget. |
| 15 | +### Issues |
22 | 16 |
|
23 | | -## Tech Stack |
| 17 | +Open issues where you're the creator, assignee, or mentioned. A scope filter lets you toggle between "Involves me" and all activity in the repo. Role badges (author, assignee, mentioned) appear on each item. Dependency Dashboard issues — typically noisy bot aggregators — are hidden by default with a toggle to show them. Filterable, sortable, and paginated. |
24 | 18 |
|
25 | | -- **Frontend:** SolidJS + Tailwind CSS v4 + TypeScript (strict) |
26 | | -- **Build:** Vite 8 + @cloudflare/vite-plugin |
27 | | -- **Hosting:** Cloudflare Workers (static assets + OAuth endpoint) |
28 | | -- **API:** @octokit/core with throttling, retry, pagination plugins |
29 | | -- **State:** localStorage (config/view) + IndexedDB (API cache with ETags) |
30 | | -- **Testing:** Vitest 4 (happy-dom for browser, @cloudflare/vitest-pool-workers for Worker) |
31 | | -- **Package Manager:** pnpm |
| 19 | +### Pull Requests |
32 | 20 |
|
33 | | -## Development |
| 21 | +Open PRs with CI status dots (green/yellow/red), review decision badges, size badges (XS–XXL by lines changed), and draft indicators. A "blocked" filter catches PRs where checks are failing or a review requested changes. The scope filter works here too. Reviewer avatars stack for multiple reviewers. |
34 | 22 |
|
35 | | -```sh |
36 | | -pnpm install |
37 | | -pnpm run dev # Start Vite dev server |
38 | | -pnpm test # Run unit/component tests |
39 | | -pnpm run typecheck # TypeScript check |
40 | | -pnpm run build # Production build (~241KB JS, ~31KB CSS) |
41 | | -``` |
| 23 | +### Actions |
| 24 | + |
| 25 | +Workflow runs grouped by repo and workflow name, with duration, triggering actor, and conclusion badges. Accordion collapse per group. A toggle hides runs triggered by PRs so you can focus on branch/schedule runs. |
| 26 | + |
| 27 | +### Personal Summary Strip |
| 28 | + |
| 29 | +A row of clickable stat chips at the top of the dashboard: assigned issues, PRs awaiting your review, PRs ready to merge, blocked PRs, and running Actions. Clicking any chip applies the matching filter on the relevant tab. |
| 30 | + |
| 31 | +### Multi-User Tracking |
| 32 | + |
| 33 | +Track other GitHub users' activity alongside your own. Add up to 10 users; each gets an independent global search across all selected repos. Bot accounts (GitHub App bots) are supported and labelled with a bot badge. Items surface the tracked user's avatar so you can see at a glance who triggered what. |
| 34 | + |
| 35 | +### Monitor-All Mode |
| 36 | + |
| 37 | +Per-repo opt-in to see all open issues and PRs in that repo, not just ones involving tracked users. Useful for repos where you want full visibility without filtering by involvement. Monitored repos show a "Monitoring all" badge on their group header. |
| 38 | + |
| 39 | +### Upstream Repo Discovery |
| 40 | + |
| 41 | +When you sign in, the app searches for repos you've interacted with that aren't in your selected list and offers to add them as "upstream" repos. These are included in issue/PR fetches but excluded from workflow run polling. |
| 42 | + |
| 43 | +### Hot Polling |
| 44 | + |
| 45 | +A second, faster poll loop (default 30s, configurable 10–120s) targets only in-flight items — PRs with pending CI checks and actively running workflow runs. These are updated via minimal GraphQL `nodes()` queries and individual REST calls rather than full re-fetches, keeping API usage low during active development. |
| 46 | + |
| 47 | +### Desktop Notifications |
| 48 | + |
| 49 | +Browser notifications for new issues, PRs, and failed runs. Per-type toggles in settings. Notification permission requested on first enable. Uses the GitHub Notifications API as a change-detection gate when the `notifications` scope is available. |
| 50 | + |
| 51 | +### Repo Pinning and Reordering |
| 52 | + |
| 53 | +Lock repos to the top of each tab's list so they don't shift around as activity changes. Drag-to-reorder within the locked set. Lock controls appear on hover on desktop, always visible on mobile. |
| 54 | + |
| 55 | +### State Visibility |
| 56 | + |
| 57 | +Shimmer animations on items being updated by the hot poll, flash highlights when values change (check status, review decision), and an inline peek on collapsed repo headers that shows what changed for 3 seconds. All animations respect `prefers-reduced-motion`. |
| 58 | + |
| 59 | +### Star Counts |
| 60 | + |
| 61 | +Star counts appear in repo group headers, fetched as part of the standard data refresh. |
| 62 | + |
| 63 | +### Themes |
| 64 | + |
| 65 | +9 themes: auto (follows system), corporate, cupcake, light, nord, dim, dracula, dark, forest. Theme is applied immediately on selection with no page reload. |
| 66 | + |
| 67 | +### Ignore System |
| 68 | + |
| 69 | +Hide specific items with a persistent ignore list. An "N ignored" badge on the repo group header lets you see what's hidden and unignore items without leaving the tab. |
| 70 | + |
| 71 | +### ETag Caching and Auto-Refresh |
| 72 | + |
| 73 | +Conditional requests using `If-None-Match` headers — GitHub doesn't count 304 responses against the rate limit. Background polling keeps data fresh even when the tab is hidden (when the notifications scope is available for efficient change detection). |
| 74 | + |
| 75 | +## Tech Stack |
| 76 | + |
| 77 | +- SolidJS + @solidjs/router |
| 78 | +- Tailwind CSS v4 + daisyUI v5 |
| 79 | +- @kobalte/core (accessible headless UI primitives) |
| 80 | +- TypeScript (strict) |
| 81 | +- Vite 8 + @cloudflare/vite-plugin |
| 82 | +- GitHub GraphQL + REST APIs via @octokit/core |
| 83 | +- Cloudflare Workers (static assets + OAuth token exchange) |
| 84 | +- Vitest 4 (happy-dom) + Playwright (E2E) |
| 85 | +- pnpm |
42 | 86 |
|
43 | 87 | ## Project Structure |
44 | 88 |
|
45 | 89 | ``` |
46 | 90 | src/ |
47 | 91 | app/ |
48 | 92 | components/ |
49 | | - dashboard/ # DashboardPage, IssuesTab, PullRequestsTab, ActionsTab, ItemRow, WorkflowRunRow, IgnoreBadge |
| 93 | + dashboard/ # DashboardPage, IssuesTab, PullRequestsTab, ActionsTab, |
| 94 | + # ItemRow, WorkflowRunRow, WorkflowSummaryCard, IgnoreBadge, |
| 95 | + # PersonalSummaryStrip |
50 | 96 | layout/ # Header, TabBar, FilterBar |
51 | 97 | onboarding/ # OnboardingWizard, OrgSelector, RepoSelector |
52 | | - settings/ # SettingsPage (7 config sections + data management) |
53 | | - shared/ # FilterInput, LoadingSpinner, StatusDot |
54 | | - pages/ # LoginPage, OAuthCallback |
| 98 | + settings/ # SettingsPage, TrackedUsersSection, ThemePicker, Section, SettingRow |
| 99 | + shared/ # 18 shared components: FilterInput, FilterChips, StatusDot, |
| 100 | + # ReviewBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls, |
| 101 | + # LoadingSpinner, SkeletonRows, ToastContainer, NotificationDrawer, |
| 102 | + # RepoLockControls, UserAvatarBadge, ExpandCollapseButtons, |
| 103 | + # RepoGitHubLink, ChevronIcon, ExternalLinkIcon |
| 104 | + lib/ # 14 modules: format, errors, notifications, oauth, pat, url, |
| 105 | + # flashDetection, grouping, reorderHighlight, collections, |
| 106 | + # emoji, label-colors, sentry, github-emoji-map.json |
| 107 | + pages/ # LoginPage, OAuthCallback, PrivacyPage |
55 | 108 | services/ |
56 | | - api.ts # GitHub API methods (fetchOrgs, fetchRepos, fetchIssues, fetchPRs, fetchWorkflowRuns) |
| 109 | + api.ts # GitHub API methods — issues, PRs, workflow runs, user validation, |
| 110 | + # upstream repo discovery, tracked user search |
57 | 111 | github.ts # Octokit client factory with ETag caching and rate limit tracking |
58 | | - poll.ts # Poll coordinator with background refresh + hot poll for in-flight items |
| 112 | + poll.ts # Poll coordinator: 5-min full refresh + hot poll loop |
59 | 113 | stores/ |
60 | | - auth.ts # OAuth token management (localStorage persistence, validateToken) |
| 114 | + auth.ts # OAuth/PAT token management, localStorage persistence |
61 | 115 | cache.ts # IndexedDB cache with TTL eviction and ETag support |
62 | 116 | config.ts # Zod v4-validated config with localStorage persistence |
63 | | - view.ts # View state (tabs, sorting, ignored items, filters) |
64 | | - lib/ |
65 | | - pat.ts # PAT format validation and token creation instruction constants |
66 | | - notifications.ts # Desktop notification permission, detection, and dispatch |
| 117 | + view.ts # View state (tabs, sorting, filters, ignored items, locked repos) |
67 | 118 | worker/ |
68 | 119 | index.ts # OAuth token exchange endpoint, CORS, security headers |
69 | | -tests/ |
70 | | - fixtures/ # GitHub API response fixtures (orgs, repos, issues, PRs, runs) |
71 | | - services/ # API service, Octokit client, and poll coordinator tests |
72 | | - stores/ # Config and cache store tests |
73 | | - components/ # ItemRow and IssuesTab component tests |
74 | | - lib/ # Notification tests |
75 | | - worker/ # Worker OAuth endpoint tests |
| 120 | +tests/ # 1522 unit/component tests across 69 test files |
| 121 | +e2e/ # 14 E2E tests across 2 spec files |
| 122 | +``` |
| 123 | + |
| 124 | +## Development |
| 125 | + |
| 126 | +```sh |
| 127 | +pnpm install |
| 128 | +pnpm run dev # Start Vite dev server |
| 129 | +pnpm test # Run unit/component tests |
| 130 | +pnpm test:e2e # Run Playwright E2E tests |
| 131 | +pnpm run typecheck # TypeScript check |
| 132 | +pnpm run build # Production build |
| 133 | +pnpm run screenshot # Capture dashboard screenshot |
76 | 134 | ``` |
77 | 135 |
|
78 | 136 | ## Security |
79 | 137 |
|
80 | | -- Strict CSP: `script-src 'self'` (SHA-256 exception for dark mode script only) |
81 | | -- PAT tokens stored in `localStorage` (same key as OAuth tokens) — single-user personal dashboard threat model |
82 | | -- OAuth CSRF protection via `crypto.getRandomValues` state parameter |
83 | | -- CORS locked to exact origin (strict equality, no substring matching) |
84 | | -- Access token stored in `localStorage` under app-specific key; CSP prevents XSS token theft |
85 | | -- Token validation on page load via `GET /user`; 401 clears auth immediately (no silent refresh) |
86 | | -- All GitHub API strings auto-escaped by SolidJS JSX (no innerHTML) |
87 | | -- `repo` scope granted (required for private repos) — app never performs write operations |
| 138 | +OAuth tokens are stored in `localStorage` under an app-specific key — this is standard for single-user personal dashboards and matches the threat model here. CSP headers block script injection via Cloudflare (`script-src 'self'` with a SHA-256 exception for the dark-mode initialization script only). An Octokit hook blocks all non-GET requests except `POST /graphql` as a read-only guard — the `repo` scope is required for private repo access, but the app never performs writes. OAuth state is generated with `crypto.getRandomValues` and verified on callback. Token validation runs on every page load via `GET /user`; a 401 clears the stored token immediately. |
88 | 139 |
|
89 | 140 | ## Deployment |
90 | 141 |
|
91 | 142 | See [DEPLOY.md](./DEPLOY.md) for Cloudflare, OAuth App, and CI/CD setup. |
| 143 | + |
| 144 | +## Contributing |
| 145 | + |
| 146 | +See [CONTRIBUTING.md](./CONTRIBUTING.md). |
0 commit comments