mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
Tighten GitHub refresh UI and pacing
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -24,6 +24,7 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
const repo = useRepoById(activeWorktree?.repoId ?? null)
|
||||
const prCache = useAppStore((s) => s.prCache)
|
||||
const fetchPRForBranch = useAppStore((s) => s.fetchPRForBranch)
|
||||
const enqueueGitHubPRRefresh = useAppStore((s) => s.enqueueGitHubPRRefresh)
|
||||
const gitConflictOperationByWorktree = useAppStore((s) => s.gitConflictOperationByWorktree)
|
||||
|
||||
// Why: the sidebar stays mounted when closed (for performance). Gate
|
||||
@@ -301,6 +302,43 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
}
|
||||
}, [repo, branch, linkedPR, fetchPRForBranch, fetchPRChecks, fetchComments])
|
||||
|
||||
const handleEntryRefresh = useCallback(async () => {
|
||||
if (!repo || !branch || !activeWorktreeId) {
|
||||
return
|
||||
}
|
||||
// Why: entering the Checks tab is automatic UI behavior, not an explicit
|
||||
// user refresh. Route PR refresh through the coordinator so rate-limit
|
||||
// guards still apply, while checks/comments can refresh from cached PR data.
|
||||
enqueueGitHubPRRefresh(activeWorktreeId, 'active', 80)
|
||||
|
||||
if (!pr) {
|
||||
setChecks([])
|
||||
setComments([])
|
||||
return
|
||||
}
|
||||
|
||||
const refreshedChecks = fetchPRChecks(repo.path, pr.number, branch, pr.headSha, {
|
||||
force: true
|
||||
}).then(
|
||||
(result) => {
|
||||
setChecks(result)
|
||||
const signature = JSON.stringify(result.map((c) => `${c.name}:${c.status}:${c.conclusion}`))
|
||||
pollIntervalRef.current =
|
||||
signature === prevChecksRef.current
|
||||
? Math.min(pollIntervalRef.current * 2, 120_000)
|
||||
: 30_000
|
||||
prevChecksRef.current = signature
|
||||
},
|
||||
(err) => {
|
||||
console.warn('Failed to fetch PR checks:', err)
|
||||
setChecks([])
|
||||
}
|
||||
)
|
||||
setChecksLoading(true)
|
||||
const refreshedComments = fetchComments({ force: true, prNumberOverride: pr.number })
|
||||
await Promise.all([refreshedChecks.finally(() => setChecksLoading(false)), refreshedComments])
|
||||
}, [repo, branch, activeWorktreeId, enqueueGitHubPRRefresh, pr, fetchPRChecks, fetchComments])
|
||||
|
||||
// Why: force a freshness check on each "entry" into the Checks tab so PRs
|
||||
// opened outside Orca, externally force-pushed heads, and stale checks/comments
|
||||
// appear without waiting for the cache TTL. The grace window suppresses
|
||||
@@ -340,8 +378,8 @@ export default function ChecksPanel(): React.JSX.Element {
|
||||
// a fresh baseline rather than colliding with the previous PR's backoff.
|
||||
pollIntervalRef.current = 30_000
|
||||
prevChecksRef.current = ''
|
||||
void handleRefresh()
|
||||
}, [entryKey, prFetchedAt, checksFetchedAt, commentsFetchedAt, prNumber, handleRefresh])
|
||||
void handleEntryRefresh()
|
||||
}, [entryKey, prFetchedAt, checksFetchedAt, commentsFetchedAt, prNumber, handleEntryRefresh])
|
||||
|
||||
const handleStartEdit = useCallback(() => {
|
||||
if (!pr) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Bell,
|
||||
GitMerge,
|
||||
LoaderCircle,
|
||||
PauseCircle,
|
||||
CircleCheck,
|
||||
CircleX,
|
||||
Server,
|
||||
@@ -64,7 +65,6 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
}: WorktreeCardProps) {
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const updateWorktreeMeta = useAppStore((s) => s.updateWorktreeMeta)
|
||||
const enqueueGitHubPRRefresh = useAppStore((s) => s.enqueueGitHubPRRefresh)
|
||||
const fetchIssue = useAppStore((s) => s.fetchIssue)
|
||||
const cardProps = useAppStore((s) => s.worktreeCardProperties)
|
||||
const handleEditIssue = useCallback(
|
||||
@@ -278,33 +278,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
]
|
||||
)
|
||||
|
||||
const showPR = cardProps.includes('pr')
|
||||
const showCI = cardProps.includes('ci')
|
||||
const showIssue = cardProps.includes('issue')
|
||||
|
||||
// Skip GitHub fetches when the corresponding card sections are hidden.
|
||||
// This preference is purely presentational, so background refreshes would
|
||||
// spend rate limit budget on data the user cannot see.
|
||||
useEffect(() => {
|
||||
if (repo && !isFolder && !worktree.isBare && prCacheKey && isActive && (showPR || showCI)) {
|
||||
// Why: pass linkedPR so worktrees created from a PR (whose new local
|
||||
// branch differs from the PR's head ref) still resolve their PR via
|
||||
// a number-based fallback in the main process.
|
||||
enqueueGitHubPRRefresh(worktree.id, 'active', 80)
|
||||
}
|
||||
}, [
|
||||
repo,
|
||||
isFolder,
|
||||
worktree.isBare,
|
||||
worktree.id,
|
||||
worktree.linkedPR,
|
||||
enqueueGitHubPRRefresh,
|
||||
isActive,
|
||||
prCacheKey,
|
||||
showPR,
|
||||
showCI
|
||||
])
|
||||
|
||||
// Same rationale for issues: once that section is hidden, polling only burns
|
||||
// GitHub calls and keeps stale-but-invisible data warm for no user benefit.
|
||||
useEffect(() => {
|
||||
@@ -530,19 +505,34 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex items-center opacity-80 hover:opacity-100 transition-opacity">
|
||||
{pr.checksStatus === 'success' && (
|
||||
<CircleCheck className="size-3.5 text-emerald-500" />
|
||||
{prRefreshState?.status === 'in-flight' && (
|
||||
<LoaderCircle className="size-3.5 text-muted-foreground animate-spin" />
|
||||
)}
|
||||
{pr.checksStatus === 'failure' && (
|
||||
<CircleX className="size-3.5 text-rose-500" />
|
||||
)}
|
||||
{pr.checksStatus === 'pending' && (
|
||||
<LoaderCircle className="size-3.5 text-amber-500 animate-spin" />
|
||||
{prRefreshState?.status === 'paused' && (
|
||||
<PauseCircle className="size-3.5 text-muted-foreground" />
|
||||
)}
|
||||
{!['in-flight', 'paused'].includes(prRefreshState?.status ?? '') &&
|
||||
pr.checksStatus === 'success' && (
|
||||
<CircleCheck className="size-3.5 text-emerald-500" />
|
||||
)}
|
||||
{!['in-flight', 'paused'].includes(prRefreshState?.status ?? '') &&
|
||||
pr.checksStatus === 'failure' && (
|
||||
<CircleX className="size-3.5 text-rose-500" />
|
||||
)}
|
||||
{!['in-flight', 'paused'].includes(prRefreshState?.status ?? '') &&
|
||||
pr.checksStatus === 'pending' && (
|
||||
<LoaderCircle className="size-3.5 text-amber-500 animate-spin" />
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
<span>CI checks {checksLabel(pr.checksStatus).toLowerCase()}</span>
|
||||
<span>
|
||||
{prRefreshState?.status === 'in-flight'
|
||||
? 'Refreshing CI checks'
|
||||
: prRefreshState?.status === 'paused'
|
||||
? 'CI refresh paused'
|
||||
: `CI checks ${checksLabel(pr.checksStatus).toLowerCase()}`}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user