@@ -34,7 +34,12 @@ import { canUpdateBranch, coerceMergeMethod } from '../lib/merge-box-status';
3434import { ConversationView } from '../views/conversation/ConversationView' ;
3535import { DiffWorkspace } from '../views/pr-modal/DiffWorkspace' ;
3636import { ShellResizers } from '../views/pr-modal/ShellResizers' ;
37- import { LAYOUT_CENTERED , LAYOUT_DIFF , layoutClassName } from '../lib/layout-mode' ;
37+ import {
38+ LAYOUT_CENTERED ,
39+ LAYOUT_DIFF ,
40+ layoutClassName ,
41+ isDiffUnavailable ,
42+ } from '../lib/layout-mode' ;
3843import {
3944 compareCacheKey ,
4045 isAllCommitsFilter ,
@@ -2121,7 +2126,8 @@ export function PrModalApp({
21212126 */
21222127 if ( searchHitHasRowIndex ( hit ) ) {
21232128 if ( layoutMode !== LAYOUT_DIFF ) {
2124- setLayoutMode ( LAYOUT_DIFF ) ;
2129+ expandDiff ( ) ;
2130+ if ( useModalStore . getState ( ) . layoutMode !== LAYOUT_DIFF ) return ;
21252131 }
21262132 const j = searchJumpRef . current ;
21272133 const top = scrollTopForIndex (
@@ -2507,7 +2513,10 @@ export function PrModalApp({
25072513 line ?: number | null ;
25082514 side ?: string | null ;
25092515 } ) => {
2510- if ( layoutMode !== LAYOUT_DIFF ) setLayoutMode ( LAYOUT_DIFF ) ;
2516+ if ( layoutMode !== LAYOUT_DIFF ) {
2517+ expandDiff ( ) ;
2518+ if ( useModalStore . getState ( ) . layoutMode !== LAYOUT_DIFF ) return ;
2519+ }
25112520 // Thread jump is a focus change — clear any line selection island.
25122521 clearLineSelectionForNav ( ) ;
25132522
@@ -4300,7 +4309,13 @@ export function PrModalApp({
43004309 // stacked PRs (do not clobber with the target PR's stored session layout).
43014310 const routePage = normalizePage ( initialRoute ?. page ) ;
43024311 if ( routePage ) {
4303- setLayoutMode ( routePage === 'diff' ? LAYOUT_DIFF : LAYOUT_CENTERED ) ;
4312+ const wantDiff = routePage === 'diff' ;
4313+ const emptyDiff =
4314+ typeof isDiffUnavailable === 'function' &&
4315+ isDiffUnavailable ( detail ) ;
4316+ setLayoutMode (
4317+ wantDiff && ! emptyDiff ? LAYOUT_DIFF : LAYOUT_CENTERED
4318+ ) ;
43044319 }
43054320
43064321 // Effective page for session gate (URI page, else stored page)
@@ -4330,8 +4345,13 @@ export function PrModalApp({
43304345 ! routePage &&
43314346 ( stored . layoutMode === 'diff' || stored . layoutMode === 'centered' )
43324347 ) {
4348+ const emptyDiff =
4349+ typeof isDiffUnavailable === 'function' &&
4350+ isDiffUnavailable ( detail ) ;
43334351 setLayoutMode (
4334- stored . layoutMode === 'diff' ? LAYOUT_DIFF : LAYOUT_CENTERED
4352+ stored . layoutMode === 'diff' && ! emptyDiff
4353+ ? LAYOUT_DIFF
4354+ : LAYOUT_CENTERED
43354355 ) ;
43364356 }
43374357 if ( stored . diffMode === 'split' || stored . diffMode === 'unified' ) {
@@ -4446,7 +4466,10 @@ export function PrModalApp({
44464466
44474467 ghSelectionAppliedRef . current = applyKey ;
44484468 setActiveFilePath ( path ) ;
4449- if ( layoutMode !== LAYOUT_DIFF ) setLayoutMode ( LAYOUT_DIFF ) ;
4469+ if ( layoutMode !== LAYOUT_DIFF ) {
4470+ expandDiff ( ) ;
4471+ if ( useModalStore . getState ( ) . layoutMode !== LAYOUT_DIFF ) return ;
4472+ }
44504473
44514474 if ( startLine != null && Number ( startLine ) >= 1 ) {
44524475 const end =
@@ -5054,6 +5077,15 @@ export function PrModalApp({
50545077
50555078 /** Instant layout swap — keep-alive panels, no fade/scale on Diff ↔ Conversation. */
50565079 function expandDiff ( after ?: any ) {
5080+ // Empty-commit PRs (0 files) have no Diff surface — stay on Conversation.
5081+ const liveDetail =
5082+ useModalStore . getState ( ) . localDetail || detail || null ;
5083+ if (
5084+ typeof isDiffUnavailable === 'function' &&
5085+ isDiffUnavailable ( liveDetail )
5086+ ) {
5087+ return ;
5088+ }
50575089 setAnimClass ( '' ) ;
50585090 setLayoutMode ( LAYOUT_DIFF ) ;
50595091 after ?.( ) ;
@@ -5069,10 +5101,30 @@ export function PrModalApp({
50695101 // peer-opt → runPaletteCommand paths (monitor fired but layout stuck).
50705102 const live =
50715103 useModalStore . getState ( ) . layoutMode || layoutMode ;
5072- if ( live === LAYOUT_DIFF ) collapseDiff ( ) ;
5073- else expandDiff ( ) ;
5104+ if ( live === LAYOUT_DIFF ) {
5105+ collapseDiff ( ) ;
5106+ return ;
5107+ }
5108+ expandDiff ( ) ;
50745109 }
50755110
5111+ // If meta settles to 0 files while Diff is open (or deep-link forced Diff
5112+ // before meta), leave Diff — empty-commit PRs have no file surface.
5113+ useEffect ( ( ) => {
5114+ if ( ! open ) return ;
5115+ const liveDetail =
5116+ useModalStore . getState ( ) . localDetail || detail || null ;
5117+ if (
5118+ typeof isDiffUnavailable !== 'function' ||
5119+ ! isDiffUnavailable ( liveDetail )
5120+ ) {
5121+ return ;
5122+ }
5123+ if ( useModalStore . getState ( ) . layoutMode === LAYOUT_DIFF ) {
5124+ setLayoutMode ( LAYOUT_CENTERED ) ;
5125+ }
5126+ } , [ open , detail ?. changedFiles , detail ?. additions , detail ?. deletions , detail ?. files , setLayoutMode ] ) ;
5127+
50765128 /** Play exit animation, then notify host to unmount (modal + side sheet). */
50775129 const requestClose = useCallback ( ( ) => {
50785130 // Embed has no exit chrome — ignore close (Escape stays no-op for shell).
0 commit comments