Author filter chips composable with repo/unread filters; 'mine' pinned first

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgBEW6qAwgn2fcQbA2f4ZQ
This commit is contained in:
Joshua Coles
2026-07-29 09:38:17 +00:00
co-authored by Claude Fable 5
parent 2015d20aa5
commit 5aa6f3367d
5 changed files with 75 additions and 16 deletions
+50 -14
View File
@@ -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<Filter>('all');
const [unreadOnly, setUnreadOnly] = useState(false);
const [repoFilter, setRepoFilter] = useState<string | null>(null);
const [authorFilter, setAuthorFilter] = useState<string | null>(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<string, number>();
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<string, typeof filtered>();
for (const pr of filtered) {
if (!byRepo.has(pr.repo)) byRepo.set(pr.repo, []);
@@ -32,19 +45,42 @@ export default function PrList({
return (
<>
<div className="filter-tabs">
<button className={filter === 'all' ? 'active' : ''} onClick={() => setFilter('all')}>
<button
className={!unreadOnly && !repoFilter && !authorFilter ? 'active' : ''}
onClick={() => {
setUnreadOnly(false);
setRepoFilter(null);
setAuthorFilter(null);
}}
>
All ({state.prs.length})
</button>
<button className={filter === 'unread' ? 'active' : ''} onClick={() => setFilter('unread')}>
<button
className={unreadOnly ? 'active' : ''}
onClick={() => setUnreadOnly((v) => !v)}
>
Unread{state.unreadTotal > 0 ? ` (${state.unreadTotal})` : ''}
</button>
{repos.map((repo) => (
{repos.length > 1 && <span className="filter-sep" />}
{repos.length > 1 &&
repos.map((repo) => (
<button
key={repo}
className={repoFilter === repo ? 'active' : ''}
onClick={() => setRepoFilter((cur) => (cur === repo ? null : repo))}
>
{shortRepo(repo)}
</button>
))}
</div>
<div className="filter-tabs filter-authors">
{authors.map(([author, count]) => (
<button
key={repo}
className={filter === repo ? 'active' : ''}
onClick={() => setFilter(repo)}
key={author}
className={authorFilter === author ? 'active' : ''}
onClick={() => setAuthorFilter((cur) => (cur === author ? null : author))}
>
{shortRepo(repo)}
{author === state.user ? 'mine' : author} ({count})
</button>
))}
</div>