@@ -4,6 +4,7 @@ import { ThemeProvider, useTheme } from '@plannotator/ui/components/ThemeProvide
44import { ConfirmDialog } from '@plannotator/ui/components/ConfirmDialog' ;
55import { Settings } from '@plannotator/ui/components/Settings' ;
66import { FeedbackButton , ApproveButton } from '@plannotator/ui/components/ToolbarButtons' ;
7+ import { PiReviewActions } from './components/PiReviewActions' ;
78import { UpdateBanner } from '@plannotator/ui/components/UpdateBanner' ;
89import { storage } from '@plannotator/ui/utils/storage' ;
910import { CompletionOverlay } from '@plannotator/ui/components/CompletionOverlay' ;
@@ -158,7 +159,8 @@ const ReviewApp: React.FC = () => {
158159 const [ diffError , setDiffError ] = useState < string | null > ( null ) ;
159160 const [ isSendingFeedback , setIsSendingFeedback ] = useState ( false ) ;
160161 const [ isApproving , setIsApproving ] = useState ( false ) ;
161- const [ submitted , setSubmitted ] = useState < 'approved' | 'feedback' | false > ( false ) ;
162+ const [ isExiting , setIsExiting ] = useState ( false ) ;
163+ const [ submitted , setSubmitted ] = useState < 'approved' | 'feedback' | 'exited' | false > ( false ) ;
162164 const [ showApproveWarning , setShowApproveWarning ] = useState ( false ) ;
163165 const [ sharingEnabled , setSharingEnabled ] = useState ( true ) ;
164166 const [ repoInfo , setRepoInfo ] = useState < { display : string ; branch ?: string } | null > ( null ) ;
@@ -1089,6 +1091,22 @@ const ReviewApp: React.FC = () => {
10891091 }
10901092 } , [ totalAnnotationCount , feedbackMarkdown , allAnnotations ] ) ;
10911093
1094+ // Exit review session without sending any feedback
1095+ const handleExit = useCallback ( async ( ) => {
1096+ setIsExiting ( true ) ;
1097+ try {
1098+ const res = await fetch ( '/api/exit' , { method : 'POST' } ) ;
1099+ if ( res . ok ) {
1100+ setSubmitted ( 'exited' ) ;
1101+ } else {
1102+ throw new Error ( 'Failed to exit' ) ;
1103+ }
1104+ } catch ( error ) {
1105+ console . error ( 'Failed to exit review:' , error ) ;
1106+ setIsExiting ( false ) ;
1107+ }
1108+ } , [ ] ) ;
1109+
10921110 // Approve without feedback (LGTM)
10931111 const handleApprove = useCallback ( async ( ) => {
10941112 setIsApproving ( true ) ;
@@ -1279,7 +1297,7 @@ const ReviewApp: React.FC = () => {
12791297 const tag = ( e . target as HTMLElement ) ?. tagName ;
12801298 if ( tag === 'INPUT' || tag === 'TEXTAREA' ) return ;
12811299 if ( showExportModal || showNoAnnotationsDialog || showApproveWarning ) return ;
1282- if ( submitted || isSendingFeedback || isApproving || isPlatformActioning ) return ;
1300+ if ( submitted || isSendingFeedback || isApproving || isExiting || isPlatformActioning ) return ;
12831301 if ( ! origin ) return ; // Demo mode
12841302
12851303 e . preventDefault ( ) ;
@@ -1499,73 +1517,92 @@ const ReviewApp: React.FC = () => {
14991517 </ div >
15001518 ) }
15011519
1502- { /* Send Feedback button — always the same label */ }
1503- < FeedbackButton
1504- onClick = { ( ) => {
1505- if ( platformMode ) {
1506- setPlatformGeneralComment ( '' ) ;
1507- setPlatformCommentDialog ( { action : 'comment' } ) ;
1508- } else {
1509- handleSendFeedback ( ) ;
1510- }
1511- } }
1512- disabled = {
1513- isSendingFeedback || isApproving || isPlatformActioning ||
1514- ( ! platformMode && totalAnnotationCount === 0 )
1515- }
1516- isLoading = { isSendingFeedback || isPlatformActioning }
1517- muted = { ! platformMode && totalAnnotationCount === 0 && ! isSendingFeedback && ! isApproving && ! isPlatformActioning }
1518- label = { platformMode ? 'Post Comments' : 'Send Feedback' }
1519- shortLabel = { platformMode ? 'Post' : 'Send' }
1520- loadingLabel = { platformMode ? 'Posting...' : 'Sending...' }
1521- shortLoadingLabel = { platformMode ? 'Posting...' : 'Sending...' }
1522- title = { ! platformMode && totalAnnotationCount === 0 ? "Add annotations to send feedback" : "Send feedback" }
1523- />
1524-
1525- { /* Approve button — always the same label */ }
1526- < div className = "relative group/approve" >
1527- < ApproveButton
1528- onClick = { ( ) => {
1529- if ( platformMode ) {
1530- if ( platformUser && prMetadata ?. author === platformUser ) return ;
1531- setPlatformGeneralComment ( '' ) ;
1532- setPlatformCommentDialog ( { action : 'approve' } ) ;
1533- } else {
1534- if ( totalAnnotationCount > 0 ) {
1535- setShowApproveWarning ( true ) ;
1536- } else {
1537- handleApprove ( ) ;
1538- }
1539- }
1540- } }
1541- disabled = {
1542- isSendingFeedback || isApproving || isPlatformActioning ||
1543- ( platformMode && ! ! platformUser && prMetadata ?. author === platformUser )
1544- }
1545- isLoading = { isApproving }
1546- dimmed = { ! platformMode && totalAnnotationCount > 0 }
1547- muted = { platformMode && ! ! platformUser && prMetadata ?. author === platformUser && ! isSendingFeedback && ! isApproving && ! isPlatformActioning }
1548- title = {
1549- platformMode && platformUser && prMetadata ?. author === platformUser
1550- ? `You can't approve your own ${ mrLabel } `
1551- : "Approve - no changes needed"
1552- }
1520+ { /* Pi agent mode: Exit/SendFeedback flip + Approve */ }
1521+ { origin === 'pi' && ! platformMode ? (
1522+ < PiReviewActions
1523+ totalAnnotationCount = { totalAnnotationCount }
1524+ isSendingFeedback = { isSendingFeedback }
1525+ isApproving = { isApproving }
1526+ isExiting = { isExiting }
1527+ onSendFeedback = { handleSendFeedback }
1528+ onApprove = { ( ) => totalAnnotationCount > 0 ? setShowApproveWarning ( true ) : handleApprove ( ) }
1529+ onExit = { handleExit }
15531530 />
1554- { /* Tooltip: own PR warning OR annotations-lost warning */ }
1555- { platformMode && platformUser && prMetadata ?. author === platformUser ? (
1556- < div className = "absolute top-full right-0 mt-2 px-3 py-2 bg-popover border border-border rounded-lg shadow-xl text-xs text-foreground w-48 text-center opacity-0 invisible group-hover/approve:opacity-100 group-hover/approve:visible transition-all pointer-events-none z-50" >
1557- < div className = "absolute bottom-full right-4 border-4 border-transparent border-b-border" />
1558- < div className = "absolute bottom-full right-4 mt-px border-4 border-transparent border-b-popover" />
1559- You can't approve your own { mrLabel === 'MR' ? 'merge request' : 'pull request' } on { platformLabel } .
1531+ ) : ! platformMode ? (
1532+ < >
1533+ { /* Other agent mode: muted Send Feedback + Approve (original behavior) */ }
1534+ < FeedbackButton
1535+ onClick = { handleSendFeedback }
1536+ disabled = { isSendingFeedback || isApproving || totalAnnotationCount === 0 }
1537+ isLoading = { isSendingFeedback }
1538+ muted = { totalAnnotationCount === 0 && ! isSendingFeedback && ! isApproving }
1539+ label = "Send Feedback"
1540+ shortLabel = "Send"
1541+ loadingLabel = "Sending..."
1542+ title = { totalAnnotationCount === 0 ? "Add annotations to send feedback" : "Send feedback" }
1543+ />
1544+ < div className = "relative group/approve" >
1545+ < ApproveButton
1546+ onClick = { ( ) => totalAnnotationCount > 0 ? setShowApproveWarning ( true ) : handleApprove ( ) }
1547+ disabled = { isSendingFeedback || isApproving }
1548+ isLoading = { isApproving }
1549+ dimmed = { totalAnnotationCount > 0 }
1550+ title = "Approve - no changes needed"
1551+ />
1552+ { totalAnnotationCount > 0 && (
1553+ < div className = "absolute top-full right-0 mt-2 px-3 py-2 bg-popover border border-border rounded-lg shadow-xl text-xs text-foreground w-56 text-center opacity-0 invisible group-hover/approve:opacity-100 group-hover/approve:visible transition-all pointer-events-none z-50" >
1554+ < div className = "absolute bottom-full right-4 border-4 border-transparent border-b-border" />
1555+ < div className = "absolute bottom-full right-4 mt-px border-4 border-transparent border-b-popover" />
1556+ Your { totalAnnotationCount } annotation{ totalAnnotationCount !== 1 ? 's' : '' } won't be sent if you approve.
1557+ </ div >
1558+ ) }
15601559 </ div >
1561- ) : ! platformMode && totalAnnotationCount > 0 ? (
1562- < div className = "absolute top-full right-0 mt-2 px-3 py-2 bg-popover border border-border rounded-lg shadow-xl text-xs text-foreground w-56 text-center opacity-0 invisible group-hover/approve:opacity-100 group-hover/approve:visible transition-all pointer-events-none z-50" >
1563- < div className = "absolute bottom-full right-4 border-4 border-transparent border-b-border" />
1564- < div className = "absolute bottom-full right-4 mt-px border-4 border-transparent border-b-popover" />
1565- Your { totalAnnotationCount } annotation{ totalAnnotationCount !== 1 ? 's' : '' } won't be sent if you approve.
1560+ </ >
1561+ ) : (
1562+ < >
1563+ { /* Platform mode: Post Comments + Approve */ }
1564+ < FeedbackButton
1565+ onClick = { ( ) => {
1566+ setPlatformGeneralComment ( '' ) ;
1567+ setPlatformCommentDialog ( { action : 'comment' } ) ;
1568+ } }
1569+ disabled = { isSendingFeedback || isApproving || isPlatformActioning }
1570+ isLoading = { isSendingFeedback || isPlatformActioning }
1571+ label = "Post Comments"
1572+ shortLabel = "Post"
1573+ loadingLabel = "Posting..."
1574+ shortLoadingLabel = "Posting..."
1575+ title = "Send feedback"
1576+ />
1577+ < div className = "relative group/approve" >
1578+ < ApproveButton
1579+ onClick = { ( ) => {
1580+ if ( platformUser && prMetadata ?. author === platformUser ) return ;
1581+ setPlatformGeneralComment ( '' ) ;
1582+ setPlatformCommentDialog ( { action : 'approve' } ) ;
1583+ } }
1584+ disabled = {
1585+ isSendingFeedback || isApproving || isPlatformActioning ||
1586+ ( ! ! platformUser && prMetadata ?. author === platformUser )
1587+ }
1588+ isLoading = { isApproving }
1589+ muted = { ! ! platformUser && prMetadata ?. author === platformUser && ! isSendingFeedback && ! isApproving && ! isPlatformActioning }
1590+ title = {
1591+ platformUser && prMetadata ?. author === platformUser
1592+ ? `You can't approve your own ${ mrLabel } `
1593+ : "Approve - no changes needed"
1594+ }
1595+ />
1596+ { platformUser && prMetadata ?. author === platformUser && (
1597+ < div className = "absolute top-full right-0 mt-2 px-3 py-2 bg-popover border border-border rounded-lg shadow-xl text-xs text-foreground w-48 text-center opacity-0 invisible group-hover/approve:opacity-100 group-hover/approve:visible transition-all pointer-events-none z-50" >
1598+ < div className = "absolute bottom-full right-4 border-4 border-transparent border-b-border" />
1599+ < div className = "absolute bottom-full right-4 mt-px border-4 border-transparent border-b-popover" />
1600+ You can't approve your own { mrLabel === 'MR' ? 'merge request' : 'pull request' } on { platformLabel } .
1601+ </ div >
1602+ ) }
15661603 </ div >
1567- ) : null }
1568- </ div >
1604+ </ >
1605+ ) }
15691606 </ >
15701607 ) : (
15711608 < button
@@ -1878,18 +1915,24 @@ const ReviewApp: React.FC = () => {
18781915 } }
18791916 />
18801917
1881- { /* Completion overlay - shown after approve/feedback */ }
1918+ { /* Completion overlay - shown after approve/feedback/exit */ }
18821919 < CompletionOverlay
18831920 submitted = { submitted }
1884- title = { submitted === 'approved' ? 'Changes Approved' : 'Feedback Sent' }
1921+ title = {
1922+ submitted === 'approved' ? 'Changes Approved'
1923+ : submitted === 'exited' ? 'Session Closed'
1924+ : 'Feedback Sent'
1925+ }
18851926 subtitle = {
1886- platformMode
1887- ? submitted === 'approved'
1888- ? `Your approval was submitted to ${ platformLabel } .`
1889- : `Your feedback was submitted to ${ platformLabel } .`
1890- : submitted === 'approved'
1891- ? `${ getAgentName ( origin ) } will proceed with the changes.`
1892- : `${ getAgentName ( origin ) } will address your review feedback.`
1927+ submitted === 'exited'
1928+ ? 'Review session closed without feedback.'
1929+ : platformMode
1930+ ? submitted === 'approved'
1931+ ? `Your approval was submitted to ${ platformLabel } .`
1932+ : `Your feedback was submitted to ${ platformLabel } .`
1933+ : submitted === 'approved'
1934+ ? `${ getAgentName ( origin ) } will proceed with the changes.`
1935+ : `${ getAgentName ( origin ) } will address your review feedback.`
18931936 }
18941937 agentLabel = { getAgentName ( origin ) }
18951938 />
0 commit comments