fix(github): keep merged PRs as branch history instead of hiding them

Branch status resolves an open PR across the whole fork network first, so a
merged fork PR can never hide an open upstream PR for the same head. Only when
no target has an open PR does the branch's newest closed/merged PR come back,
as history.

The panel shows that history as a compact note and offers creating the next PR
below it, instead of either sticking on a terminal PR or going blank after a
merge. Terminal associations stay persisted for reload continuity but are never
treated as authority: they revalidate on the discovery cadence and on focus.

History is looked up only for the branch's own remote and name, and remembered
per repo+branch, so the extra lookup cannot exhaust the route's resolve budget.
The checks summary and merge-permission lookup are skipped for a closed or
merged PR, where neither is actionable.
This commit is contained in:
Bohdan Triapitsyn
2026-08-15 17:56:55 +03:00
parent 52ac367b1e
commit 268f9ea9f2
21 changed files with 405 additions and 190 deletions
+36 -30
View File
@@ -574,10 +574,17 @@ export function registerGitHubRoutes(app) {
return res.json({ connected: true, repo: searchRepo, branch, pr: null, checks: null, canMerge: false });
}
const isMerged = Boolean(prData.merged || prData.merged_at);
const prState = isMerged ? 'merged' : (prData.state === 'closed' ? 'closed' : 'open');
// A closed/merged PR is a historical record for this branch: its checks
// are no longer actionable and it can never be merged from here, so skip
// the extra GitHub calls those two fields would cost.
const isHistorical = prState !== 'open';
// Checks summary: prefer check-runs (Actions), fallback to classic statuses.
let checks = null;
const sha = prData.head?.sha;
if (sha) {
if (sha && !isHistorical) {
try {
const runs = await octokit.rest.checks.listForRef({
owner: searchRepo.owner,
@@ -610,38 +617,37 @@ export function registerGitHubRoutes(app) {
// Permission check (best-effort)
let canMerge = false;
try {
const auth = getGitHubAuth();
// gh-CLI tokens have no persisted user record; resolve the login from
// the API once (memoized) so permissions still resolve for them.
let username = auth?.user?.login;
if (!username) {
if (!resolvedAuthLoginPromise) {
resolvedAuthLoginPromise = octokit.rest.users.getAuthenticated()
.then((resp) => resp?.data?.login || null)
.catch(() => {
resolvedAuthLoginPromise = null;
return null;
});
if (!isHistorical) {
try {
const auth = getGitHubAuth();
// gh-CLI tokens have no persisted user record; resolve the login from
// the API once (memoized) so permissions still resolve for them.
let username = auth?.user?.login;
if (!username) {
if (!resolvedAuthLoginPromise) {
resolvedAuthLoginPromise = octokit.rest.users.getAuthenticated()
.then((resp) => resp?.data?.login || null)
.catch(() => {
resolvedAuthLoginPromise = null;
return null;
});
}
username = await resolvedAuthLoginPromise;
}
username = await resolvedAuthLoginPromise;
if (username) {
const perm = await octokit.rest.repos.getCollaboratorPermissionLevel({
owner: searchRepo.owner,
repo: searchRepo.repo,
username,
});
const level = perm?.data?.permission;
canMerge = level === 'admin' || level === 'maintain' || level === 'write';
}
} catch {
canMerge = false;
}
if (username) {
const perm = await octokit.rest.repos.getCollaboratorPermissionLevel({
owner: searchRepo.owner,
repo: searchRepo.repo,
username,
});
const level = perm?.data?.permission;
canMerge = level === 'admin' || level === 'maintain' || level === 'write';
}
} catch {
canMerge = false;
}
const isMerged = Boolean(prData.merged || prData.merged_at);
const mergedState = isMerged ? 'merged' : (prData.state === 'closed' ? 'closed' : 'open');
return res.json({
connected: true,
repo: searchRepo,
@@ -651,7 +657,7 @@ export function registerGitHubRoutes(app) {
title: prData.title,
body: prData.body || '',
url: prData.html_url,
state: mergedState,
state: prState,
draft: Boolean(prData.draft),
base: prData.base?.ref,
head: prData.head?.ref,