@@ -197,6 +197,7 @@ async function initApp() {
197197 const [ isTailing , setIsTailing ] = useState ( false ) ;
198198 const [ hiddenCommentKeys , setHiddenCommentKeys ] = useState ( new Set ( ) ) ;
199199 const [ copyFeedback , setCopyFeedback ] = useState ( { status : 'idle' , message : '' } ) ;
200+ const [ handoffModal , setHandoffModal ] = useState ( { isOpen : false , type : '' , message : '' } ) ;
200201
201202 const pollingRef = useRef ( null ) ;
202203 const eventsPollingRef = useRef ( null ) ;
@@ -442,6 +443,56 @@ async function initApp() {
442443 } ) ;
443444 } , [ ] ) ;
444445
446+ const handleSendToAgent = useCallback ( async ( ) => {
447+ const filteredFiles = ( reviewData . files || reviewData . Files || [ ] ) . map ( file => {
448+ const filePath = file . file_path || file . filePath || file . FilePath ;
449+ const newComments = ( file . comments || file . Comments || [ ] ) . filter ( c => {
450+ const sev = ( c . severity || c . Severity || '' ) . toLowerCase ( ) ;
451+ if ( ! visibleSeverities . has ( sev ) ) return false ;
452+ const key = getCommentVisibilityKey ( filePath , c ) ;
453+ return ! hiddenCommentKeys . has ( key ) ;
454+ } ) ;
455+ return { ...file , comments : newComments , Comments : newComments } ;
456+ } ) . filter ( file => file . comments . length > 0 ) ;
457+
458+ if ( filteredFiles . length === 0 ) {
459+ setHandoffModal ( {
460+ isOpen : true ,
461+ type : 'error' ,
462+ message : "No visible comments to send to the AI agent. Please show some comments first."
463+ } ) ;
464+ return ;
465+ }
466+
467+ const payload = {
468+ ...reviewData ,
469+ files : filteredFiles ,
470+ Files : filteredFiles ,
471+ summary : "AI Agent Handoff generated for visible issues." ,
472+ status : "completed"
473+ } ;
474+
475+ try {
476+ const response = await fetch ( '/handoff' , {
477+ method : 'POST' ,
478+ headers : { 'Content-Type' : 'application/json' } ,
479+ body : JSON . stringify ( payload )
480+ } ) ;
481+ if ( ! response . ok ) throw new Error ( "Handoff failed" ) ;
482+ setHandoffModal ( {
483+ isOpen : true ,
484+ type : 'success' ,
485+ message : "Claude Code is now starting in your terminal! You can safely close this browser window."
486+ } ) ;
487+ } catch ( e ) {
488+ setHandoffModal ( {
489+ isOpen : true ,
490+ type : 'error' ,
491+ message : "Failed to send to agent: " + e . message
492+ } ) ;
493+ }
494+ } , [ reviewData , visibleSeverities , hiddenCommentKeys ] ) ;
495+
445496 const showCopyFeedback = useCallback ( ( status , message ) => {
446497 setCopyFeedback ( { status, message } ) ;
447498 if ( copyFeedbackTimerRef . current ) {
@@ -614,6 +665,25 @@ async function initApp() {
614665 // Calculate totalComments from actual files - single source of truth
615666 const totalComments = files . reduce ( ( sum , file ) => sum + ( file . CommentCount || 0 ) , 0 ) ;
616667
668+ // Calculate visible comments for the agent button
669+ let totalVisibleComments = 0 ;
670+ files . forEach ( file => {
671+ if ( ! file . HasComments ) return ;
672+ file . Hunks . forEach ( hunk => {
673+ hunk . Lines . forEach ( line => {
674+ if ( line . IsComment && line . Comments ) {
675+ line . Comments . forEach ( ( comment ) => {
676+ const sev = ( comment . Severity || '' ) . toLowerCase ( ) ;
677+ if ( ! visibleSeverities . has ( sev ) ) return ;
678+ const visibilityKey = getCommentVisibilityKey ( file . FilePath , comment ) ;
679+ if ( visibilityKey && hiddenCommentKeys . has ( visibilityKey ) ) return ;
680+ totalVisibleComments ++ ;
681+ } ) ;
682+ }
683+ } ) ;
684+ } ) ;
685+ } ) ;
686+
617687 // Status display
618688 const getStatusDisplay = ( ) => {
619689 if ( reviewData ?. blocked ) {
@@ -708,6 +778,8 @@ async function initApp() {
708778 hiddenCommentKeys=${ hiddenCommentKeys }
709779 copyFeedbackStatus=${ copyFeedback . status }
710780 copyFeedbackMessage=${ copyFeedback . message }
781+ onSendToAgent=${ handleSendToAgent }
782+ visibleCount=${ totalVisibleComments }
711783 />
712784 ` }
713785
@@ -735,6 +807,38 @@ async function initApp() {
735807 }
736808 </ div >
737809
810+ ${ handoffModal . isOpen && html `
811+ < div class ="modal-overlay " style ="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); ">
812+ < div class ="modal-content " style ="background: var(--bg-card); padding: 32px; border-radius: 12px; max-width: 400px; width: 90%; border: 1px solid var(--border-color); box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.5); text-align: center; ">
813+ ${ handoffModal . type === 'success'
814+ ? html `
815+ < div style ="margin-bottom: 16px; color: #8b5cf6; ">
816+ < svg width ="48 " height ="48 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round ">
817+ < rect x ="2 " y ="3 " width ="20 " height ="18 " rx ="2 " ry ="2 "> </ rect >
818+ < polyline points ="6 8 10 12 6 16 "> </ polyline >
819+ < line x1 ="14 " y1 ="16 " x2 ="18 " y2 ="16 "> </ line >
820+ </ svg >
821+ </ div >
822+ `
823+ : html `< div style ="font-size: 48px; margin-bottom: 16px; "> ⚠️</ div > `
824+ }
825+ < h3 style ="margin: 0 0 12px 0; font-size: 20px; color: var(--text-primary); ">
826+ ${ handoffModal . type === 'success' ? 'Check Your Terminal' : 'Notice' }
827+ </ h3 >
828+ < p style ="margin: 0 0 24px 0; color: var(--text-secondary); line-height: 1.5; ">
829+ ${ handoffModal . message }
830+ </ p >
831+ < button
832+ class ="btn btn-primary "
833+ onClick =${ ( ) => setHandoffModal ( { ...handoffModal , isOpen : false } ) }
834+ style ="width: 100%; padding: 12px; font-size: 16px;"
835+ >
836+ ${ handoffModal . type === 'success' ? 'Got it' : 'Close' }
837+ </ button >
838+ </ div >
839+ </ div >
840+ ` }
841+
738842 <!-- Events Tab -->
739843 < div id ="events-tab " class ="tab-content ${ activeTab === 'events' ? 'active' : '' } " style ="display: ${ activeTab === 'events' ? 'block' : 'none' } ">
740844 < ${ EventLog }
0 commit comments