@@ -26,7 +26,6 @@ import { cn } from "../lib/utils";
2626import { PRAttentionPanel , PRSummaryMeta } from "./PRSummaryDisplay" ;
2727
2828type ProjectConfig = components [ "schemas" ] [ "ProjectConfig" ] ;
29- type ReviewRun = components [ "schemas" ] [ "ReviewRun" ] ;
3029type PRReviewState = components [ "schemas" ] [ "PRReviewState" ] ;
3130type ReviewsResponse = components [ "schemas" ] [ "ListReviewsResponse" ] ;
3231type OpenReviewerTerminal = ( target : { handleId : string ; harness : string } ) => void ;
@@ -388,7 +387,8 @@ function ReviewsView({
388387 enabled : hasPr ,
389388 refetchInterval : ( query ) => {
390389 const data = query . state . data as ReviewsResponse | undefined ;
391- return data ?. reviews . some ( ( review ) => review . status === "running" ) ? 2500 : false ;
390+ const reviews = data ?. reviews ?? [ ] ;
391+ return reviews . some ( ( review ) => review . status === "running" ) ? 2500 : false ;
392392 } ,
393393 queryFn : async ( ) => {
394394 const { data, error } = await apiClient . GET ( "/api/v1/sessions/{sessionId}/reviews" , {
@@ -423,17 +423,18 @@ function ReviewsView({
423423 onSuccess : ( { data, reused } ) => {
424424 void queryClient . invalidateQueries ( { queryKey : [ "session-reviews" , session . id ] } ) ;
425425 void queryClient . invalidateQueries ( { queryKey : workspaceQueryKey } ) ;
426- if ( reused ) {
427- setReviewNotice ( "Review is already up to date for this commit." ) ;
426+ const started = data ?. reviews ?. find ( ( review ) => review . status === "running" && review . latestRun ) ;
427+ if ( reused || ! started ?. latestRun ) {
428+ setReviewNotice ( "No needed reviews were started." ) ;
428429 return ;
429430 }
430431 if ( data ?. reviewerHandleId ) {
431- const harness = latestReview ( data . reviews ) ? .harness || "reviewer" ;
432+ const harness = started . latestRun . harness || "reviewer" ;
432433 onOpenReviewerTerminal ?.( { handleId : data . reviewerHandleId , harness } ) ;
433434 }
434435 } ,
435436 } ) ;
436- const reviews = reviewsQuery . data ?. reviews ?? [ ] ;
437+ const reviewStates = reviewsQuery . data ?. reviews ?? [ ] ;
437438
438439 return (
439440 < div role = "tabpanel" >
@@ -446,7 +447,7 @@ function ReviewsView({
446447 onOpenTerminal = { onOpenReviewerTerminal }
447448 onTrigger = { ( ) => triggerReview . mutate ( ) }
448449 reviewerHandleId = { reviewsQuery . data ?. reviewerHandleId ?? "" }
449- reviews = { reviews }
450+ reviewStates = { reviewStates }
450451 notice = { reviewNotice }
451452 session = { session }
452453 />
@@ -463,7 +464,7 @@ function projectConfig(project: components["schemas"]["ProjectOrDegraded"] | und
463464function ReviewPanel ( {
464465 session,
465466 config,
466- reviews ,
467+ reviewStates ,
467468 reviewerHandleId,
468469 isLoading,
469470 isTriggering,
@@ -474,7 +475,7 @@ function ReviewPanel({
474475} : {
475476 session : WorkspaceSession ;
476477 config ?: ProjectConfig ;
477- reviews : PRReviewState [ ] ;
478+ reviewStates : PRReviewState [ ] ;
478479 reviewerHandleId : string ;
479480 isLoading : boolean ;
480481 isTriggering : boolean ;
@@ -490,111 +491,98 @@ function ReviewPanel({
490491 return < p className = "inspector-empty" > Loading reviews...</ p > ;
491492 }
492493
493- const latest = latestReview ( reviews ) ;
494+ const latest = reviewStates . find ( ( review ) => review . latestRun ) ?. latestRun ;
494495 const harness = latest ?. harness || config ?. reviewers ?. [ 0 ] ?. harness || session . provider || "reviewer" ;
496+ const terminalEnabled = Boolean ( reviewerHandleId && onOpenTerminal ) ;
495497
496498 return (
497499 < div className = "reviewer-list" >
498500 { error ? < p className = "reviewer-error" > { apiErrorMessage ( error , "Review request failed" ) } </ p > : null }
499501 { notice ? < p className = "reviewer-notice" > { notice } </ p > : null }
500- < ReviewerCard
501- handleId = { reviewerHandleId }
502- harness = { harness }
503- isTriggering = { isTriggering }
504- onOpenTerminal = { onOpenTerminal }
505- onTrigger = { onTrigger }
506- review = { latest }
507- />
502+ < div className = "reviewer-card" >
503+ < div className = "reviewer-card__top" >
504+ < div className = "reviewer-card__name" >
505+ < Shield aria-hidden = "true" />
506+ < span > { harness } </ span >
507+ </ div >
508+ < span className = "reviewer-status reviewer-status--neutral" > { reviewStates . length } PRs</ span >
509+ </ div >
510+ < div className = "reviewer-card__actions" >
511+ < button
512+ className = "reviewer-card__action reviewer-card__action--primary"
513+ disabled = { isTriggering }
514+ onClick = { onTrigger }
515+ type = "button"
516+ >
517+ < Play aria-hidden = "true" />
518+ { isTriggering ? "Starting..." : "Run needed reviews" }
519+ </ button >
520+ < button
521+ className = "reviewer-card__action"
522+ disabled = { ! terminalEnabled }
523+ onClick = { ( ) => {
524+ if ( ! terminalEnabled ) return ;
525+ onOpenTerminal ?.( { handleId : reviewerHandleId , harness } ) ;
526+ } }
527+ type = "button"
528+ >
529+ < Terminal aria-hidden = "true" />
530+ Open terminal
531+ </ button >
532+ </ div >
533+ </ div >
534+ < div className = "flex flex-col gap-2" >
535+ { reviewStates . length === 0 ? < p className = "inspector-empty" > No review state loaded yet.</ p > : null }
536+ { reviewStates . map ( ( reviewState ) => (
537+ < ReviewStateCard key = { `${ reviewState . prUrl } :${ reviewState . targetSha } ` } reviewState = { reviewState } />
538+ ) ) }
539+ </ div >
508540 </ div >
509541 ) ;
510542}
511543
512- function latestReview ( reviews : PRReviewState [ ] ) : ReviewRun | undefined {
513- return reviews
514- . map ( ( review ) => review . latestRun )
515- . filter ( ( review ) : review is ReviewRun => Boolean ( review ) )
516- . sort ( ( a , b ) => Date . parse ( b . createdAt ) - Date . parse ( a . createdAt ) ) [ 0 ] ;
517- }
518-
519- function ReviewerCard ( {
520- harness,
521- review,
522- handleId,
523- isTriggering,
524- onTrigger,
525- onOpenTerminal,
526- } : {
527- harness : string ;
528- review ?: ReviewRun ;
529- handleId : string ;
530- isTriggering : boolean ;
531- onTrigger : ( ) => void ;
532- onOpenTerminal ?: OpenReviewerTerminal ;
533- } ) {
534- const status = reviewStatus ( review ) ;
535- const terminalEnabled = Boolean ( handleId && onOpenTerminal ) ;
536- const runLabel = review ? "Re-run review" : "Run review" ;
537-
544+ function ReviewStateCard ( { reviewState } : { reviewState : PRReviewState } ) {
545+ const status = reviewStateStatus ( reviewState ) ;
538546 return (
539- < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` ) } >
547+ < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` , reviewState . status === "ineligible" && "opacity-70" ) } >
540548 < div className = "reviewer-card__top" >
541549 < div className = "reviewer-card__name" >
542- < Shield aria-hidden = "true" />
543- < span > { harness } </ span >
550+ < GitPullRequest aria-hidden = "true" />
551+ < span > PR # { reviewState . prNumber } </ span >
544552 </ div >
545553 < span className = { cn ( "reviewer-status" , `reviewer-status--${ status . tone } ` ) } >
546554 { status . icon }
547555 { status . label }
548556 </ span >
549557 </ div >
550- < div className = "reviewer-card__actions" >
551- < button
552- className = "reviewer-card__action reviewer-card__action--primary"
553- disabled = { isTriggering }
554- onClick = { onTrigger }
555- type = "button"
556- >
557- < Play aria-hidden = "true" />
558- { isTriggering ? "Starting..." : runLabel }
559- </ button >
560- { review ? (
561- < button
562- className = "reviewer-card__action"
563- disabled = { ! terminalEnabled }
564- onClick = { ( ) => {
565- if ( ! terminalEnabled ) return ;
566- onOpenTerminal ?.( { handleId, harness } ) ;
567- } }
568- type = "button"
569- >
570- < Terminal aria-hidden = "true" />
571- Open terminal
572- </ button >
573- ) : null }
558+ < div className = "mt-2 min-w-0 truncate font-mono text-[10.5px] text-passive" >
559+ < a href = { reviewState . prUrl } target = "_blank" rel = "noopener noreferrer" className = "text-accent hover:underline" >
560+ { reviewState . prUrl }
561+ </ a >
562+ { reviewState . targetSha ? < span className = "ml-2" > { reviewState . targetSha . slice ( 0 , 7 ) } </ span > : null }
574563 </ div >
575564 </ div >
576565 ) ;
577566}
578567
579- function reviewStatus ( review ?: ReviewRun ) : {
568+ function reviewStateStatus ( reviewState : PRReviewState ) : {
580569 label : string ;
581570 tone : "neutral" | "running" | "success" | "danger" ;
582571 icon : ReactNode ;
583572} {
584- if ( ! review ) return { label : "Not run" , tone : "neutral" , icon : null } ;
585- if ( review . status === "running" ) {
586- return { label : "Running" , tone : "running" , icon : < Play aria-hidden = "true" /> } ;
587- }
588- if ( review . status === "failed" ) {
589- return { label : "Failed" , tone : "danger" , icon : < AlertCircle aria-hidden = "true" /> } ;
590- }
591- if ( review . verdict === "approved" ) {
592- return { label : "Approved" , tone : "success" , icon : < CheckCircle2 aria-hidden = "true" /> } ;
593- }
594- if ( review . verdict === "changes_requested" ) {
595- return { label : "Changes requested" , tone : "danger" , icon : < CircleMinus aria-hidden = "true" /> } ;
573+ switch ( reviewState . status ) {
574+ case "needs_review" :
575+ return { label : "Needs review" , tone : "neutral" , icon : null } ;
576+ case "running" :
577+ return { label : "Running" , tone : "running" , icon : < Play aria-hidden = "true" /> } ;
578+ case "up_to_date" :
579+ return { label : "Up to date" , tone : "success" , icon : < CheckCircle2 aria-hidden = "true" /> } ;
580+ case "changes_requested" :
581+ return { label : "Changes requested" , tone : "danger" , icon : < CircleMinus aria-hidden = "true" /> } ;
582+ case "ineligible" :
583+ return { label : "Ineligible" , tone : "neutral" , icon : < AlertCircle aria-hidden = "true" /> } ;
596584 }
597- return { label : "Complete" , tone : "success " , icon : < CheckCircle2 aria-hidden = "true" /> } ;
585+ return { label : reviewState . status , tone : "neutral " , icon : null } ;
598586}
599587
600588function BrowserView ( {
0 commit comments