Skip to content

Commit 6ce6ab5

Browse files
committed
Improve the README
1 parent 8fa0bf3 commit 6ce6ab5

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
Fast, filterable PR review. Entirely client-side.
77

88
> [!WARNING]
9-
> Pulldash is in alpha. Please report bugs.
9+
> Pulldash is WIP. Expect bugs.
1010
1111
[![Example](./docs/screenshots/overview.png)](https://pulldash.com)
1212

1313
## Why
1414

15-
- GitHub's review UI is slow
16-
- No way to filter PRs by repo or query
17-
- AI tooling means more PRs than ever
15+
- GitHub's review UI is slow (especially for large diffs)
16+
- No central view to filter PRs you care about
17+
- AI tooling has produced more PRs than ever before—making a snappy review UI essential
1818

1919
## Try It
2020

src/browser/contexts/github.tsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ function createGitHubStore() {
457457
let octokit: Octokit | null = null;
458458
let batcher: GraphQLBatcher | null = null;
459459
let onUnauthorized: (() => void) | null = null;
460+
let prListAbortController: AbortController | null = null;
460461
let onRateLimited: (() => void) | null = null;
461462

462463
function setOnUnauthorized(callback: () => void) {
@@ -641,9 +642,21 @@ function createGitHubStore() {
641642
// PR List
642643
// ---------------------------------------------------------------------------
643644

644-
async function fetchPRList(queries: string[], page = 1, perPage = 30) {
645+
async function fetchPRList(
646+
queries: string[],
647+
page = 1,
648+
perPage = 30,
649+
options?: { backgroundRefresh?: boolean }
650+
) {
645651
if (!octokit || !batcher) return;
646652

653+
const { backgroundRefresh = false } = options ?? {};
654+
655+
// Abort any in-flight request
656+
prListAbortController?.abort();
657+
const abortController = new AbortController();
658+
prListAbortController = abortController;
659+
647660
if (queries.length === 0) {
648661
setState({
649662
prList: {
@@ -671,7 +684,8 @@ function createGitHubStore() {
671684
setState({
672685
prList: {
673686
...stale.data,
674-
loading: stale.isStale,
687+
// Don't show loading state for background refreshes
688+
loading: backgroundRefresh ? false : stale.isStale,
675689
error: null,
676690
lastFetchedAt: Date.now(),
677691
},
@@ -680,7 +694,8 @@ function createGitHubStore() {
680694
});
681695
// If fresh, don't revalidate
682696
if (!stale.isStale) return;
683-
} else {
697+
} else if (!backgroundRefresh) {
698+
// Only show loading state if this is not a background refresh
684699
setState((s) => ({
685700
prList: { ...s.prList, loading: true, error: null },
686701
prListQueries: queries,
@@ -689,11 +704,14 @@ function createGitHubStore() {
689704
}
690705

691706
try {
692-
// Fetch PRs with caching
707+
// Fetch PRs with caching, passing the abort signal
693708
const results = await Promise.all(
694-
queries.map((q) => searchPRs(q, page, perPage))
709+
queries.map((q) => searchPRs(q, page, perPage, abortController.signal))
695710
);
696711

712+
// Check if aborted before processing results
713+
if (abortController.signal.aborted) return;
714+
697715
// Combine and dedupe by PR id
698716
const seen = new Set<number>();
699717
const combined: PRSearchResult[] = [];
@@ -732,6 +750,10 @@ function createGitHubStore() {
732750
if (prIdentifiers.length > 0) {
733751
try {
734752
const enrichmentMap = await getPREnrichment(prIdentifiers);
753+
754+
// Check if aborted after enrichment
755+
if (abortController.signal.aborted) return;
756+
735757
for (const item of combined) {
736758
const match = item.repository_url?.match(/repos\/([^/]+)\/([^/]+)/);
737759
if (match && item.number) {
@@ -747,6 +769,9 @@ function createGitHubStore() {
747769
}
748770
}
749771

772+
// Final check before updating state
773+
if (abortController.signal.aborted) return;
774+
750775
// Cache the result (persist for instant load next time)
751776
// Use combined.length instead of total to reflect deduplicated count
752777
cache.set(
@@ -765,6 +790,12 @@ function createGitHubStore() {
765790
},
766791
});
767792
} catch (e) {
793+
// Ignore abort errors - they're expected when switching filters
794+
if (e instanceof Error && e.name === "AbortError") return;
795+
796+
// Only update error state if not aborted
797+
if (abortController.signal.aborted) return;
798+
768799
setState((s) => ({
769800
prList: {
770801
...s.prList,
@@ -778,7 +809,7 @@ function createGitHubStore() {
778809
function refreshPRList() {
779810
const { prListQueries, prListPage } = state;
780811
if (prListQueries.length > 0) {
781-
fetchPRList(prListQueries, prListPage);
812+
fetchPRList(prListQueries, prListPage, 30, { backgroundRefresh: true });
782813
}
783814
}
784815

@@ -919,7 +950,12 @@ function createGitHubStore() {
919950
// API Methods (with caching and deduplication)
920951
// ---------------------------------------------------------------------------
921952

922-
async function searchPRs(query: string, page = 1, perPage = 30) {
953+
async function searchPRs(
954+
query: string,
955+
page = 1,
956+
perPage = 30,
957+
signal?: AbortSignal
958+
) {
923959
if (!octokit) throw new Error("Not initialized");
924960

925961
const cacheKey = `search:prs:${query}:${page}:${perPage}`;
@@ -947,6 +983,7 @@ function createGitHubStore() {
947983
order: "desc",
948984
per_page: perPage,
949985
page,
986+
request: { signal },
950987
})
951988
.then((res) => {
952989
cache.set(cacheKey, res.data);

0 commit comments

Comments
 (0)