@@ -51,7 +51,6 @@ import {
5151 lazy ,
5252 Suspense ,
5353 useCallback ,
54- useEffect ,
5554 useMemo ,
5655 useRef ,
5756 useState ,
@@ -113,10 +112,13 @@ import type {
113112 PullWorkflowApproval ,
114113 TimelineEvent ,
115114} from "#/lib/github.types" ;
115+ import { removePullFromOpenViews } from "#/lib/github-query-updates" ;
116+ import { githubRevalidationSignalKeys } from "#/lib/github-revalidation" ;
116117import {
117118 mergeIssueStateIntoCloseEvent ,
118119 parseCloseReason ,
119120} from "#/lib/timeline-close-reason" ;
121+ import { useGitHubSignalStream } from "#/lib/use-github-signal-stream" ;
120122import { usePrefersNoHover } from "#/lib/use-prefers-no-hover" ;
121123import { checkPermissionWarning } from "#/lib/warning-store" ;
122124
@@ -334,16 +336,53 @@ function MergeStatusSection({
334336 prTitle : string ;
335337 firstCommitMessage ?: string ;
336338} ) {
339+ const input = useMemo (
340+ ( ) => ( { owner, repo, pullNumber } ) ,
341+ [ owner , repo , pullNumber ] ,
342+ ) ;
343+ const statusQueryKey = useMemo (
344+ ( ) => githubQueryKeys . pulls . status ( scope , input ) ,
345+ [ scope , input ] ,
346+ ) ;
337347 const statusQuery = useQuery ( {
338- ...githubPullStatusQueryOptions ( scope , { owner , repo , pullNumber } ) ,
348+ ...githubPullStatusQueryOptions ( scope , input ) ,
339349 } ) ;
350+ const workflowStatusRefreshTargets = useMemo (
351+ ( ) => [
352+ {
353+ queryKey : statusQueryKey ,
354+ signalKeys : [
355+ githubRevalidationSignalKeys . pullEntity ( input ) ,
356+ githubRevalidationSignalKeys . repoProtection ( {
357+ owner : input . owner ,
358+ repo : input . repo ,
359+ } ) ,
360+ githubRevalidationSignalKeys . repoStatuses ( {
361+ owner : input . owner ,
362+ repo : input . repo ,
363+ } ) ,
364+ ...( statusQuery . data ?. pendingWorkflowApprovals ?? [ ] ) . map (
365+ ( approval ) =>
366+ githubRevalidationSignalKeys . workflowRunEntity ( {
367+ owner : input . owner ,
368+ repo : input . repo ,
369+ runId : approval . workflowRunId ,
370+ } ) ,
371+ ) ,
372+ ] ,
373+ } ,
374+ ] ,
375+ [ input , statusQuery . data ?. pendingWorkflowApprovals , statusQueryKey ] ,
376+ ) ;
377+ useGitHubSignalStream ( workflowStatusRefreshTargets ) ;
340378
341379 const status = statusQuery . data ?? null ;
342380
343381 if ( ! status ) return < MergeStatusSkeleton /> ;
344382
345383 return (
346384 < MergeStatusCard
385+ scope = { scope }
347386 status = { status }
348387 owner = { owner }
349388 repo = { repo }
@@ -440,13 +479,15 @@ function MergedBranchBanner({
440479}
441480
442481function MergeStatusCard ( {
482+ scope,
443483 status,
444484 owner,
445485 repo,
446486 pullNumber,
447487 prTitle,
448488 firstCommitMessage,
449489} : {
490+ scope : GitHubQueryScope ;
450491 status : PullStatus ;
451492 owner : string ;
452493 repo : string ;
@@ -506,6 +547,7 @@ function MergeStatusCard({
506547 { /* Checks section */ }
507548 { ( checks . total > 0 || pendingWorkflowApprovals . length > 0 ) && (
508549 < ChecksSection
550+ scope = { scope }
509551 checks = { checks }
510552 checkRuns = { checkRuns }
511553 pendingWorkflowApprovals = { pendingWorkflowApprovals }
@@ -545,6 +587,7 @@ function MergeStatusCard({
545587
546588 { /* Merge action footer */ }
547589 < MergeFooter
590+ scope = { scope }
548591 isMergeBlocked = { isMergeBlocked }
549592 canMerge = { canMerge }
550593 bypass = { bypass }
@@ -754,6 +797,7 @@ function ReviewsSection({
754797// ── Checks section ──────────────────────────────────────────────────
755798
756799function ChecksSection ( {
800+ scope,
757801 checks,
758802 checkRuns,
759803 pendingWorkflowApprovals,
@@ -763,6 +807,7 @@ function ChecksSection({
763807 repo,
764808 pullNumber,
765809} : {
810+ scope : GitHubQueryScope ;
766811 checks : PullStatus [ "checks" ] ;
767812 checkRuns : PullCheckRun [ ] ;
768813 pendingWorkflowApprovals : PullWorkflowApproval [ ] ;
@@ -776,6 +821,10 @@ function ChecksSection({
776821 const [ isRerunning , setIsRerunning ] = useState ( false ) ;
777822 const [ isApproving , setIsApproving ] = useState ( false ) ;
778823 const queryClient = useQueryClient ( ) ;
824+ const statusQueryKey = useMemo (
825+ ( ) => githubQueryKeys . pulls . status ( scope , { owner, repo, pullNumber } ) ,
826+ [ scope , owner , repo , pullNumber ] ,
827+ ) ;
779828
780829 const pendingTotal = checks . pending + checks . expected ;
781830 const approvalCount = pendingWorkflowApprovals . length ;
@@ -894,33 +943,27 @@ function ChecksSection({
894943 toast . warning (
895944 `Approved ${ approved } workflow${ approved !== 1 ? "s" : "" } , but ${ failed } failed` ,
896945 ) ;
946+ } else {
947+ toast . success (
948+ `Approved ${ pendingWorkflowApprovals . length } workflow${ pendingWorkflowApprovals . length !== 1 ? "s" : "" } ` ,
949+ ) ;
897950 }
898- // Keep the button in loading state; the effect below resets it once the
899- // workflow_run webhook invalidates the cache and the pending list drains.
900- await queryClient . invalidateQueries ( { queryKey : [ "github" ] } ) ;
951+ await queryClient . invalidateQueries ( {
952+ queryKey : statusQueryKey ,
953+ exact : true ,
954+ refetchType : "active" ,
955+ } ) ;
901956 } else {
902957 toast . error ( result . error ) ;
903958 checkPermissionWarning ( result , `${ owner } /${ repo } ` ) ;
904- setIsApproving ( false ) ;
905959 }
906960 } catch {
907961 toast . error ( "Failed to approve workflows" ) ;
962+ } finally {
908963 setIsApproving ( false ) ;
909964 }
910965 } ;
911966
912- // Reset the approving state when the pending list drains (webhook arrived) or
913- // after a safety timeout to avoid a permanently-stuck spinner.
914- useEffect ( ( ) => {
915- if ( ! isApproving ) return ;
916- if ( pendingWorkflowApprovals . length === 0 ) {
917- setIsApproving ( false ) ;
918- return ;
919- }
920- const timer = setTimeout ( ( ) => setIsApproving ( false ) , 30_000 ) ;
921- return ( ) => clearTimeout ( timer ) ;
922- } , [ isApproving , pendingWorkflowApprovals . length ] ) ;
923-
924967 return (
925968 < Collapsible open = { open } onOpenChange = { setOpen } >
926969 < CollapsibleTrigger asChild >
@@ -1296,6 +1339,7 @@ const MERGE_STRATEGIES = [
12961339] ;
12971340
12981341function MergeFooter ( {
1342+ scope,
12991343 isMergeBlocked,
13001344 canMerge,
13011345 bypass,
@@ -1305,6 +1349,7 @@ function MergeFooter({
13051349 prTitle,
13061350 firstCommitMessage,
13071351} : {
1352+ scope : GitHubQueryScope ;
13081353 isMergeBlocked : boolean ;
13091354 canMerge : boolean ;
13101355 bypass : ReturnType < typeof useMergeBypass > ;
@@ -1357,6 +1402,11 @@ function MergeFooter({
13571402 } ,
13581403 } ) ;
13591404 if ( result . ok ) {
1405+ removePullFromOpenViews ( queryClient , scope , {
1406+ owner,
1407+ repo,
1408+ pullNumber,
1409+ } ) ;
13601410 await queryClient . invalidateQueries ( { queryKey : [ "github" ] } ) ;
13611411 } else {
13621412 toast . error ( result . error ) ;
0 commit comments