22// Fetches data from /api/review and updates reactively
33
44import { waitForPreact , filePathToId , transformEvent , getBadgeClass , formatIssueForCopy , getCommentVisibilityKey } from './components/utils.js' ;
5- import { buildIssueCategoryGroups , buildIssueFacetOptions , buildIssueFilterUniverse , countIssuesByFilters , createDefaultIssueFilters , getIssueFilterSummary , matchesIssueFilters , resetIssueFilters , toggleIssueFilterValue } from './components/issue_filter_state.mjs' ;
5+ import { buildIssueCategoryGroups , buildIssueFacetOptions , buildIssueFilterUniverse , countIssuesByFilters , createDefaultIssueFilters , DEFAULT_SEVERITIES , getCommentFilterValue , getIssueFilterSummary , matchesIssueFilters , resetIssueFilters , toggleIssueFilterValue } from './components/issue_filter_state.mjs' ;
66import { appendStreamedCommentsToFiles , buildEventsURL , extractExternalCommentsFromEvents , extractNewEvents , inferReviewStatusFromEvents } from './components/review_stream_state.mjs' ;
77import { getHeader } from './components/Header.js' ;
88import { getSidebar } from './components/Sidebar.js' ;
@@ -12,6 +12,8 @@ import { getPrecommitBar } from './components/PrecommitBar.js';
1212import { getFileBlock } from './components/FileBlock.js' ;
1313import { getEventLog } from './components/EventLog.js' ;
1414import { getIssueFilterBar } from './components/IssueFilterBar.js' ;
15+ import { getSendToAgentInfo } from './components/SendToAgentButton.js' ;
16+ import { renderHandoffConfetti } from './components/handoffConfetti.js' ;
1517import { getToolbar } from './components/Toolbar.js' ;
1618import { getCommentNav } from './components/CommentNav.js' ;
1719import { UsageBanner } from './components/UsageBanner.js' ;
@@ -36,6 +38,30 @@ function countCommentsFromFiles(files) {
3638 } , 0 ) ;
3739}
3840
41+ // Build a severity + type breakdown for the handoff modal so the user can see the
42+ // scope of what's being auto-fixed (e.g. "6 issues — 2 Critical, 3 Warning, 1 Info").
43+ function buildHandoffImpactSummary ( files ) {
44+ const bySeverity = Object . fromEntries ( DEFAULT_SEVERITIES . map ( ( severity ) => [ severity , 0 ] ) ) ;
45+ const typeCounts = new Map ( ) ;
46+ let total = 0 ;
47+ ( files || [ ] ) . forEach ( ( file ) => {
48+ ( file . comments || file . Comments || [ ] ) . forEach ( ( comment ) => {
49+ total += 1 ;
50+ const severity = getCommentFilterValue ( comment , 'severity' ) ;
51+ bySeverity [ severity ] = ( bySeverity [ severity ] || 0 ) + 1 ;
52+
53+ const type = getCommentFilterValue ( comment , 'type' ) ;
54+ if ( type ) {
55+ typeCounts . set ( type , ( typeCounts . get ( type ) || 0 ) + 1 ) ;
56+ }
57+ } ) ;
58+ } ) ;
59+ const byType = [ ...typeCounts . entries ( ) ]
60+ . map ( ( [ label , count ] ) => ( { label, count } ) )
61+ . sort ( ( a , b ) => b . count - a . count || a . label . localeCompare ( b . label ) ) ;
62+ return { total, bySeverity, byType } ;
63+ }
64+
3965function convertFilesToUIFormat ( files ) {
4066 if ( ! files ) return [ ] ;
4167
@@ -267,7 +293,7 @@ async function initApp() {
267293 const [ isTailing , setIsTailing ] = useState ( false ) ;
268294 const [ hiddenCommentKeys , setHiddenCommentKeys ] = useState ( new Set ( ) ) ;
269295 const [ copyFeedback , setCopyFeedback ] = useState ( { status : 'idle' , message : '' } ) ;
270- const [ handoffModal , setHandoffModal ] = useState ( { isOpen : false , type : '' , message : '' } ) ;
296+ const [ handoffModal , setHandoffModal ] = useState ( { isOpen : false , type : '' , message : '' , agent : null , summary : null } ) ;
271297 const [ slideShowOpen , setSlideShowOpen ] = useState ( false ) ;
272298 const [ embeddedSlideshowActive , setEmbeddedSlideshowActive ] = useState ( false ) ;
273299 const [ summarySlideIndex , setSummarySlideIndex ] = useState ( 0 ) ;
@@ -679,7 +705,19 @@ async function initApp() {
679705 } ) ;
680706 } , [ ] ) ;
681707
682- const handleSendToAgent = useCallback ( async ( ) => {
708+ const handleSendToAgent = useCallback ( async ( agentKey ) => {
709+ const agent = getSendToAgentInfo ( agentKey ) ;
710+
711+ if ( ! agent . available ) {
712+ setHandoffModal ( {
713+ isOpen : true ,
714+ type : 'info' ,
715+ agent,
716+ message : `${ agent . label } handoff is coming soon. Try Claude for now.`
717+ } ) ;
718+ return ;
719+ }
720+
683721 const filteredFiles = ( reviewData . files || reviewData . Files || [ ] ) . map ( file => {
684722 const filePath = file . file_path || file . filePath || file . FilePath ;
685723 const newComments = ( file . comments || file . Comments || [ ] ) . filter ( c => {
@@ -689,24 +727,36 @@ async function initApp() {
689727 } ) ;
690728 return { ...file , comments : newComments , Comments : newComments } ;
691729 } ) . filter ( file => file . comments . length > 0 ) ;
692-
730+
693731 if ( filteredFiles . length === 0 ) {
694- setHandoffModal ( {
695- isOpen : true ,
696- type : 'error' ,
697- message : "No visible comments to send to the AI agent. Please show some comments first."
732+ setHandoffModal ( {
733+ isOpen : true ,
734+ type : 'error' ,
735+ agent,
736+ message : "No visible comments to send to the AI agent. Please show some comments first."
698737 } ) ;
699738 return ;
700739 }
701-
740+
741+ const impactSummary = buildHandoffImpactSummary ( filteredFiles ) ;
742+
743+ setHandoffModal ( {
744+ isOpen : true ,
745+ type : 'starting' ,
746+ agent,
747+ summary : impactSummary ,
748+ message : `${ agent . label } started auto-fixing the issues…`
749+ } ) ;
750+
702751 const payload = {
703752 ...reviewData ,
704753 files : filteredFiles ,
705754 Files : filteredFiles ,
706755 summary : "AI Agent Handoff generated for visible issues." ,
707- status : "completed"
756+ status : "completed" ,
757+ agent : agent . key
708758 } ;
709-
759+
710760 try {
711761 const handoffURL = sessionReviewID ? `/handoff?r=${ sessionReviewID } ` : '/handoff' ;
712762 const response = await fetch ( handoffURL , {
@@ -715,16 +765,19 @@ async function initApp() {
715765 body : JSON . stringify ( payload )
716766 } ) ;
717767 if ( ! response . ok ) throw new Error ( "Handoff failed" ) ;
718- setHandoffModal ( {
719- isOpen : true ,
720- type : 'success' ,
721- message : "Claude Code is now starting in your terminal! You can safely close this browser window."
768+ setHandoffModal ( {
769+ isOpen : true ,
770+ type : 'success' ,
771+ agent,
772+ summary : impactSummary ,
773+ message : `Auto-fixing started for your issues — check the ${ agent . label } Code terminal to follow along. You can safely close this browser window.`
722774 } ) ;
723775 } catch ( e ) {
724- setHandoffModal ( {
725- isOpen : true ,
726- type : 'error' ,
727- message : "Failed to send to agent: " + e . message
776+ setHandoffModal ( {
777+ isOpen : true ,
778+ type : 'error' ,
779+ agent,
780+ message : "Failed to send to agent: " + e . message
728781 } ) ;
729782 }
730783 } , [ reviewData , issueFilters , hiddenCommentKeys ] ) ;
@@ -1131,25 +1184,71 @@ async function initApp() {
11311184 }
11321185 </ div >
11331186
1187+ ${ handoffModal . isOpen && handoffModal . type === 'success' && renderHandoffConfetti ( html ) }
11341188 ${ handoffModal . isOpen && html `
1135- < 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); ">
1136- < 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; ">
1137- ${ handoffModal . type === 'success'
1189+ < div class ="modal-overlay handoff-modal-overlay ">
1190+ < div class ="modal-content handoff-modal-content ">
1191+ ${ ( handoffModal . type === 'starting' || handoffModal . type === 'success' )
11381192 ? html `
1139- < div style ="margin-bottom: 16px; color: #8b5cf6; ">
1140- ${ renderIcon ( html , 'handoffSuccess' , { size : 48 } ) }
1193+ < div class ="handoff-agent-badge ${ handoffModal . type === 'success' ? 'is-success' : '' } ">
1194+ ${ handoffModal . type === 'starting' && html `< span class ="handoff-agent-badge-ring "> </ span > ` }
1195+ ${ ( handoffModal . agent ?. logoOnLight || handoffModal . agent ?. logo )
1196+ ? html `< img class ="handoff-agent-badge-logo " src =${ handoffModal . agent . logoOnLight || handoffModal . agent . logo } alt ="${ handoffModal . agent . label } " /> `
1197+ : renderIcon ( html , handoffModal . agent ?. icon || 'sendToAgent' , { size : 26 } ) }
1198+ ${ handoffModal . type === 'success' && html `
1199+ < span class ="handoff-agent-badge-check "> ${ renderIcon ( html , 'check' , { size : 12 } ) } </ span >
1200+ ` }
11411201 </ div >
11421202 `
11431203 : html `< div style ="margin-bottom: 16px; "> ${ renderIcon ( html , 'handoffNotice' , { size : 48 } ) } </ div > `
11441204 }
11451205 < h3 style ="margin: 0 0 12px 0; font-size: 20px; color: var(--text-primary); ">
1146- ${ handoffModal . type === 'success' ? 'Check Your Terminal' : 'Notice' }
1206+ ${ handoffModal . type === 'starting'
1207+ ? `${ handoffModal . agent ?. label || 'Agent' } is on it`
1208+ : handoffModal . type === 'success'
1209+ ? `Auto-fixing Using ${ handoffModal . agent ?. label || 'Agent' } Code`
1210+ : handoffModal . type === 'info'
1211+ ? 'Coming Soon'
1212+ : 'Notice' }
11471213 </ h3 >
1148- < p style ="margin: 0 0 24px 0; color: var(--text-secondary); line-height: 1.5; ">
1214+ < p style ="margin: 0 0 16px 0; color: var(--text-secondary); line-height: 1.5; ">
11491215 ${ handoffModal . message }
11501216 </ p >
1151- < button
1152- class ="btn btn-primary "
1217+ ${ handoffModal . summary && handoffModal . summary . total > 0 && html `
1218+ < div class ="handoff-impact-summary ">
1219+ < div class ="handoff-impact-total ">
1220+ ${ handoffModal . summary . total } issue${ handoffModal . summary . total === 1 ? '' : 's' } being fixed
1221+ </ div >
1222+ < div class ="handoff-impact-section ">
1223+ < div class ="handoff-impact-label "> Severity</ div >
1224+ < div class ="handoff-impact-chips ">
1225+ ${ [ 'critical' , 'warning' , 'info' ]
1226+ . filter ( ( severity ) => handoffModal . summary . bySeverity [ severity ] > 0 )
1227+ . map ( ( severity ) => html `
1228+ < span class ="handoff-impact-chip severity- ${ severity } ">
1229+ < span class ="handoff-impact-chip-dot "> </ span >
1230+ ${ handoffModal . summary . bySeverity [ severity ] } ${ severity . charAt ( 0 ) . toUpperCase ( ) + severity . slice ( 1 ) }
1231+ </ span >
1232+ ` ) }
1233+ </ div >
1234+ </ div >
1235+ ${ handoffModal . summary . byType && handoffModal . summary . byType . length > 0 && html `
1236+ < div class ="handoff-impact-section handoff-impact-section-divided ">
1237+ < div class ="handoff-impact-label "> Type</ div >
1238+ < div class ="handoff-impact-types ">
1239+ ${ handoffModal . summary . byType . map ( ( entry ) => html `
1240+ < span class ="handoff-impact-type-chip ">
1241+ ${ entry . label }
1242+ < span class ="handoff-impact-type-count "> ${ entry . count } </ span >
1243+ </ span >
1244+ ` ) }
1245+ </ div >
1246+ </ div >
1247+ ` }
1248+ </ div >
1249+ ` }
1250+ < button
1251+ class ="btn btn-primary "
11531252 onClick =${ ( ) => setHandoffModal ( { ...handoffModal , isOpen : false } ) }
11541253 style ="width: 100%; padding: 12px; font-size: 16px;"
11551254 >
0 commit comments