@@ -4914,6 +4914,16 @@ export default function ChatView({
49144914 </ Suspense >
49154915 </ div >
49164916 ) : null ;
4917+ const isFullscreenPreviewMode = Boolean (
4918+ previewOpen && activeProject && previewLayoutMode === "fullscreen" ,
4919+ ) ;
4920+ const floatingComposerStatus = isTurnActive
4921+ ? `Working for ${ formatElapsed ( activeWorkStartedAt , nowIso ) } `
4922+ : showPlanFollowUpPrompt && activeProposedPlan
4923+ ? "Plan ready for follow-up"
4924+ : phase === "disconnected"
4925+ ? "Chat offline"
4926+ : "Ready for follow-up" ;
49174927
49184928 return (
49194929 < div className = "flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-background" >
@@ -4982,7 +4992,7 @@ export default function ChatView({
49824992 < div
49834993 ref = { previewSplitRef }
49844994 className = { cn (
4985- "flex min-h-0 min-w-0 flex-1" ,
4995+ "relative flex min-h-0 min-w-0 flex-1" ,
49864996 previewOpen && activeProject && previewLayoutMode === "top"
49874997 ? "flex-col"
49884998 : previewOpen && activeProject && previewLayoutMode === "side"
@@ -5051,14 +5061,15 @@ export default function ChatView({
50515061 </ div >
50525062 ) : null }
50535063
5054- { /* Chat column — hidden in fullscreen preview mode */ }
5064+ { /* Chat column */ }
50555065 < div
50565066 className = { cn (
50575067 "flex min-h-0 min-w-0 flex-1 flex-col" ,
5058- previewOpen && activeProject && previewLayoutMode === "fullscreen" && "hidden" ,
5068+ isFullscreenPreviewMode &&
5069+ "pointer-events-none absolute inset-x-0 bottom-0 z-20 min-h-0 flex-none" ,
50595070 ) }
50605071 >
5061- { isMobileCompanion ? (
5072+ { ! isFullscreenPreviewMode && isMobileCompanion ? (
50625073 < div className = "mx-auto w-full max-w-7xl px-3 pt-3 sm:px-5" >
50635074 < MobileThreadAttentionBar
50645075 activePendingApproval = { activePendingApproval }
@@ -5073,75 +5084,87 @@ export default function ChatView({
50735084 </ div >
50745085 ) : null }
50755086 { /* Messages Wrapper */ }
5076- < div className = "relative flex min-h-0 flex-1 flex-col" >
5077- { /* Messages */ }
5078- < div
5079- ref = { setMessagesScrollContainerRef }
5080- className = "min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain px-3 py-3 sm:px-5 sm:py-4"
5081- onScroll = { onMessagesScroll }
5082- onClickCapture = { onMessagesClickCapture }
5083- onWheel = { onMessagesWheel }
5084- onPointerDown = { onMessagesPointerDown }
5085- onPointerUp = { onMessagesPointerUp }
5086- onPointerCancel = { onMessagesPointerCancel }
5087- onTouchStart = { onMessagesTouchStart }
5088- onTouchMove = { onMessagesTouchMove }
5089- onTouchEnd = { onMessagesTouchEnd }
5090- onTouchCancel = { onMessagesTouchEnd }
5091- >
5092- < MessagesTimeline
5093- threadId = { activeThread . id }
5094- key = { activeThread . id }
5095- hasMessages = { timelineEntries . length > 0 }
5096- isWorking = { isWorking }
5097- activeTurnInProgress = { isWorking || ! latestTurnSettled }
5098- activeTurnStartedAt = { activeWorkStartedAt }
5099- scrollContainer = { messagesScrollElement }
5100- timelineEntries = { timelineEntries }
5101- completionDividerBeforeEntryId = { completionDividerBeforeEntryId }
5102- completionSummary = { completionSummary }
5103- turnDiffSummaryByAssistantMessageId = { turnDiffSummaryByAssistantMessageId }
5104- nowIso = { nowIso }
5105- expandedWorkGroups = { expandedWorkGroups }
5106- onToggleWorkGroup = { onToggleWorkGroup }
5107- revertTurnCountByUserMessageId = { revertTurnCountByUserMessageId }
5108- onRevertUserMessage = { onRevertUserMessage }
5109- isRevertingCheckpoint = { isRevertingCheckpoint }
5110- onImageExpand = { onExpandTimelineImage }
5111- markdownCwd = { gitCwd ?? undefined }
5112- resolvedTheme = { resolvedTheme }
5113- showReasoningContent = { showReasoningContent }
5114- timestampFormat = { timestampFormat }
5115- workspaceRoot = { activeProject ?. cwd ?? undefined }
5116- shortcutGuides = { chatShortcutGuides }
5117- onRemoveQueuedMessage = { onRemoveQueuedMessage }
5118- onOpenSettings = { ( ) => void navigate ( { to : "/settings" } ) }
5119- onOpenTurnDiff = { handleOpenTurnDiff }
5120- />
5121- < div ref = { messagesBottomRef } aria-hidden = "true" className = "h-px w-full" />
5122- </ div >
5123-
5124- { /* scroll to bottom pill — shown when user has scrolled away from the bottom */ }
5125- { showScrollToBottom && (
5126- < div className = "pointer-events-none absolute bottom-1 left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" >
5127- < button
5128- type = "button"
5129- onClick = { ( ) => scrollMessagesToBottom ( "smooth" ) }
5130- className = "pointer-events-auto flex items-center gap-1.5 rounded-full border border-border/60 bg-card px-3 py-1 text-muted-foreground text-xs shadow-sm transition-colors hover:border-border hover:text-foreground hover:cursor-pointer"
5131- >
5132- < ChevronDownIcon className = "size-3.5" />
5133- Scroll to bottom
5134- </ button >
5087+ { ! isFullscreenPreviewMode ? (
5088+ < div className = "relative flex min-h-0 flex-1 flex-col" >
5089+ { /* Messages */ }
5090+ < div
5091+ ref = { setMessagesScrollContainerRef }
5092+ className = "min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain px-3 py-3 sm:px-5 sm:py-4"
5093+ onScroll = { onMessagesScroll }
5094+ onClickCapture = { onMessagesClickCapture }
5095+ onWheel = { onMessagesWheel }
5096+ onPointerDown = { onMessagesPointerDown }
5097+ onPointerUp = { onMessagesPointerUp }
5098+ onPointerCancel = { onMessagesPointerCancel }
5099+ onTouchStart = { onMessagesTouchStart }
5100+ onTouchMove = { onMessagesTouchMove }
5101+ onTouchEnd = { onMessagesTouchEnd }
5102+ onTouchCancel = { onMessagesTouchEnd }
5103+ >
5104+ < MessagesTimeline
5105+ threadId = { activeThread . id }
5106+ key = { activeThread . id }
5107+ hasMessages = { timelineEntries . length > 0 }
5108+ isWorking = { isWorking }
5109+ activeTurnInProgress = { isWorking || ! latestTurnSettled }
5110+ activeTurnStartedAt = { activeWorkStartedAt }
5111+ scrollContainer = { messagesScrollElement }
5112+ timelineEntries = { timelineEntries }
5113+ completionDividerBeforeEntryId = { completionDividerBeforeEntryId }
5114+ completionSummary = { completionSummary }
5115+ turnDiffSummaryByAssistantMessageId = { turnDiffSummaryByAssistantMessageId }
5116+ nowIso = { nowIso }
5117+ expandedWorkGroups = { expandedWorkGroups }
5118+ onToggleWorkGroup = { onToggleWorkGroup }
5119+ revertTurnCountByUserMessageId = { revertTurnCountByUserMessageId }
5120+ onRevertUserMessage = { onRevertUserMessage }
5121+ isRevertingCheckpoint = { isRevertingCheckpoint }
5122+ onImageExpand = { onExpandTimelineImage }
5123+ markdownCwd = { gitCwd ?? undefined }
5124+ resolvedTheme = { resolvedTheme }
5125+ showReasoningContent = { showReasoningContent }
5126+ timestampFormat = { timestampFormat }
5127+ workspaceRoot = { activeProject ?. cwd ?? undefined }
5128+ shortcutGuides = { chatShortcutGuides }
5129+ onRemoveQueuedMessage = { onRemoveQueuedMessage }
5130+ onOpenSettings = { ( ) => void navigate ( { to : "/settings" } ) }
5131+ onOpenTurnDiff = { handleOpenTurnDiff }
5132+ />
5133+ < div ref = { messagesBottomRef } aria-hidden = "true" className = "h-px w-full" />
51355134 </ div >
5136- ) }
5137- </ div >
5135+
5136+ { /* scroll to bottom pill — shown when user has scrolled away from the bottom */ }
5137+ { showScrollToBottom && (
5138+ < div className = "pointer-events-none absolute bottom-1 left-1/2 z-30 flex -translate-x-1/2 justify-center py-1.5" >
5139+ < button
5140+ type = "button"
5141+ onClick = { ( ) => scrollMessagesToBottom ( "smooth" ) }
5142+ className = "pointer-events-auto flex items-center gap-1.5 rounded-full border border-border/60 bg-card px-3 py-1 text-muted-foreground text-xs shadow-sm transition-colors hover:border-border hover:text-foreground hover:cursor-pointer"
5143+ >
5144+ < ChevronDownIcon className = "size-3.5" />
5145+ Scroll to bottom
5146+ </ button >
5147+ </ div >
5148+ ) }
5149+ </ div >
5150+ ) : null }
51385151
51395152 { /* Input bar */ }
5140- < div className = { cn ( "px-3 pt-1.5 sm:px-5 sm:pt-2" , isGitRepo ? "pb-1" : "pb-3 sm:pb-4" ) } >
5153+ < div
5154+ className = { cn (
5155+ isFullscreenPreviewMode
5156+ ? "pointer-events-auto px-4 pb-4 sm:px-6 sm:pb-6"
5157+ : "px-3 pt-1.5 sm:px-5 sm:pt-2" ,
5158+ ! isFullscreenPreviewMode && ( isGitRepo ? "pb-1" : "pb-3 sm:pb-4" ) ,
5159+ ) }
5160+ >
51415161 < form
51425162 ref = { composerFormRef }
51435163 onSubmit = { onSend }
5144- className = "mx-auto w-full min-w-0 max-w-7xl"
5164+ className = { cn (
5165+ "w-full min-w-0" ,
5166+ isFullscreenPreviewMode ? "mx-auto max-w-4xl" : "mx-auto max-w-7xl" ,
5167+ ) }
51455168 data-chat-composer-form = "true"
51465169 >
51475170 < input
@@ -5154,8 +5177,10 @@ export default function ChatView({
51545177 />
51555178 < div
51565179 className = { cn (
5157- "group relative rounded-[22px] p-px transition-colors duration-200" ,
5158- composerProviderState . composerFrameClassName ,
5180+ "group relative p-px transition-colors duration-200" ,
5181+ isFullscreenPreviewMode
5182+ ? "overflow-hidden rounded-[30px] border border-white/10 bg-[rgba(28,28,32,0.92)] text-white shadow-[0_24px_80px_rgba(0,0,0,0.45)] backdrop-blur-2xl"
5183+ : [ "rounded-[22px]" , composerProviderState . composerFrameClassName ] ,
51595184 ) }
51605185 onDragEnter = { onComposerDragEnter }
51615186 onDragOver = { onComposerDragOver }
@@ -5179,22 +5204,46 @@ export default function ChatView({
51795204 </ div >
51805205 </ div >
51815206 ) }
5207+ { isFullscreenPreviewMode ? (
5208+ < div className = "flex items-center justify-between gap-3 border-white/10 border-b px-4 py-3 text-[12px] text-white/70" >
5209+ < span className = "truncate font-medium text-white/82" >
5210+ { floatingComposerStatus }
5211+ </ span >
5212+ < span className = "shrink-0 text-white/45" > Preview</ span >
5213+ </ div >
5214+ ) : null }
51825215 < div
51835216 className = { cn (
5184- "rounded-[20px] border bg-card transition-colors duration-200 focus-within:border-ring/45" ,
5185- isDragOverComposer ? "border-primary/70 bg-accent/30" : "border-border" ,
5186- composerProviderState . composerSurfaceClassName ,
5217+ isFullscreenPreviewMode
5218+ ? "bg-transparent"
5219+ : "rounded-[20px] border bg-card transition-colors duration-200 focus-within:border-ring/45" ,
5220+ isFullscreenPreviewMode
5221+ ? null
5222+ : isDragOverComposer
5223+ ? "border-primary/70 bg-accent/30"
5224+ : "border-border" ,
5225+ ! isFullscreenPreviewMode && composerProviderState . composerSurfaceClassName ,
51875226 ) }
51885227 >
51895228 { activePendingApproval ? (
5190- < div className = "rounded-t-[19px] border-b border-border/65 bg-muted/20" >
5229+ < div
5230+ className = { cn (
5231+ "border-b border-border/65 bg-muted/20" ,
5232+ ! isFullscreenPreviewMode && "rounded-t-[19px]" ,
5233+ ) }
5234+ >
51915235 < ComposerPendingApprovalPanel
51925236 approval = { activePendingApproval }
51935237 pendingCount = { pendingApprovals . length }
51945238 />
51955239 </ div >
51965240 ) : pendingUserInputs . length > 0 ? (
5197- < div className = "rounded-t-[19px] border-b border-border/65 bg-muted/20" >
5241+ < div
5242+ className = { cn (
5243+ "border-b border-border/65 bg-muted/20" ,
5244+ ! isFullscreenPreviewMode && "rounded-t-[19px]" ,
5245+ ) }
5246+ >
51985247 < ComposerPendingUserInputPanel
51995248 pendingUserInputs = { pendingUserInputs }
52005249 respondingRequestIds = { respondingRequestIds }
@@ -5205,7 +5254,12 @@ export default function ChatView({
52055254 />
52065255 </ div >
52075256 ) : showPlanFollowUpPrompt && activeProposedPlan ? (
5208- < div className = "rounded-t-[19px] border-b border-border/65 bg-muted/20" >
5257+ < div
5258+ className = { cn (
5259+ "border-b border-border/65 bg-muted/20" ,
5260+ ! isFullscreenPreviewMode && "rounded-t-[19px]" ,
5261+ ) }
5262+ >
52095263 < ComposerPlanFollowUpBanner
52105264 key = { activeProposedPlan . id }
52115265 planTitle = { proposedPlanTitle ( activeProposedPlan . planMarkdown ) ?? null }
@@ -5866,7 +5920,7 @@ export default function ChatView({
58665920 </ form >
58675921 </ div >
58685922
5869- { isGitRepo && (
5923+ { ! isFullscreenPreviewMode && isGitRepo && (
58705924 < BranchToolbar
58715925 threadId = { activeThread . id }
58725926 onEnvModeChange = { onEnvModeChange }
@@ -5877,7 +5931,7 @@ export default function ChatView({
58775931 : { } ) }
58785932 />
58795933 ) }
5880- { pullRequestDialogState ? (
5934+ { ! isFullscreenPreviewMode && pullRequestDialogState ? (
58815935 < PullRequestThreadDialog
58825936 key = { pullRequestDialogState . key }
58835937 open
0 commit comments