@@ -27,6 +27,7 @@ import { PRAttentionPanel, PRSummaryMeta } from "./PRSummaryDisplay";
2727
2828type ProjectConfig = components [ "schemas" ] [ "ProjectConfig" ] ;
2929type ReviewRun = components [ "schemas" ] [ "ReviewRun" ] ;
30+ type PRReviewItem = components [ "schemas" ] [ "PRReviewItem" ] ;
3031type ReviewsResponse = components [ "schemas" ] [ "ListReviewsResponse" ] ;
3132type OpenReviewerTerminal = ( target : { handleId : string ; harness : string } ) => void ;
3233
@@ -387,14 +388,16 @@ function ReviewsView({
387388 enabled : hasPr ,
388389 refetchInterval : ( query ) => {
389390 const data = query . state . data as ReviewsResponse | undefined ;
390- return data ?. reviews . some ( ( review ) => review . status === "running" ) ? 2500 : false ;
391+ return data ?. items . some ( ( item ) => item . status === "running" ) || data ?. reviews . some ( ( review ) => review . status === "running" )
392+ ? 2500
393+ : false ;
391394 } ,
392395 queryFn : async ( ) => {
393396 const { data, error } = await apiClient . GET ( "/api/v1/sessions/{sessionId}/reviews" , {
394397 params : { path : { sessionId : session . id } } ,
395398 } ) ;
396399 if ( error ) throw new Error ( apiErrorMessage ( error , "Unable to load reviews" ) ) ;
397- return data ?? ( { reviewerHandleId : "" , reviews : [ ] } satisfies ReviewsResponse ) ;
400+ return data ?? ( { reviewerHandleId : "" , reviews : [ ] , items : [ ] } satisfies ReviewsResponse ) ;
398401 } ,
399402 } ) ;
400403 const projectConfigQuery = useQuery ( {
@@ -422,16 +425,18 @@ function ReviewsView({
422425 onSuccess : ( { data, reused } ) => {
423426 void queryClient . invalidateQueries ( { queryKey : [ "session-reviews" , session . id ] } ) ;
424427 void queryClient . invalidateQueries ( { queryKey : workspaceQueryKey } ) ;
425- if ( reused ) {
426- setReviewNotice ( "Review is already up to date for this commit ." ) ;
428+ if ( reused || ! data ?. reviews ?. length ) {
429+ setReviewNotice ( "No needed reviews were started ." ) ;
427430 return ;
428431 }
429432 if ( data ?. reviewerHandleId ) {
430- onOpenReviewerTerminal ?.( { handleId : data . reviewerHandleId , harness : data . review . harness || "reviewer" } ) ;
433+ const harness = data . reviews [ 0 ] ?. harness || data . review . harness || "reviewer" ;
434+ onOpenReviewerTerminal ?.( { handleId : data . reviewerHandleId , harness } ) ;
431435 }
432436 } ,
433437 } ) ;
434438 const reviews = reviewsQuery . data ?. reviews ?? [ ] ;
439+ const items = reviewsQuery . data ?. items ?? [ ] ;
435440
436441 return (
437442 < div role = "tabpanel" >
@@ -445,6 +450,7 @@ function ReviewsView({
445450 onTrigger = { ( ) => triggerReview . mutate ( ) }
446451 reviewerHandleId = { reviewsQuery . data ?. reviewerHandleId ?? "" }
447452 reviews = { reviews }
453+ items = { items }
448454 notice = { reviewNotice }
449455 session = { session }
450456 />
@@ -462,6 +468,7 @@ function ReviewPanel({
462468 session,
463469 config,
464470 reviews,
471+ items,
465472 reviewerHandleId,
466473 isLoading,
467474 isTriggering,
@@ -473,6 +480,7 @@ function ReviewPanel({
473480 session : WorkspaceSession ;
474481 config ?: ProjectConfig ;
475482 reviews : ReviewRun [ ] ;
483+ items : PRReviewItem [ ] ;
476484 reviewerHandleId : string ;
477485 isLoading : boolean ;
478486 isTriggering : boolean ;
@@ -490,19 +498,50 @@ function ReviewPanel({
490498
491499 const latest = latestReview ( reviews ) ;
492500 const harness = latest ?. harness || config ?. reviewers ?. [ 0 ] ?. harness || session . provider || "reviewer" ;
501+ const terminalEnabled = Boolean ( reviewerHandleId && onOpenTerminal ) ;
493502
494503 return (
495504 < div className = "reviewer-list" >
496505 { error ? < p className = "reviewer-error" > { apiErrorMessage ( error , "Review request failed" ) } </ p > : null }
497506 { 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- />
507+ < div className = "reviewer-card" >
508+ < div className = "reviewer-card__top" >
509+ < div className = "reviewer-card__name" >
510+ < Shield aria-hidden = "true" />
511+ < span > { harness } </ span >
512+ </ div >
513+ < span className = "reviewer-status reviewer-status--neutral" > { items . length } PRs</ span >
514+ </ div >
515+ < div className = "reviewer-card__actions" >
516+ < button
517+ className = "reviewer-card__action reviewer-card__action--primary"
518+ disabled = { isTriggering }
519+ onClick = { onTrigger }
520+ type = "button"
521+ >
522+ < Play aria-hidden = "true" />
523+ { isTriggering ? "Starting..." : "Run needed reviews" }
524+ </ button >
525+ < button
526+ className = "reviewer-card__action"
527+ disabled = { ! terminalEnabled }
528+ onClick = { ( ) => {
529+ if ( ! terminalEnabled ) return ;
530+ onOpenTerminal ?.( { handleId : reviewerHandleId , harness } ) ;
531+ } }
532+ type = "button"
533+ >
534+ < Terminal aria-hidden = "true" />
535+ Open terminal
536+ </ button >
537+ </ div >
538+ </ div >
539+ < div className = "flex flex-col gap-2" >
540+ { items . length === 0 ? < p className = "inspector-empty" > No review state loaded yet.</ p > : null }
541+ { items . map ( ( item ) => (
542+ < ReviewItemCard key = { `${ item . prUrl } :${ item . targetSha } ` } item = { item } />
543+ ) ) }
544+ </ div >
506545 </ div >
507546 ) ;
508547}
@@ -511,85 +550,48 @@ function latestReview(reviews: ReviewRun[]): ReviewRun | undefined {
511550 return [ ...reviews ] . sort ( ( a , b ) => Date . parse ( b . createdAt ) - Date . parse ( a . createdAt ) ) [ 0 ] ;
512551}
513552
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-
553+ function ReviewItemCard ( { item } : { item : PRReviewItem } ) {
554+ const status = reviewItemStatus ( item ) ;
533555 return (
534- < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` ) } >
556+ < div className = { cn ( "reviewer-card" , status . tone && `reviewer-card--${ status . tone } ` , item . status === "ineligible" && "opacity-70" ) } >
535557 < div className = "reviewer-card__top" >
536558 < div className = "reviewer-card__name" >
537- < Shield aria-hidden = "true" />
538- < span > { harness } </ span >
559+ < GitPullRequest aria-hidden = "true" />
560+ < span > PR # { item . prNumber } </ span >
539561 </ div >
540562 < span className = { cn ( "reviewer-status" , `reviewer-status--${ status . tone } ` ) } >
541563 { status . icon }
542564 { status . label }
543565 </ span >
544566 </ 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 }
567+ < div className = "mt-2 min-w-0 truncate font-mono text-[10.5px] text-passive" >
568+ < a href = { item . prUrl } target = "_blank" rel = "noopener noreferrer" className = "text-accent hover:underline" >
569+ { item . prUrl }
570+ </ a >
571+ { item . targetSha ? < span className = "ml-2" > { item . targetSha . slice ( 0 , 7 ) } </ span > : null }
569572 </ div >
570573 </ div >
571574 ) ;
572575}
573576
574- function reviewStatus ( review ?: ReviewRun ) : {
577+ function reviewItemStatus ( item : PRReviewItem ) : {
575578 label : string ;
576579 tone : "neutral" | "running" | "success" | "danger" ;
577580 icon : ReactNode ;
578581} {
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" /> } ;
582+ switch ( item . status ) {
583+ case "needs_review" :
584+ return { label : "Needs review" , tone : "neutral" , icon : null } ;
585+ case "running" :
586+ return { label : "Running" , tone : "running" , icon : < Play aria-hidden = "true" /> } ;
587+ case "up_to_date" :
588+ return { label : "Up to date" , tone : "success" , icon : < CheckCircle2 aria-hidden = "true" /> } ;
589+ case "changes_requested" :
590+ return { label : "Changes requested" , tone : "danger" , icon : < CircleMinus aria-hidden = "true" /> } ;
591+ case "ineligible" :
592+ return { label : "Ineligible" , tone : "neutral" , icon : < AlertCircle aria-hidden = "true" /> } ;
591593 }
592- return { label : "Complete" , tone : "success " , icon : < CheckCircle2 aria-hidden = "true" /> } ;
594+ return { label : item . status , tone : "neutral " , icon : null } ;
593595}
594596
595597function BrowserView ( {
0 commit comments