From d595d9f5d669189177faf5c20793abe7ccd4849d Mon Sep 17 00:00:00 2001 From: Gagancreates Date: Mon, 8 Jun 2026 01:17:49 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20measure=20actual=20DOM=20overflow=20inst?= =?UTF-8?q?ead=20of=20estimating=20=E2=80=94=20eliminates=20half-text=20an?= =?UTF-8?q?d=20disappearing=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/chat-input-with-mentions.tsx | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx index 878ba8ca..015d8253 100644 --- a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx +++ b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ArrowUp, @@ -283,36 +283,39 @@ function ChatInputInner({ const [permissionMode, setPermissionMode] = useState('auto') const [recentWorkDirs, setRecentWorkDirs] = useState([]) const toolbarRef = useRef(null) - // 0 = all full; collapse order (right→left): 1=code, 2=search, 3=workDir, 4=perm + const leftGroupRef = useRef(null) + // 0 = all full; collapse order (right→left): 1=code, 2=perm, 3=search, 4=workDir const [collapseLevel, setCollapseLevel] = useState(0) + // After every render, synchronously check if the left group overflows and step up + // one collapse level if so. Cascades (each level change triggers another check) + // until items fit — all resolved before the browser paints, so no half-visible text. + useLayoutEffect(() => { + const left = leftGroupRef.current + if (!left) return + if (left.scrollWidth > left.clientWidth + 1) { + setCollapseLevel(prev => Math.min(4, prev + 1)) + } + }, [collapseLevel, workDir, codeModeEnabled, codeModeFeatureEnabled, searchEnabled, searchAvailable]) + + // When the outer toolbar grows, try stepping back down one level at a time. + // Require slack > item's expansion delta + buffer to avoid oscillation at boundaries. useEffect(() => { - const el = toolbarRef.current - if (!el) return - const ro = new ResizeObserver(([entry]) => { - const w = entry.contentRect.width - // Approximate full/compact px widths per item (icon = 28, gap = 8) - const codeOn = codeModeEnabled && codeModeFeatureEnabled - const codeFullW = codeOn ? 85 + 8 : 0 - const codeIconW = codeOn ? 28 + 8 : 0 - const searchFullW = searchAvailable ? (searchEnabled ? 75 + 8 : 28 + 8) : 0 - const searchIconW = searchAvailable ? 28 + 8 : 0 - const workDirFullW = workDir ? 120 + 8 : 0 - const workDirIconW = workDir ? 28 + 8 : 0 - const permFullW = 65 + 8 // "Auto" label - const permIconW = 28 + 8 - const base = 28 + 80 + 28 + 2 * 8 // +btn + model + submit + 2 outer gaps - // Collapse order right→left: code(1) → perm(2) → search(3) → workDir(4) - const t0 = base + workDirFullW + searchFullW + permFullW + codeFullW // all full - const t1 = base + workDirFullW + searchFullW + permFullW + codeIconW // code → icon - const t2 = base + workDirFullW + searchFullW + permIconW + codeIconW // perm → icon - const t3 = base + workDirFullW + searchIconW + permIconW + codeIconW // search → icon - const t4 = base + workDirIconW + searchIconW + permIconW + codeIconW // workDir → icon - setCollapseLevel(w >= t0 ? 0 : w >= t1 ? 1 : w >= t2 ? 2 : w >= t3 ? 3 : w >= t4 ? 4 : 4) + const outer = toolbarRef.current + const left = leftGroupRef.current + if (!outer || !left) return + // Width gained when un-collapsing each level (full-width minus icon-width, px) + const expandDelta = [107, 62, 52, 132] // levels 1→0, 2→1, 3→2, 4→3 + const ro = new ResizeObserver(() => { + const slack = left.clientWidth - left.scrollWidth + setCollapseLevel(prev => { + if (prev === 0) return 0 + return slack > (expandDelta[prev - 1] ?? 0) + 20 ? prev - 1 : prev + }) }) - ro.observe(el) + ro.observe(outer) return () => ro.disconnect() - }, [workDir, searchAvailable, searchEnabled, codeModeEnabled, codeModeFeatureEnabled]) + }, []) // When a run exists, freeze the dropdown to the run's resolved model+provider. useEffect(() => { @@ -789,7 +792,7 @@ function ChatInputInner({ />
-
+