@@ -26,7 +26,7 @@ import { cn } from "../lib/utils";
2626import { PRAttentionPanel , PRSummaryMeta } from "./PRSummaryDisplay" ;
2727
2828type ProjectConfig = components [ "schemas" ] [ "ProjectConfig" ] ;
29- type ReviewRun = components [ "schemas" ] [ "ReviewRun " ] ;
29+ type PRReviewItem = components [ "schemas" ] [ "PRReviewItem " ] ;
3030type ReviewsResponse = components [ "schemas" ] [ "ListReviewsResponse" ] ;
3131type OpenReviewerTerminal = ( target : { handleId : string ; harness : string } ) => void ;
3232
@@ -387,7 +387,8 @@ function ReviewsView({
387387 enabled : hasPr ,
388388 refetchInterval : ( query ) => {
389389 const data = query . state . data as ReviewsResponse | undefined ;
390- 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 ;
391392 } ,
392393 queryFn : async ( ) => {
393394 const { data, error } = await apiClient . GET ( "/api/v1/sessions/{sessionId}/reviews" , {
@@ -422,16 +423,18 @@ function ReviewsView({
422423 onSuccess : ( { data, reused } ) => {
423424 void queryClient . invalidateQueries ( { queryKey : [ "session-reviews" , session . id ] } ) ;
424425 void queryClient . invalidateQueries ( { queryKey : workspaceQueryKey } ) ;
425- if ( reused ) {
426- 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." ) ;
427429 return ;
428430 }
429431 if ( data ?. reviewerHandleId ) {
430- onOpenReviewerTerminal ?.( { handleId : data . reviewerHandleId , harness : data . review . harness || "reviewer" } ) ;
432+ const harness = started . latestRun . harness || "reviewer" ;
433+ onOpenReviewerTerminal ?.( { handleId : data . reviewerHandleId , harness } ) ;
431434 }
432435 } ,
433436 } ) ;
434- const reviews = reviewsQuery . data ?. reviews ?? [ ] ;
437+ const reviewItems = reviewsQuery . data ?. reviews ?? [ ] ;
435438
436439 return (
437440 < div role = "tabpanel" >
@@ -444,7 +447,7 @@ function ReviewsView({
444447 onOpenTerminal = { onOpenReviewerTerminal }
445448 onTrigger = { ( ) => triggerReview . mutate ( ) }
446449 reviewerHandleId = { reviewsQuery . data ?. reviewerHandleId ?? "" }
447- reviews = { reviews }
450+ reviewItems = { reviewItems }
448451 notice = { reviewNotice }
449452 session = { session }
450453 />
@@ -461,7 +464,7 @@ function projectConfig(project: components["schemas"]["ProjectOrDegraded"] | und
461464function ReviewPanel ( {
462465 session,
463466 config,
464- reviews ,
467+ reviewItems ,
465468 reviewerHandleId,
466469 isLoading,
467470 isTriggering,
@@ -472,7 +475,7 @@ function ReviewPanel({
472475} : {
473476 session : WorkspaceSession ;
474477 config ?: ProjectConfig ;
475- reviews : ReviewRun [ ] ;
478+ reviewItems : PRReviewItem [ ] ;
476479 reviewerHandleId : string ;
477480 isLoading : boolean ;
478481 isTriggering : boolean ;
@@ -488,108 +491,98 @@ function ReviewPanel({
488491 return < p className = "inspector-empty" > Loading reviews...</ p > ;
489492 }
490493
491- const latest = latestReview ( reviews ) ;
494+ const latest = reviewItems . find ( ( review ) => review . latestRun ) ?. latestRun ;
492495 const harness = latest ?. harness || config ?. reviewers ?. [ 0 ] ?. harness || session . provider || "reviewer" ;
496+ const terminalEnabled = Boolean ( reviewerHandleId && onOpenTerminal ) ;
493497
494498 return (
495499 < div className = "reviewer-list" >
496500 { error ? < p className = "reviewer-error" > { apiErrorMessage ( error , "Review request failed" ) } </ p > : null }
497501 { notice ? < p className = "reviewer-notice" > { notice } </ p > : null }
498- < ReviewerCard
499- handleId = { reviewerHandleId }
500- harness = { harness }
501- isTriggering = { isTriggering }
502- onOpenTerminal = { onOpenTerminal }
503- onTrigger = { onTrigger }
504- review = { latest }
505- />
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" > { reviewItems . 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+ { reviewItems . length === 0 ? < p className = "inspector-empty" > No review state loaded yet.</ p > : null }
536+ { reviewItems . map ( ( item ) => (
537+ < ReviewItemCard key = { `${ item . prUrl } :${ item . targetSha } ` } item = { item } />
538+ ) ) }
539+ </ div >
506540 </ div >
507541 ) ;
508542}
509543
510- function latestReview ( reviews : ReviewRun [ ] ) : ReviewRun | undefined {
511- return [ ...reviews ] . sort ( ( a , b ) => Date . parse ( b . createdAt ) - Date . parse ( a . createdAt ) ) [ 0 ] ;
512- }
513-
514- function ReviewerCard ( {
515- harness,
516- review,
517- handleId,
518- isTriggering,
519- onTrigger,
520- onOpenTerminal,
521- } : {
522- harness : string ;
523- review ?: ReviewRun ;
524- handleId : string ;
525- isTriggering : boolean ;
526- onTrigger : ( ) => void ;
527- onOpenTerminal ?: OpenReviewerTerminal ;
528- } ) {
529- const status = reviewStatus ( review ) ;
530- const terminalEnabled = Boolean ( handleId && onOpenTerminal ) ;
531- const runLabel = review ? "Re-run review" : "Run review" ;
532-
544+ function ReviewItemCard ( { item } : { item : PRReviewItem } ) {
545+ const status = reviewItemStatus ( item ) ;
533546 return (
534- < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` ) } >
547+ < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` , item . status === "ineligible" && "opacity-70" ) } >
535548 < div className = "reviewer-card__top" >
536549 < div className = "reviewer-card__name" >
537- < Shield aria-hidden = "true" />
538- < span > { harness } </ span >
550+ < GitPullRequest aria-hidden = "true" />
551+ < span > PR # { item . prNumber } </ span >
539552 </ div >
540553 < span className = { cn ( "reviewer-status" , `reviewer-status--${ status . tone } ` ) } >
541554 { status . icon }
542555 { status . label }
543556 </ span >
544557 </ div >
545- < div className = "reviewer-card__actions" >
546- < button
547- className = "reviewer-card__action reviewer-card__action--primary"
548- disabled = { isTriggering }
549- onClick = { onTrigger }
550- type = "button"
551- >
552- < Play aria-hidden = "true" />
553- { isTriggering ? "Starting..." : runLabel }
554- </ button >
555- { review ? (
556- < button
557- className = "reviewer-card__action"
558- disabled = { ! terminalEnabled }
559- onClick = { ( ) => {
560- if ( ! terminalEnabled ) return ;
561- onOpenTerminal ?.( { handleId, harness } ) ;
562- } }
563- type = "button"
564- >
565- < Terminal aria-hidden = "true" />
566- Open terminal
567- </ button >
568- ) : null }
558+ < div className = "mt-2 min-w-0 truncate font-mono text-[10.5px] text-passive" >
559+ < a href = { item . prUrl } target = "_blank" rel = "noopener noreferrer" className = "text-accent hover:underline" >
560+ { item . prUrl }
561+ </ a >
562+ { item . targetSha ? < span className = "ml-2" > { item . targetSha . slice ( 0 , 7 ) } </ span > : null }
569563 </ div >
570564 </ div >
571565 ) ;
572566}
573567
574- function reviewStatus ( review ?: ReviewRun ) : {
568+ function reviewItemStatus ( item : PRReviewItem ) : {
575569 label : string ;
576570 tone : "neutral" | "running" | "success" | "danger" ;
577571 icon : ReactNode ;
578572} {
579- if ( ! review ) return { label : "Not run" , tone : "neutral" , icon : null } ;
580- if ( review . status === "running" ) {
581- return { label : "Running" , tone : "running" , icon : < Play aria-hidden = "true" /> } ;
582- }
583- if ( review . status === "failed" ) {
584- return { label : "Failed" , tone : "danger" , icon : < AlertCircle aria-hidden = "true" /> } ;
585- }
586- if ( review . verdict === "approved" ) {
587- return { label : "Approved" , tone : "success" , icon : < CheckCircle2 aria-hidden = "true" /> } ;
588- }
589- if ( review . verdict === "changes_requested" ) {
590- return { label : "Changes requested" , tone : "danger" , icon : < CircleMinus aria-hidden = "true" /> } ;
573+ switch ( item . 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" /> } ;
591584 }
592- return { label : "Complete" , tone : "success " , icon : < CheckCircle2 aria-hidden = "true" /> } ;
585+ return { label : item . status , tone : "neutral " , icon : null } ;
593586}
594587
595588function BrowserView ( {
0 commit comments