diff --git a/src/main/github/client.test.ts b/src/main/github/client.test.ts index 2a418bde0a7..cea82707d7d 100644 --- a/src/main/github/client.test.ts +++ b/src/main/github/client.test.ts @@ -32,7 +32,8 @@ describe('getPRForBranch', () => { url: 'https://github.com/acme/widgets/pull/42', statusCheckRollup: [], updatedAt: '2026-03-28T00:00:00Z', - isDraft: false + isDraft: false, + mergeable: 'MERGEABLE' } ]) }) @@ -58,12 +59,13 @@ describe('getPRForBranch', () => { '--limit', '1', '--json', - 'number,title,state,url,statusCheckRollup,updatedAt,isDraft' + 'number,title,state,url,statusCheckRollup,updatedAt,isDraft,mergeable' ], { cwd: '/repo-root', encoding: 'utf-8' } ) expect(pr?.number).toBe(42) expect(pr?.state).toBe('open') + expect(pr?.mergeable).toBe('MERGEABLE') }) it('falls back to gh pr view when the remote cannot be resolved to GitHub', async () => { @@ -75,7 +77,8 @@ describe('getPRForBranch', () => { url: 'https://example.com/pr/7', statusCheckRollup: [], updatedAt: '2026-03-28T00:00:00Z', - isDraft: true + isDraft: true, + mergeable: 'CONFLICTING' }) }) @@ -89,12 +92,13 @@ describe('getPRForBranch', () => { 'view', 'feature/test', '--json', - 'number,title,state,url,statusCheckRollup,updatedAt,isDraft' + 'number,title,state,url,statusCheckRollup,updatedAt,isDraft,mergeable' ], { cwd: '/non-github-repo', encoding: 'utf-8' } ) expect(pr?.number).toBe(7) expect(pr?.state).toBe('draft') + expect(pr?.mergeable).toBe('CONFLICTING') }) it('returns null for empty branch (e.g. during rebase with detached HEAD)', async () => { diff --git a/src/main/github/client.ts b/src/main/github/client.ts index 2daa407b209..24b6420e00b 100644 --- a/src/main/github/client.ts +++ b/src/main/github/client.ts @@ -1,6 +1,6 @@ import { execFile } from 'child_process' import { promisify } from 'util' -import type { PRInfo, IssueInfo, PRCheckDetail } from '../../shared/types' +import type { PRInfo, PRMergeableState, IssueInfo, PRCheckDetail } from '../../shared/types' import { mapCheckRunRESTStatus, mapCheckRunRESTConclusion, @@ -94,6 +94,7 @@ export async function getPRForBranch(repoPath: string, branch: string): Promise< statusCheckRollup: unknown[] updatedAt: string isDraft?: boolean + mergeable: string } | null = null if (ownerRepo) { @@ -111,7 +112,7 @@ export async function getPRForBranch(repoPath: string, branch: string): Promise< '--limit', '1', '--json', - 'number,title,state,url,statusCheckRollup,updatedAt,isDraft' + 'number,title,state,url,statusCheckRollup,updatedAt,isDraft,mergeable' ], { cwd: repoPath, @@ -128,7 +129,7 @@ export async function getPRForBranch(repoPath: string, branch: string): Promise< 'view', branchName, '--json', - 'number,title,state,url,statusCheckRollup,updatedAt,isDraft' + 'number,title,state,url,statusCheckRollup,updatedAt,isDraft,mergeable' ], { cwd: repoPath, @@ -148,7 +149,8 @@ export async function getPRForBranch(repoPath: string, branch: string): Promise< state: mapPRState(data.state, data.isDraft), url: data.url, checksStatus: deriveCheckStatus(data.statusCheckRollup), - updatedAt: data.updatedAt + updatedAt: data.updatedAt, + mergeable: (data.mergeable as PRMergeableState) ?? 'UNKNOWN' } } catch { return null diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index 129056d74fb..4e09ccc04fe 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -1,19 +1,9 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { - CircleCheck, - CircleX, - LoaderCircle, - CircleDashed, - ExternalLink, - RefreshCw, - Check, - X, - Pencil -} from 'lucide-react' +import { LoaderCircle, ExternalLink, RefreshCw, Check, X, Pencil } from 'lucide-react' import { useAppStore } from '@/store' import { cn } from '@/lib/utils' import PRActions from './PRActions' -import { PullRequestIcon, CHECK_ICON, CHECK_COLOR, prStateColor } from './checks-helpers' +import { PullRequestIcon, prStateColor, MergeConflictWarning, ChecksList } from './checks-helpers' import type { PRInfo, PRCheckDetail } from '../../../../shared/types' export default function ChecksPanel(): React.JSX.Element { @@ -246,30 +236,6 @@ export default function ChecksPanel(): React.JSX.Element { ) } - // ── Sorted checks: failures first, then pending, then success ── - const sortedChecks = [...checks].sort((a, b) => { - const order = { - failure: 0, - timed_out: 0, - cancelled: 1, - pending: 2, - neutral: 3, - skipped: 4, - success: 5 - } - const aOrder = order[a.conclusion ?? 'pending'] ?? 3 - const bOrder = order[b.conclusion ?? 'pending'] ?? 3 - return aOrder - bOrder - }) - - const passingCount = checks.filter((c) => c.conclusion === 'success').length - const failingCount = checks.filter( - (c) => c.conclusion === 'failure' || c.conclusion === 'timed_out' - ).length - const pendingCount = checks.filter( - (c) => c.conclusion === 'pending' || c.conclusion === null - ).length - return (