fix(ui): keep work status PR checks current

This commit is contained in:
Bohdan Triapitsyn
2026-08-20 01:38:43 +03:00
parent a2a9eabd04
commit 90d8868bfc
4 changed files with 78 additions and 26 deletions
@@ -4,7 +4,7 @@ import type { GitHubPullRequestStatus, RuntimeAPIs } from "@/lib/api/types"
let runtimeKey = "runtime-a"
mock.module("@/lib/runtime-switch", () => ({ getRuntimeKey: () => runtimeKey }))
const { getGitHubPrStatusKey, useGitHubPrStatusStore } = await import("./useGitHubPrStatusStore")
const { getFreshestPrStatusForBranch, getGitHubPrStatusKey, useGitHubPrStatusStore } = await import("./useGitHubPrStatusStore")
const deferred = <T>() => {
let resolve!: (value: T) => void
@@ -38,6 +38,34 @@ describe("GitHub PR status cache ownership", () => {
expect(new Set([originA, upstreamA, originB]).size).toBe(3)
})
test("passive branch readers follow the freshest remote-keyed status", () => {
const automatic = getGitHubPrStatusKey("/repo", "feature")
const origin = getGitHubPrStatusKey("/repo", "feature", "origin")
useGitHubPrStatusStore.getState().ensureEntry(automatic)
useGitHubPrStatusStore.getState().ensureEntry(origin)
useGitHubPrStatusStore.getState().updateStatus(automatic, () => ({
connected: true,
pr: { number: 7, title: "old", url: "u7", state: "open", draft: false, base: "main", head: "feature" },
checks: { state: "pending", total: 3, success: 1, failure: 0, pending: 2 },
}))
useGitHubPrStatusStore.getState().updateStatus(origin, () => ({
connected: true,
pr: { number: 7, title: "current", url: "u7", state: "open", draft: false, base: "main", head: "feature" },
checks: { state: "success", total: 3, success: 3, failure: 0, pending: 0 },
}))
useGitHubPrStatusStore.setState((state) => ({
entries: {
...state.entries,
[automatic]: { ...state.entries[automatic], lastRefreshAt: 1 },
[origin]: { ...state.entries[origin], lastRefreshAt: 2 },
},
}))
const freshest = getFreshestPrStatusForBranch(useGitHubPrStatusStore.getState().entries, "/repo", "feature")
expect(freshest?.pr?.title).toBe("current")
expect(freshest?.checks?.pending).toBe(0)
})
test("rejects a response after params change", async () => {
const request = deferred<GitHubPullRequestStatus>()
const github = { prStatus: () => request.promise } as unknown as RuntimeAPIs["github"]
@@ -238,11 +238,11 @@ const findResolvedSiblingEntry = (
* instead of a single key: the entry being actively watched/refreshed may be
* keyed by a concrete remote while the 'auto' entry goes stale.
*/
export const getFreshestPrStatusForBranch = (
const getFreshestPrEntryForBranch = (
entries: Record<string, PrStatusEntry>,
directory: string,
branch: string,
): GitHubPullRequestStatus | null => {
): PrStatusEntry | null => {
const runtimeKey = getRuntimeKey();
let best: PrStatusEntry | null = null;
for (const [key, entry] of Object.entries(entries)) {
@@ -260,7 +260,15 @@ export const getFreshestPrStatusForBranch = (
best = entry;
}
}
return best?.status ?? null;
return best;
};
export const getFreshestPrStatusForBranch = (
entries: Record<string, PrStatusEntry>,
directory: string,
branch: string,
): GitHubPullRequestStatus | null => {
return getFreshestPrEntryForBranch(entries, directory, branch)?.status ?? null;
};
const getKeysBySignature = (entries: Record<string, PrStatusEntry>, signature: string): string[] => {
@@ -951,23 +959,39 @@ const summarySignature = (s: PrVisualSummary): string =>
const PR_SUMMARY_CACHE_MAX_ENTRIES = 300;
const prSummaryCacheByKey = new Map<string, { sig: string; summary: PrVisualSummary }>();
const getCachedPrSummary = (cacheKey: string, entry: PrStatusEntry | null | undefined): PrVisualSummary | null => {
const summary = entry ? deriveSummary(entry) : null;
if (!summary) {
prSummaryCacheByKey.delete(cacheKey);
return null;
}
const sig = summarySignature(summary);
const cached = prSummaryCacheByKey.get(cacheKey);
if (cached?.sig === sig) return cached.summary;
if (!cached && prSummaryCacheByKey.size >= PR_SUMMARY_CACHE_MAX_ENTRIES) {
const oldestKey = prSummaryCacheByKey.keys().next().value;
if (oldestKey !== undefined) prSummaryCacheByKey.delete(oldestKey);
}
prSummaryCacheByKey.set(cacheKey, { sig, summary });
return summary;
};
export const usePrVisualSummary = (key: string | null): PrVisualSummary | null => {
return useGitHubPrStatusStore((state) => {
if (!key) return null;
const entry = state.entries[key];
const summary = entry ? deriveSummary(entry) : null;
if (!summary) {
prSummaryCacheByKey.delete(key);
return null;
}
const sig = summarySignature(summary);
const cached = prSummaryCacheByKey.get(key);
if (cached && cached.sig === sig) return cached.summary;
if (!cached && prSummaryCacheByKey.size >= PR_SUMMARY_CACHE_MAX_ENTRIES) {
const oldestKey = prSummaryCacheByKey.keys().next().value;
if (oldestKey !== undefined) prSummaryCacheByKey.delete(oldestKey);
}
prSummaryCacheByKey.set(key, { sig, summary });
return summary;
return getCachedPrSummary(key, state.entries[key]);
});
};
export const useFreshestPrVisualSummaryForBranch = (
directory: string | null,
branch: string | null,
): PrVisualSummary | null => {
const cacheKey = directory && branch ? JSON.stringify(['branch', getRuntimeKey(), directory, branch]) : null;
return useGitHubPrStatusStore((state) => {
if (!directory || !branch || !cacheKey) return null;
return getCachedPrSummary(cacheKey, getFreshestPrEntryForBranch(state.entries, directory, branch));
});
};