From aff5ae692fb2e09a68344c841f5a6a461fb33f3f Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 22 Jun 2026 13:41:53 -0500 Subject: [PATCH] fix(desktop): move composer out of contain wrapper instead of portaling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the body-portal approach: render ChatBar as a sibling of the contain:[layout paint] chat wrapper (inside the same runtime boundary) rather than portaling the floating instance to . The wrapper is a containing block for — and clips — position:fixed descendants, which is what stranded the popped-out composer off-screen. As a sibling it anchors to the outer relative container: docked stays absolute (identical placement), floating resolves against the viewport. Both states stay mounted, so dock<->float no longer remounts the editor (the portal toggle did). --- apps/desktop/src/app/chat/composer/index.tsx | 16 +-- apps/desktop/src/app/chat/index.tsx | 120 ++++++++++--------- 2 files changed, 65 insertions(+), 71 deletions(-) diff --git a/apps/desktop/src/app/chat/composer/index.tsx b/apps/desktop/src/app/chat/composer/index.tsx index f6a5c5ff48d..44ad0fa2a39 100644 --- a/apps/desktop/src/app/chat/composer/index.tsx +++ b/apps/desktop/src/app/chat/composer/index.tsx @@ -12,7 +12,6 @@ import { useRef, useState } from 'react' -import { createPortal } from 'react-dom' import { hermesDirectiveFormatter, type SlashChipKind } from '@/components/assistant-ui/directive-text' import { composerFill, composerSurfaceGlass } from '@/components/chat/composer-dock' @@ -1924,7 +1923,7 @@ export function ChatBar({ ) - const composerOverlay = ( + return ( <> {dragging && poppedOut && (
- - ) - - return ( - <> - {/* Floating: portal to so position:fixed resolves against the - viewport. The chat content wrapper sets `contain: layout paint`, which - makes it a containing block for (and clips) fixed descendants — left - inline, the popped-out composer is positioned/clipped relative to the - chat column (which shifts with the sidebars), not the viewport, so the - viewport-based clamp can't keep it on-screen. Docked stays inline: it's - `absolute` within that column by design. */} - {poppedOut ? createPortal(composerOverlay, document.body) : composerOverlay} -
- - {showChatBar && ( - }> - - + {resumeExhausted && routedSessionId && ( +
+ +
+ +
+
+
)} -
- {resumeExhausted && routedSessionId && ( -
- -
- -
-
-
+ {showChatBar && } + + +
+ {/* Composer renders OUTSIDE the contain:[layout paint] wrapper above: + that wrapper is a containing block for — and clips — position:fixed + descendants, so the popped-out (fixed) composer would anchor to the + chat column (which shifts/resizes with the sidebars) and get clipped + off-screen instead of floating against the viewport. As a sibling it + anchors to the outer relative container instead: docked is absolute + (identical placement), floating resolves against the viewport. Both + states stay mounted here, so dock⇄float never remounts the editor. */} + {showChatBar && ( + }> + + )} - {showChatBar && } - - -
+ ) }