@@ -33,6 +33,7 @@ interface MountedPayload {
3333 hostContext ?: HostContext ;
3434 errorMessage ?: string | null ;
3535 visibleFileCount ?: number ;
36+ presentation ?: "inline" | "fullscreen" ;
3637 } ) : void ;
3738 unmount ( ) : void ;
3839}
@@ -44,6 +45,8 @@ let hostContext: HostContext | undefined;
4445let card : ToolResultCard | null = null ;
4546let expanded = false ;
4647let reviewFilesExpanded = false ;
48+ let reviewDisplayModePending = false ;
49+ let reviewDisplayModeError : string | null = null ;
4750let errorMessage : string | null = null ;
4851let currentPayload : MountedPayload | null = null ;
4952let currentPayloadContainer : HTMLElement | null = null ;
@@ -78,6 +81,8 @@ async function boot(): Promise<void> {
7881 card = null ;
7982 expanded = false ;
8083 reviewFilesExpanded = false ;
84+ reviewDisplayModePending = false ;
85+ reviewDisplayModeError = null ;
8186 errorMessage = "No result card is available for this tool result." ;
8287 render ( ) ;
8388 return ;
@@ -87,16 +92,27 @@ async function boot(): Promise<void> {
8792 card = nextCard ;
8893 expanded = isReviewTool ( tool ) && isExpandableCard ( nextCard ) ;
8994 reviewFilesExpanded = false ;
95+ reviewDisplayModePending = false ;
96+ reviewDisplayModeError = null ;
9097 errorMessage = null ;
9198 render ( ) ;
9299 } ;
93100
94101 app . onhostcontextchanged = ( ctx ) => {
102+ const previousDisplayMode = hostContext ?. displayMode ;
95103 hostContext = {
96104 ...hostContext ,
97105 ...ctx ,
98106 } ;
99107 applyHostContext ( ) ;
108+ if (
109+ previousDisplayMode !== hostContext . displayMode &&
110+ card &&
111+ isReviewTool ( card . tool )
112+ ) {
113+ render ( ) ;
114+ return ;
115+ }
100116 renderPayloadIfNeeded ( ) ;
101117 } ;
102118
@@ -216,7 +232,10 @@ function renderEmpty(message: string, tone: "muted" | "error" = "muted"): void {
216232}
217233
218234async function renderPayloadIfNeeded ( ) : Promise < void > {
219- if ( ! card || ! currentPayloadContainer || ! expanded ) return ;
235+ const fullscreenReview = card &&
236+ isReviewTool ( card . tool ) &&
237+ hostContext ?. displayMode === "fullscreen" ;
238+ if ( ! card || ! currentPayloadContainer || ( ! expanded && ! fullscreenReview ) ) return ;
220239
221240 const target = currentPayloadContainer ;
222241
@@ -262,12 +281,23 @@ async function renderPayloadIfNeeded(): Promise<void> {
262281 }
263282
264283 if ( isReviewTool ( card . tool ) || isPatchTool ( card . tool ) ) {
265- 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
266290 ? Math . max ( 3 , ( card . files ?? [ ] ) . slice ( 0 , 3 ) . length )
267291 : undefined ;
268292
269293 if ( currentPayload ) {
270- currentPayload . update ( { card, hostContext, errorMessage, visibleFileCount } ) ;
294+ currentPayload . update ( {
295+ card,
296+ hostContext,
297+ errorMessage,
298+ visibleFileCount,
299+ presentation,
300+ } ) ;
271301 return ;
272302 }
273303
@@ -281,6 +311,7 @@ async function renderPayloadIfNeeded(): Promise<void> {
281311 hostContext,
282312 errorMessage,
283313 visibleFileCount,
314+ presentation,
284315 } ) ;
285316 return ;
286317 }
@@ -351,11 +382,17 @@ function renderHeaderSummary(card: ToolResultCard): HTMLElement {
351382function renderReviewCard ( card : ToolResultCard , display : ToolDisplay ) : void {
352383 unmountPayload ( ) ;
353384
385+ if ( hostContext ?. displayMode === "fullscreen" ) {
386+ renderFullscreenReview ( card , display ) ;
387+ return ;
388+ }
389+
354390 const files = card . files ?? [ ] ;
355391 const hiddenCount = Math . max ( 0 , files . length - 3 ) ;
356392 const expandable = isExpandableCard ( card ) ;
357393 const main = element ( "main" , { className : "shell" } ) ;
358394 const section = element ( "section" , { className : "tool-card review" } ) ;
395+ const headerRow = element ( "div" , { className : "review-header-row" } ) ;
359396 const header = element ( "button" , {
360397 className : "tool-header review-header" ,
361398 type : "button" ,
@@ -389,7 +426,28 @@ function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void {
389426 renderChevron ( expanded , expandable ) ,
390427 ) ;
391428
392- section . append ( header ) ;
429+ headerRow . append ( header ) ;
430+ if ( files . length > 0 && canRequestDisplayMode ( "fullscreen" ) ) {
431+ const reviewButton = element ( "button" , {
432+ className : "review-button" ,
433+ type : "button" ,
434+ text : reviewDisplayModePending ? "Opening…" : "Review" ,
435+ disabled : reviewDisplayModePending ,
436+ } ) ;
437+ reviewButton . setAttribute ( "aria-busy" , String ( reviewDisplayModePending ) ) ;
438+ reviewButton . addEventListener ( "click" , ( ) => {
439+ void requestReviewDisplayMode ( "fullscreen" ) ;
440+ } ) ;
441+ headerRow . append ( reviewButton ) ;
442+ }
443+
444+ section . append ( headerRow ) ;
445+ if ( reviewDisplayModeError ) {
446+ section . append ( element ( "div" , {
447+ className : "review-mode-error" ,
448+ text : reviewDisplayModeError ,
449+ } ) ) ;
450+ }
393451 if ( expanded ) {
394452 const body = element ( "div" , { className : "review-summary" } ) ;
395453 const payload = element ( "div" , { className : "review-payload" } ) ;
@@ -425,6 +483,82 @@ function renderReviewCard(card: ToolResultCard, display: ToolDisplay): void {
425483 renderPayloadIfNeeded ( ) ;
426484}
427485
486+ function renderFullscreenReview ( card : ToolResultCard , display : ToolDisplay ) : void {
487+ const main = element ( "main" , { className : "shell review-fullscreen-shell" } ) ;
488+ const section = element ( "section" , { className : "review-fullscreen" } ) ;
489+ const header = element ( "header" , { className : "review-fullscreen-header" } ) ;
490+ const titleGroup = element ( "div" , { className : "review-fullscreen-title" } ) ;
491+ const icon = element ( "span" , { className : "tool-icon" , ariaHidden : "true" } ) ;
492+ icon . append ( renderIcon ( display . icon ) ) ;
493+
494+ const heading = element ( "div" , { className : "review-title-group" } ) ;
495+ heading . append ( element ( "span" , { className : "tool-title" , text : "Review changes" } ) ) ;
496+ if ( display . label ) {
497+ heading . append ( element ( "span" , {
498+ className : "tool-label" ,
499+ text : display . label ,
500+ title : display . label ,
501+ } ) ) ;
502+ }
503+ titleGroup . append ( icon , heading ) ;
504+
505+ const actions = element ( "div" , { className : "review-fullscreen-actions" } ) ;
506+ const closeButton = element ( "button" , {
507+ className : "review-button" ,
508+ type : "button" ,
509+ text : reviewDisplayModePending ? "Closing…" : "Close review" ,
510+ disabled : reviewDisplayModePending ,
511+ } ) ;
512+ closeButton . setAttribute ( "aria-busy" , String ( reviewDisplayModePending ) ) ;
513+ closeButton . addEventListener ( "click" , ( ) => {
514+ void requestReviewDisplayMode ( "inline" ) ;
515+ } ) ;
516+ actions . append ( renderHeaderSummary ( card ) , closeButton ) ;
517+ header . append ( titleGroup , actions ) ;
518+
519+ const body = element ( "div" , { className : "review-fullscreen-body" } ) ;
520+ currentPayloadContainer = body ;
521+ section . append ( header ) ;
522+ if ( reviewDisplayModeError ) {
523+ section . append ( element ( "div" , {
524+ className : "review-mode-error" ,
525+ text : reviewDisplayModeError ,
526+ } ) ) ;
527+ }
528+ section . append ( body ) ;
529+ main . append ( section ) ;
530+ appRoot . replaceChildren ( main ) ;
531+ renderPayloadIfNeeded ( ) ;
532+ }
533+
534+ function canRequestDisplayMode ( mode : "inline" | "fullscreen" ) : boolean {
535+ return Boolean ( hostContext ?. availableDisplayModes ?. includes ( mode ) ) ;
536+ }
537+
538+ async function requestReviewDisplayMode ( mode : "inline" | "fullscreen" ) : Promise < void > {
539+ if ( ! app || reviewDisplayModePending ) return ;
540+
541+ reviewDisplayModePending = true ;
542+ reviewDisplayModeError = null ;
543+ render ( ) ;
544+
545+ try {
546+ const result = await app . requestDisplayMode ( { mode } ) ;
547+ hostContext = {
548+ ...hostContext ,
549+ displayMode : result . mode ,
550+ } ;
551+ if ( result . mode === "fullscreen" ) expanded = true ;
552+ } catch ( requestError ) {
553+ reviewDisplayModeError = requestError instanceof Error
554+ ? requestError . message
555+ : "Unable to change the review display mode." ;
556+ } finally {
557+ reviewDisplayModePending = false ;
558+ render ( ) ;
559+ }
560+ }
561+
428562function renderChevron ( isExpanded : boolean , visible : boolean ) : HTMLElement {
429563 const chevron = element ( "span" , {
430564 className : visible ? `chevron ${ isExpanded ? "expanded" : "" } ` : "chevron" ,
0 commit comments