From 5aa6f3367ddd65c2f2c7281df49926989fab09aa Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Wed, 29 Jul 2026 09:38:17 +0000 Subject: [PATCH] Author filter chips composable with repo/unread filters; 'mine' pinned first Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DgBEW6qAwgn2fcQbA2f4ZQ --- server/index.ts | 2 +- server/routes.ts | 4 ++- shared/types.ts | 2 ++ web/src/components/PrList.tsx | 64 +++++++++++++++++++++++++++-------- web/src/styles/app.css | 19 +++++++++++ 5 files changed, 75 insertions(+), 16 deletions(-) diff --git a/server/index.ts b/server/index.ts index 7ee7cfb..a88c8ed 100644 --- a/server/index.ts +++ b/server/index.ts @@ -40,7 +40,7 @@ function scheduleNext(): void { }, intervalMs); } -const app = createApp({ db, triggerSync: poll }); +const app = createApp({ db, user: config.user, triggerSync: poll }); app.listen(config.port, config.host, () => { console.log(`pr-monitor listening on http://${config.host}:${config.port}`); }); diff --git a/server/routes.ts b/server/routes.ts index 81cdf2c..152697f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -9,6 +9,7 @@ import { buildFailureReport } from './failures.js'; export interface RouteDeps { db: Db; + user: string; triggerSync: () => Promise; } @@ -22,13 +23,14 @@ export function buildSyncPayload(db: Db, changedPrIds: string[] = [], newEventCo }; } -export function createApp({ db, triggerSync }: RouteDeps): Express { +export function createApp({ db, user, triggerSync }: RouteDeps): Express { const app = express(); app.use(express.json()); app.get('/api/state', (_req, res) => { const { byPr, total } = db.unreadCounts(); const payload: StatePayload = { + user, prs: db.getOpenSnapshots(), lastSyncAt: db.getMeta('last_sync_at'), lastSyncError: db.getMeta('last_sync_error'), diff --git a/shared/types.ts b/shared/types.ts index c3773f0..4adb25d 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -88,6 +88,8 @@ export interface FailureReport extends FailureReportSummary { } export interface StatePayload { + /** The configured GitHub login this dashboard belongs to. */ + user: string; prs: PrSnapshot[]; lastSyncAt: string | null; lastSyncError: string | null; diff --git a/web/src/components/PrList.tsx b/web/src/components/PrList.tsx index 3771d16..9f71d8d 100644 --- a/web/src/components/PrList.tsx +++ b/web/src/components/PrList.tsx @@ -3,8 +3,6 @@ import type { StatePayload } from '../../../shared/types'; import PrCard from './PrCard'; import { shortRepo } from '../format'; -type Filter = 'all' | 'unread' | string; - export default function PrList({ state, selectedPrId, @@ -14,15 +12,30 @@ export default function PrList({ selectedPrId: string | null; onSelect: (id: string) => void; }) { - const [filter, setFilter] = useState('all'); + const [unreadOnly, setUnreadOnly] = useState(false); + const [repoFilter, setRepoFilter] = useState(null); + const [authorFilter, setAuthorFilter] = useState(null); const repos = [...new Set(state.prs.map((p) => p.repo))]; - const filtered = state.prs.filter((pr) => { - if (filter === 'all') return true; - if (filter === 'unread') return (state.unreadCountsByPr[pr.id] ?? 0) > 0; - return pr.repo === filter; + + const authorCounts = new Map(); + for (const pr of state.prs) { + authorCounts.set(pr.author, (authorCounts.get(pr.author) ?? 0) + 1); + } + // "mine" first, then by open-PR count. + const authors = [...authorCounts.entries()].sort((a, b) => { + if (a[0] === state.user) return -1; + if (b[0] === state.user) return 1; + return b[1] - a[1] || a[0].localeCompare(b[0]); }); + const filtered = state.prs.filter( + (pr) => + (!unreadOnly || (state.unreadCountsByPr[pr.id] ?? 0) > 0) && + (!repoFilter || pr.repo === repoFilter) && + (!authorFilter || pr.author === authorFilter), + ); + const byRepo = new Map(); for (const pr of filtered) { if (!byRepo.has(pr.repo)) byRepo.set(pr.repo, []); @@ -32,19 +45,42 @@ export default function PrList({ return ( <>
- - - {repos.map((repo) => ( + {repos.length > 1 && } + {repos.length > 1 && + repos.map((repo) => ( + + ))} +
+
+ {authors.map(([author, count]) => ( ))}
diff --git a/web/src/styles/app.css b/web/src/styles/app.css index 296f9eb..ef03347 100644 --- a/web/src/styles/app.css +++ b/web/src/styles/app.css @@ -214,6 +214,25 @@ button { border-color: var(--ink); } +.filter-sep { + width: 1px; + align-self: stretch; + background: var(--hairline); + margin: 2px 2px; +} + +.filter-authors { + padding-top: 0; +} + +.filter-authors button { + border-style: dashed; +} + +.filter-authors button.active { + border-style: solid; +} + /* ---------- PR cards ---------- */ .pr-list {