@@ -39,6 +39,7 @@ interface MountedPayload {
3939 hostContext ?: HostContext ;
4040 errorMessage ?: string | null ;
4141 visibleFileCount ?: number ;
42+ presentation ?: "inline" | "fullscreen" ;
4243 } ) : void ;
4344 unmount ( ) : void ;
4445}
@@ -50,6 +51,8 @@ let hostContext: HostContext | undefined;
5051let card : ToolResultCard | null = null ;
5152let expanded = false ;
5253let reviewFilesExpanded = false ;
54+ let reviewDisplayModePending = false ;
55+ let reviewDisplayModeError : string | null = null ;
5356let errorMessage : string | null = null ;
5457let currentPayload : MountedPayload | null = null ;
5558let currentPayloadContainer : HTMLElement | null = null ;
@@ -84,6 +87,8 @@ async function boot(): Promise<void> {
8487 card = null ;
8588 expanded = false ;
8689 reviewFilesExpanded = false ;
90+ reviewDisplayModePending = false ;
91+ reviewDisplayModeError = null ;
8792 errorMessage = "No result card is available for this tool result." ;
8893 render ( ) ;
8994 return ;
@@ -92,16 +97,27 @@ async function boot(): Promise<void> {
9297 card = { ...structured , tool } ;
9398 expanded = false ;
9499 reviewFilesExpanded = false ;
100+ reviewDisplayModePending = false ;
101+ reviewDisplayModeError = null ;
95102 errorMessage = null ;
96103 render ( ) ;
97104 } ;
98105
99106 app . onhostcontextchanged = ( ctx ) => {
107+ const previousDisplayMode = hostContext ?. displayMode ;
100108 hostContext = {
101109 ...hostContext ,
102110 ...ctx ,
103111 } ;
104112 applyHostContext ( ) ;
113+ if (
114+ previousDisplayMode !== hostContext . displayMode &&
115+ card &&
116+ isReviewTool ( card . tool )
117+ ) {
118+ render ( ) ;
119+ return ;
120+ }
105121 renderPayloadIfNeeded ( ) ;
106122 } ;
107123
@@ -265,12 +281,23 @@ async function renderPayloadIfNeeded(): Promise<void> {
265281 }
266282
267283 if ( isReviewTool ( card . tool ) || isPatchTool ( card . tool ) ) {
268- const visibleFileCount = isReviewTool ( card . tool ) && ! reviewFilesExpanded
284+ const presentation = isReviewTool ( card . tool ) && hostContext ?. displayMode === "fullscreen"
285+ ? "fullscreen"
286+ : "inline" ;
287+ const visibleFileCount = isReviewTool ( card . tool ) &&
288+ presentation === "inline" &&
289+ ! reviewFilesExpanded
269290 ? Math . max ( 3 , ( card . files ?? [ ] ) . slice ( 0 , 3 ) . length )
270291 : undefined ;
271292
272293 if ( currentPayload ) {
273- currentPayload . update ( { card, hostContext, errorMessage, visibleFileCount } ) ;
294+ currentPayload . update ( {
295+ card,
296+ hostContext,
297+ errorMessage,
298+ visibleFileCount,
299+ presentation,
300+ } ) ;
274301 return ;
275302 }
276303
@@ -284,6 +311,7 @@ async function renderPayloadIfNeeded(): Promise<void> {
284311 hostContext,
285312 errorMessage,
286313 visibleFileCount,
314+ presentation,
287315 } ) ;
288316 return ;
289317 }
@@ -389,6 +417,11 @@ function renderSummaryBadge(card: ToolResultCard): HTMLElement {
389417function renderReviewCard ( card : ToolResultCard , display : ToolDisplay ) : void {
390418 unmountPayload ( ) ;
391419
420+ if ( hostContext ?. displayMode === "fullscreen" ) {
421+ renderFullscreenReview ( card , display ) ;
422+ return ;
423+ }
424+
392425 const files = card . files ?? [ ] ;
393426 const hiddenCount = Math . max ( 0 , files . length - 3 ) ;
394427 const main = element ( "main" , { className : "shell" } ) ;
@@ -402,7 +435,24 @@ function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void {
402435 element ( "span" , { className : "tool-title" , text : display . title } ) ,
403436 element ( "span" , { className : "tool-label" , text : display . label , title : display . label } ) ,
404437 ) ;
405- header . append ( icon , titleGroup , renderSummaryBadge ( card ) ) ;
438+
439+ const headerActions = element ( "div" , { className : "review-header-actions" } ) ;
440+ headerActions . append ( renderSummaryBadge ( card ) ) ;
441+ if ( files . length > 0 && canRequestDisplayMode ( "fullscreen" ) ) {
442+ const reviewButton = element ( "button" , {
443+ className : "review-button" ,
444+ type : "button" ,
445+ text : reviewDisplayModePending ? "Opening…" : "Review" ,
446+ disabled : reviewDisplayModePending ,
447+ } ) ;
448+ reviewButton . setAttribute ( "aria-busy" , String ( reviewDisplayModePending ) ) ;
449+ reviewButton . addEventListener ( "click" , ( ) => {
450+ void requestReviewDisplayMode ( "fullscreen" ) ;
451+ } ) ;
452+ headerActions . append ( reviewButton ) ;
453+ }
454+
455+ header . append ( icon , titleGroup , headerActions ) ;
406456
407457 const body = element ( "div" , { className : "review-summary" } ) ;
408458 currentPayloadContainer = body ;
@@ -430,7 +480,14 @@ function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void {
430480 actions . append ( toggleFiles ) ;
431481 }
432482
433- section . append ( header , body ) ;
483+ section . append ( header ) ;
484+ if ( reviewDisplayModeError ) {
485+ section . append ( element ( "div" , {
486+ className : "review-mode-error" ,
487+ text : reviewDisplayModeError ,
488+ } ) ) ;
489+ }
490+ section . append ( body ) ;
434491 if ( actions . childElementCount > 0 ) {
435492 section . append ( actions ) ;
436493 }
@@ -440,6 +497,77 @@ function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void {
440497 renderPayloadIfNeeded ( ) ;
441498}
442499
500+ function renderFullscreenReview ( card : ToolResultCard , display : ToolDisplay ) : void {
501+ const main = element ( "main" , { className : "shell review-fullscreen-shell" } ) ;
502+ const section = element ( "section" , { className : "review-fullscreen" } ) ;
503+ const header = element ( "header" , { className : "review-fullscreen-header" } ) ;
504+ const titleGroup = element ( "div" , { className : "review-fullscreen-title" } ) ;
505+ const icon = element ( "span" , { className : "tool-icon" , ariaHidden : "true" } ) ;
506+ icon . innerHTML = display . icon ;
507+
508+ const heading = element ( "div" , { className : "review-title-group" } ) ;
509+ heading . append (
510+ element ( "span" , { className : "tool-title" , text : "Review changes" } ) ,
511+ element ( "span" , { className : "tool-label" , text : display . label , title : display . label } ) ,
512+ ) ;
513+ titleGroup . append ( icon , heading ) ;
514+
515+ const actions = element ( "div" , { className : "review-fullscreen-actions" } ) ;
516+ const closeButton = element ( "button" , {
517+ className : "review-button" ,
518+ type : "button" ,
519+ text : reviewDisplayModePending ? "Closing…" : "Close review" ,
520+ disabled : reviewDisplayModePending ,
521+ } ) ;
522+ closeButton . setAttribute ( "aria-busy" , String ( reviewDisplayModePending ) ) ;
523+ closeButton . addEventListener ( "click" , ( ) => {
524+ void requestReviewDisplayMode ( "inline" ) ;
525+ } ) ;
526+ actions . append ( renderSummaryBadge ( card ) , closeButton ) ;
527+ header . append ( titleGroup , actions ) ;
528+
529+ const body = element ( "div" , { className : "review-fullscreen-body" } ) ;
530+ currentPayloadContainer = body ;
531+ section . append ( header ) ;
532+ if ( reviewDisplayModeError ) {
533+ section . append ( element ( "div" , {
534+ className : "review-mode-error" ,
535+ text : reviewDisplayModeError ,
536+ } ) ) ;
537+ }
538+ section . append ( body ) ;
539+ main . append ( section ) ;
540+ appRoot . replaceChildren ( main ) ;
541+ renderPayloadIfNeeded ( ) ;
542+ }
543+
544+ function canRequestDisplayMode ( mode : "inline" | "fullscreen" ) : boolean {
545+ return Boolean ( hostContext ?. availableDisplayModes ?. includes ( mode ) ) ;
546+ }
547+
548+ async function requestReviewDisplayMode ( mode : "inline" | "fullscreen" ) : Promise < void > {
549+ if ( ! app || reviewDisplayModePending ) return ;
550+
551+ reviewDisplayModePending = true ;
552+ reviewDisplayModeError = null ;
553+ render ( ) ;
554+
555+ try {
556+ const result = await app . requestDisplayMode ( { mode } ) ;
557+ hostContext = {
558+ ...hostContext ,
559+ displayMode : result . mode ,
560+ } ;
561+ } catch ( requestError ) {
562+ reviewDisplayModeError = requestError instanceof Error
563+ ? requestError . message
564+ : "Unable to change the review display mode." ;
565+ } finally {
566+ reviewDisplayModePending = false ;
567+ render ( ) ;
568+ }
569+ }
570+
443571function renderChevron ( isExpanded : boolean , visible : boolean ) : HTMLElement {
444572 const chevron = element ( "span" , {
445573 className : visible ? `chevron ${ isExpanded ? "expanded" : "" } ` : "chevron" ,
0 commit comments