Skip to content

Commit 57608fa

Browse files
authored
🤖 fix: eliminate chat textarea race condition on initial render (#958)
The chat input textarea could appear too large on initial app launch, then collapse to correct size when text was entered. **Root cause:** The previous fix used `useLayoutEffect` but still relied on measuring `scrollHeight` after setting `height: auto`. For an empty textarea, this measurement could return incorrect values before the flex container layout had settled. **Fix:** Skip `scrollHeight` measurement entirely when the textarea is empty or whitespace-only. Instead, clear the inline height style and let the CSS `min-h-8` (32px) handle sizing. This is deterministic and avoids the race condition. Also unified height management: ChatInput no longer hardcodes `36px` when clearing input - it clears the inline style to let VimTextArea's CSS take over, ensuring consistent behavior. --- _Generated with `mux`_
1 parent f9a85a2 commit 57608fa

File tree

2 files changed

+21
-9
lines changed

2 files changed

+21
-9
lines changed

src/browser/components/ChatInput/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,10 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
641641
if (ok) {
642642
setInput("");
643643
setImageAttachments([]);
644+
// Height is managed by VimTextArea's useLayoutEffect - clear inline style
645+
// to let CSS min-height take over
644646
if (inputRef.current) {
645-
inputRef.current.style.height = "36px";
647+
inputRef.current.style.height = "";
646648
}
647649
}
648650
setIsSending(false);
@@ -661,7 +663,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
661663
if (parsed.type === "clear") {
662664
setInput("");
663665
if (inputRef.current) {
664-
inputRef.current.style.height = "36px";
666+
inputRef.current.style.height = "";
665667
}
666668
await props.onTruncateHistory(1.0);
667669
setToast({
@@ -676,7 +678,7 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
676678
if (parsed.type === "truncate") {
677679
setInput("");
678680
if (inputRef.current) {
679-
inputRef.current.style.height = "36px";
681+
inputRef.current.style.height = "";
680682
}
681683
await props.onTruncateHistory(parsed.percentage);
682684
setToast({
@@ -994,9 +996,9 @@ export const ChatInput: React.FC<ChatInputProps> = (props) => {
994996
// These will be restored if the send operation fails
995997
setInput("");
996998
setImageAttachments([]);
997-
// Reset textarea height
999+
// Clear inline height style - VimTextArea's useLayoutEffect will handle sizing
9981000
if (inputRef.current) {
999-
inputRef.current.style.height = "36px";
1001+
inputRef.current.style.height = "";
10001002
}
10011003

10021004
const result = await api.workspace.sendMessage({

src/browser/components/VimTextArea.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,25 @@ export const VimTextArea = React.forwardRef<HTMLTextAreaElement, VimTextAreaProp
8181
const yankBufferRef = useRef<string>("");
8282

8383
// Auto-resize when value changes
84-
// Uses useLayoutEffect to measure and set height synchronously before paint,
85-
// preventing flash of wrong size on initial render
84+
// Uses useLayoutEffect to measure and set height synchronously before paint.
85+
// Key insight: when value is empty or whitespace-only, skip measurement entirely
86+
// and use the CSS min-height. This avoids race conditions where scrollHeight
87+
// returns incorrect values before flexbox layout settles.
8688
useLayoutEffect(() => {
8789
const el = textareaRef.current;
8890
if (!el) return;
89-
// Reset to auto to get accurate scrollHeight measurement
91+
92+
// For empty/whitespace content, let CSS min-height handle sizing.
93+
// This is deterministic and avoids measuring scrollHeight when the
94+
// flex container may not have settled.
95+
if (!value.trim()) {
96+
el.style.height = "";
97+
return;
98+
}
99+
100+
// For non-empty content, measure and set height
90101
el.style.height = "auto";
91102
const max = window.innerHeight * 0.5; // 50vh
92-
// scrollHeight gives the full content height; clamp to max
93103
el.style.height = Math.min(el.scrollHeight, max) + "px";
94104
}, [value]);
95105

0 commit comments

Comments
 (0)