1- import type { EnvironmentId , PullRequestDetailView , PullRequestRef } from "@t3tools/contracts" ;
1+ import type {
2+ EnvironmentId ,
3+ PullRequestComment ,
4+ PullRequestDetailView ,
5+ PullRequestRef ,
6+ } from "@t3tools/contracts" ;
27import {
38 ArrowDownUpIcon ,
49 CheckCircle2Icon ,
10+ ChevronDownIcon ,
511 ChevronRightIcon ,
612 HammerIcon ,
713 MessageSquareIcon ,
@@ -44,6 +50,51 @@ function labelDotColor(color: string | null): string | null {
4450 return / ^ [ 0 - 9 a - f A - F ] { 6 } $ / . test ( hex ) ? `#${ hex } ` : null ;
4551}
4652
53+ /** A resolved conversation is finished work, so it opens collapsed the way the timeline folds. */
54+ function ResolvedComment ( { comment, cwd } : { comment : PullRequestComment ; cwd : string } ) {
55+ const [ open , setOpen ] = useState ( false ) ;
56+ return (
57+ < Collapsible open = { open } onOpenChange = { setOpen } >
58+ < article className = "rounded-lg border border-border/60 [contain-intrinsic-block-size:44px] [content-visibility:auto]" >
59+ < CollapsibleTrigger
60+ className = { cn (
61+ "flex w-full items-center gap-2 p-3 text-left transition-opacity hover:opacity-100" ,
62+ open ? "opacity-100" : "opacity-65" ,
63+ ) }
64+ >
65+ < PullRequestMetaLine className = "min-w-0 flex-1 text-xs text-muted-foreground" >
66+ < PullRequestActorLabel actor = { comment . author } className = "font-medium text-foreground" />
67+ < span > { formatRelativeTimeLabel ( comment . createdAt ) } </ span >
68+ < span className = "flex items-center gap-1 text-emerald-600 dark:text-emerald-500" >
69+ < CheckCircle2Icon className = "size-3" />
70+ Resolved
71+ </ span >
72+ </ PullRequestMetaLine >
73+ < ChevronDownIcon
74+ aria-hidden
75+ className = { cn (
76+ "size-3.5 shrink-0 text-muted-foreground transition-transform" ,
77+ open && "rotate-180" ,
78+ ) }
79+ />
80+ </ CollapsibleTrigger >
81+ < CollapsiblePanel >
82+ { open ? (
83+ < div className = "px-3 pb-3" >
84+ { comment . path ? (
85+ < p className = "truncate text-xs text-muted-foreground" title = { comment . path } >
86+ { comment . path }
87+ </ p >
88+ ) : null }
89+ < PullRequestMarkdown className = "mt-2" text = { comment . body } cwd = { cwd } />
90+ </ div >
91+ ) : null }
92+ </ CollapsiblePanel >
93+ </ article >
94+ </ Collapsible >
95+ ) ;
96+ }
97+
4798function MetaRow ( {
4899 icon,
49100 label,
@@ -415,16 +466,23 @@ export function PullRequestSummaryTab({
415466 ) : null }
416467 { visibleComments . map ( ( comment ) => {
417468 const thread = threadByCommentId . get ( comment . id ) ;
469+ if ( thread ?. isResolved ) {
470+ return (
471+ < ResolvedComment
472+ key = { comment . id }
473+ comment = { comment }
474+ cwd = { detail . workspaceRoot }
475+ />
476+ ) ;
477+ }
418478 // An approval is a verdict, not a finding: there is nothing in it to fix.
419479 const finding : PullRequestFinding | null =
420480 ( comment . kind !== "review" && comment . kind !== "review-comment" ) ||
421481 comment . reviewState ?. toLowerCase ( ) === "approved"
422482 ? null
423483 : thread === undefined
424484 ? { kind : "comment" , comment }
425- : thread . isResolved
426- ? null
427- : { kind : "thread" , thread } ;
485+ : { kind : "thread" , thread } ;
428486 return (
429487 < article
430488 key = { comment . id }
@@ -442,12 +500,6 @@ export function PullRequestSummaryTab({
442500 { comment . reviewState ? (
443501 < span > { comment . reviewState . toLowerCase ( ) } </ span >
444502 ) : null }
445- { thread ?. isResolved ? (
446- < span className = "flex items-center gap-1 text-emerald-600 dark:text-emerald-500" >
447- < CheckCircle2Icon className = "size-3" />
448- Resolved
449- </ span >
450- ) : null }
451503 </ PullRequestMetaLine >
452504 { /* Review remarks only. A plain conversation comment is talk, not a finding,
453505 and offering to fix one would promise more than it says. */ }
0 commit comments