Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Contributing

## Development setup

**Prerequisites:** Node.js 24+, pnpm 10+

```bash
git clone https://github.com/gordon-code/github-tracker.git
cd github-tracker
pnpm install
pnpm run dev
```

The dev server starts at `http://localhost:5173`. You'll need a GitHub OAuth app client ID in `.env` (copy `.env.example` and fill in your value).

## Running checks

```bash
pnpm test # unit tests (Vitest)
pnpm test:e2e # Playwright E2E tests (chromium)
pnpm run typecheck # TypeScript validation
pnpm run screenshot # Capture dashboard screenshot (saves to docs/)
```

CI runs typecheck, unit tests, and E2E tests on every PR. Make sure they pass locally before pushing.

To run a specific test file:

```bash
pnpm test -- tests/path/to/test.ts
```

## Code style

**TypeScript:** strict mode throughout. Don't use `any` — if you're reaching for it, there's usually a better type.

**SolidJS patterns:**
- Use `createMemo` for derived state; don't recompute inside JSX
- Use `<Show>` and `<Switch>`/`<Match>` instead of ternaries or early returns
- Early returns in components break SolidJS reactivity — use `<Show>` as a wrapper instead

**UI components:**
- Tailwind v4 + daisyUI v5 for styling — use semantic classes (`btn`, `card`, `badge`) over raw utilities where possible
- @kobalte/core for interactive primitives that need accessibility (Select, Tabs, Dialog)
- Don't reach for a custom implementation when Kobalte has a well-tested one

**Validation:** Zod v4 for all runtime validation. Note that nested `.default({})` doesn't apply inner field defaults — be explicit.

## Testing

Tests live in `tests/` and mirror the `src/` directory structure. Test files end in `.test.ts` or `.test.tsx`.

Factory helpers in `tests/helpers/index.tsx` (`makeIssue`, `makePullRequest`, `makeWorkflowRun`) give you typed test fixtures — use them instead of hand-rolling objects.

A few things to know:
- `createResource` error state is unreliable in happy-dom; use manual signals with `onMount` + async functions instead
- Kobalte Select uses `aria-labelledby`, which overrides `aria-label` — query by regex in tests
- If you're testing auth state, call `vi.resetModules()` and use dynamic imports — `auth.ts` reads localStorage at module scope

## Branch and commit conventions

Branch from `main`. Use one of these prefixes:

- `feat/` — new functionality
- `fix/` — bug fixes
- `docs/` — documentation only
- `refactor/` — code changes with no behavior change
- `test/` — test additions or fixes
- `chore/` — build, deps, tooling

Commits follow [Conventional Commits](https://www.conventionalcommits.org/):

```
type(scope): description
```

Scope is optional. Use imperative mood: "add feature", not "adds feature" or "added feature".

## Pull requests

All PRs target `main` on `gordon-code/github-tracker`. Keep PRs focused — one feature or fix per PR makes review faster and reverts cleaner.

In the PR body, describe what changed and why. CI runs typecheck, unit tests, and E2E tests automatically. PRs need a passing CI run before merge.
163 changes: 109 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,88 +4,143 @@

# GitHub Tracker

Dashboard SPA tracking GitHub issues, PRs, and GHA workflow runs across multiple repos/orgs. Built with SolidJS on Cloudflare Workers.
A dashboard for tracking GitHub issues, PRs, and Actions workflow runs across many repos and orgs. Built with SolidJS, deployed on Cloudflare Workers.

**Live demo:** https://gh.gordoncode.dev

![Dashboard](docs/dashboard-screenshot.png)

## Features

- **Issues Tab** — Open issues where you're the creator, assignee, or mentioned. Sortable, filterable, paginated. Dependency Dashboard issues hidden by default (toggleable).
- **Pull Requests Tab** — Open PRs with CI check status indicators (green/yellow/red dots). Draft badges, reviewer names.
- **Actions Tab** — GHA workflow runs grouped by repo and workflow. Accordion collapse, PR run toggle.
- **Onboarding Wizard** — Single-step repo selection with search filtering and bulk select.
- **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.
- **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.
- **Desktop Notifications** — New item alerts with per-type toggles and batching.
- **Ignore System** — Hide specific items with an "N ignored" badge and unignore popover.
- **Dark Mode** — System-aware with flash prevention via inline script + CSP SHA-256 hash.
- **ETag Caching** — Conditional requests (304s are free against GitHub's rate limit).
- **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.
### Issues

## Tech Stack
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.

- **Frontend:** SolidJS + Tailwind CSS v4 + TypeScript (strict)
- **Build:** Vite 8 + @cloudflare/vite-plugin
- **Hosting:** Cloudflare Workers (static assets + OAuth endpoint)
- **API:** @octokit/core with throttling, retry, pagination plugins
- **State:** localStorage (config/view) + IndexedDB (API cache with ETags)
- **Testing:** Vitest 4 (happy-dom for browser, @cloudflare/vitest-pool-workers for Worker)
- **Package Manager:** pnpm
### Pull Requests

## Development
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.

```sh
pnpm install
pnpm run dev # Start Vite dev server
pnpm test # Run unit/component tests
pnpm run typecheck # TypeScript check
pnpm run build # Production build (~241KB JS, ~31KB CSS)
```
### Actions

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.

### Personal Summary Strip

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.

### Multi-User Tracking

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.

### Monitor-All Mode

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.

### Upstream Repo Discovery

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.

### Hot Polling

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.

### Desktop Notifications

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.

### Repo Pinning and Reordering

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.

### State Visibility

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`.

### Star Counts

Star counts appear in repo group headers, fetched as part of the standard data refresh.

### Themes

9 themes: auto (follows system), corporate, cupcake, light, nord, dim, dracula, dark, forest. Theme is applied immediately on selection with no page reload.

### Ignore System

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.

### ETag Caching and Auto-Refresh

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).

## Tech Stack

- SolidJS + @solidjs/router
- Tailwind CSS v4 + daisyUI v5
- @kobalte/core (accessible headless UI primitives)
- TypeScript (strict)
- Vite 8 + @cloudflare/vite-plugin
- GitHub GraphQL + REST APIs via @octokit/core
- Cloudflare Workers (static assets + OAuth token exchange)
- Vitest 4 (happy-dom) + Playwright (E2E)
- pnpm

## Project Structure

```
src/
app/
components/
dashboard/ # DashboardPage, IssuesTab, PullRequestsTab, ActionsTab, ItemRow, WorkflowRunRow, IgnoreBadge
dashboard/ # DashboardPage, IssuesTab, PullRequestsTab, ActionsTab,
# ItemRow, WorkflowRunRow, WorkflowSummaryCard, IgnoreBadge,
# PersonalSummaryStrip
layout/ # Header, TabBar, FilterBar
onboarding/ # OnboardingWizard, OrgSelector, RepoSelector
settings/ # SettingsPage (7 config sections + data management)
shared/ # FilterInput, LoadingSpinner, StatusDot
pages/ # LoginPage, OAuthCallback
settings/ # SettingsPage, TrackedUsersSection, ThemePicker, Section, SettingRow
shared/ # 18 shared components: FilterInput, FilterChips, StatusDot,
# ReviewBadge, SizeBadge, RoleBadge, SortDropdown, PaginationControls,
# LoadingSpinner, SkeletonRows, ToastContainer, NotificationDrawer,
# RepoLockControls, UserAvatarBadge, ExpandCollapseButtons,
# RepoGitHubLink, ChevronIcon, ExternalLinkIcon
lib/ # 14 modules: format, errors, notifications, oauth, pat, url,
# flashDetection, grouping, reorderHighlight, collections,
# emoji, label-colors, sentry, github-emoji-map.json
pages/ # LoginPage, OAuthCallback, PrivacyPage
services/
api.ts # GitHub API methods (fetchOrgs, fetchRepos, fetchIssues, fetchPRs, fetchWorkflowRuns)
api.ts # GitHub API methods — issues, PRs, workflow runs, user validation,
# upstream repo discovery, tracked user search
github.ts # Octokit client factory with ETag caching and rate limit tracking
poll.ts # Poll coordinator with background refresh + hot poll for in-flight items
poll.ts # Poll coordinator: 5-min full refresh + hot poll loop
stores/
auth.ts # OAuth token management (localStorage persistence, validateToken)
auth.ts # OAuth/PAT token management, localStorage persistence
cache.ts # IndexedDB cache with TTL eviction and ETag support
config.ts # Zod v4-validated config with localStorage persistence
view.ts # View state (tabs, sorting, ignored items, filters)
lib/
pat.ts # PAT format validation and token creation instruction constants
notifications.ts # Desktop notification permission, detection, and dispatch
view.ts # View state (tabs, sorting, filters, ignored items, locked repos)
worker/
index.ts # OAuth token exchange endpoint, CORS, security headers
tests/
fixtures/ # GitHub API response fixtures (orgs, repos, issues, PRs, runs)
services/ # API service, Octokit client, and poll coordinator tests
stores/ # Config and cache store tests
components/ # ItemRow and IssuesTab component tests
lib/ # Notification tests
worker/ # Worker OAuth endpoint tests
tests/ # 1522 unit/component tests across 69 test files
e2e/ # 14 E2E tests across 2 spec files
```

## Development

```sh
pnpm install
pnpm run dev # Start Vite dev server
pnpm test # Run unit/component tests
pnpm test:e2e # Run Playwright E2E tests
pnpm run typecheck # TypeScript check
pnpm run build # Production build
pnpm run screenshot # Capture dashboard screenshot
```

## Security

- Strict CSP: `script-src 'self'` (SHA-256 exception for dark mode script only)
- PAT tokens stored in `localStorage` (same key as OAuth tokens) — single-user personal dashboard threat model
- OAuth CSRF protection via `crypto.getRandomValues` state parameter
- CORS locked to exact origin (strict equality, no substring matching)
- Access token stored in `localStorage` under app-specific key; CSP prevents XSS token theft
- Token validation on page load via `GET /user`; 401 clears auth immediately (no silent refresh)
- All GitHub API strings auto-escaped by SolidJS JSX (no innerHTML)
- `repo` scope granted (required for private repos) — app never performs write operations
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.

## Deployment

See [DEPLOY.md](./DEPLOY.md) for Cloudflare, OAuth App, and CI/CD setup.

## Contributing

See [CONTRIBUTING.md](./CONTRIBUTING.md).
Binary file added docs/dashboard-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading