From 62f3872004589398ef5f2cdadf3e3f2e0f060555 Mon Sep 17 00:00:00 2001 From: Ammar Date: Sat, 9 May 2026 21:56:03 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20fix:=20keep=20chat=20input=20aut?= =?UTF-8?q?o-resize=20height=20applied?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - Ensure the textarea auto-resize hook reapplies the expected pixel height when the cached height matches but the DOM inline style was reset. - Cover capped-height deletion and external inline-style clearing regressions in the hook tests. --- _Generated with `mux` • Model: `openai:gpt-5.5` • Thinking: `xhigh` • Cost: `1782918{MUX_COSTS_USD:-n/a}`_ --- .../hooks/useAutoResizeTextarea.test.tsx | 31 +++++++++++++++++++ src/browser/hooks/useAutoResizeTextarea.ts | 9 +++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/browser/hooks/useAutoResizeTextarea.test.tsx b/src/browser/hooks/useAutoResizeTextarea.test.tsx index c524f40340..c4b58c7e44 100644 --- a/src/browser/hooks/useAutoResizeTextarea.test.tsx +++ b/src/browser/hooks/useAutoResizeTextarea.test.tsx @@ -8,6 +8,7 @@ function createFakeTextarea(initialScrollHeight: number): { ref: RefObject; assignments: string[]; setScrollHeight: (value: number) => void; + setInlineHeight: (value: string) => void; } { let height = ""; let scrollHeight = initialScrollHeight; @@ -35,6 +36,9 @@ function createFakeTextarea(initialScrollHeight: number): { setScrollHeight: (value) => { scrollHeight = value; }, + setInlineHeight: (value) => { + height = value; + }, }; } @@ -90,6 +94,33 @@ describe("useAutoResizeTextarea", () => { expect(textarea.assignments).toEqual(["auto", "40px"]); }); + it("restores the capped height when a deletion keeps the same measured height", () => { + const textarea = createFakeTextarea(800); + const { rerender } = renderHook(({ value }) => useAutoResizeTextarea(textarea.ref, value, 50), { + initialProps: { value: "line one\nline two\nline three" }, + }); + expect(textarea.assignments).toEqual(["auto", "500px"]); + textarea.assignments.length = 0; + + rerender({ value: "line one\nline two" }); + + expect(textarea.assignments).toEqual(["auto", "500px"]); + }); + + it("repairs the inline height after another caller clears it", () => { + const textarea = createFakeTextarea(800); + const { rerender } = renderHook(({ value }) => useAutoResizeTextarea(textarea.ref, value, 50), { + initialProps: { value: "line one\nline two" }, + }); + expect(textarea.assignments).toEqual(["auto", "500px"]); + textarea.assignments.length = 0; + + textarea.setInlineHeight(""); + rerender({ value: "line one\nline two!" }); + + expect(textarea.assignments).toEqual(["500px"]); + }); + it("shrinks when a longer replacement does not preserve the previous text", () => { const textarea = createFakeTextarea(84); const { rerender } = renderHook(({ value }) => useAutoResizeTextarea(textarea.ref, value, 50), { diff --git a/src/browser/hooks/useAutoResizeTextarea.ts b/src/browser/hooks/useAutoResizeTextarea.ts index 6e32895092..bc11c69a9d 100644 --- a/src/browser/hooks/useAutoResizeTextarea.ts +++ b/src/browser/hooks/useAutoResizeTextarea.ts @@ -69,7 +69,14 @@ export function useAutoResizeTextarea( nextHeight = Math.min(el.scrollHeight, max); } - if (appliedHeightRef.current !== nextHeight) { + // The cached height can match even after this effect temporarily set `auto`, or + // after callers cleared the inline style. Verify the DOM still has the px height + // before skipping the write; otherwise large drafts collapse to the CSS min-height. + const currentInlineHeight = Number.parseFloat(el.style.height); + const inlineHeightMatches = + Number.isFinite(currentInlineHeight) && Math.abs(currentInlineHeight - nextHeight) < 0.5; + + if (appliedHeightRef.current !== nextHeight || !inlineHeightMatches) { el.style.height = `${nextHeight}px`; appliedHeightRef.current = nextHeight; }