diff --git a/apps/desktop/src/app/chat/composer/index.tsx b/apps/desktop/src/app/chat/composer/index.tsx index ae175c902eb..1ecc76de8bc 100644 --- a/apps/desktop/src/app/chat/composer/index.tsx +++ b/apps/desktop/src/app/chat/composer/index.tsx @@ -543,9 +543,12 @@ export function ChatBar({ syncComposerMetrics() }, [poppedOut, syncComposerMetrics]) - // Keep the floating box on-screen: re-clamp (with the real measured size) when - // it pops out and whenever the window resizes — so a position persisted on a - // bigger/other monitor, or a shrunk window, can never strand it out of reach. + // Keep the floating box on-screen: re-clamp (with the real measured size + + // thread bounds) when it pops out and on every window resize — so a position + // persisted on a bigger/other monitor, a shrunk window, or now-wider sidebar + // can never strand it. The rAF pass re-clamps after layout settles (sidebar + // widths, fonts), so anyone loading in out of bounds is pulled back + saved + // even if the first measure was premature. useEffect(() => { if (!poppedOut) { return undefined @@ -558,10 +561,14 @@ export function ChatBar({ } reclamp(true) + const raf = requestAnimationFrame(() => reclamp(true)) const onResize = () => reclamp(false) window.addEventListener('resize', onResize) - return () => window.removeEventListener('resize', onResize) + return () => { + cancelAnimationFrame(raf) + window.removeEventListener('resize', onResize) + } }, [poppedOut]) useEffect(() => { diff --git a/apps/desktop/src/store/composer-popout.ts b/apps/desktop/src/store/composer-popout.ts index 1cc2d5f2f96..a739f2f3cb8 100644 --- a/apps/desktop/src/store/composer-popout.ts +++ b/apps/desktop/src/store/composer-popout.ts @@ -88,9 +88,11 @@ export function readPopoutBounds(composer: Element | null): PopoutBounds | undef return undefined } - const { bottom, left, right, top } = el.getBoundingClientRect() + const { bottom, height, left, right, top, width } = el.getBoundingClientRect() - return { bottom, left, right, top } + // Pre-layout (mount before first layout) the rect is empty — fall back to the + // window rather than clamping the box into a collapsed area. + return width > 0 && height > 0 ? { bottom, left, right, top } : undefined } // Bound the bottom/right inset so the WHOLE box stays inside `area` (the thread