From 71a888fe4d0d0496fcbe90400f83afdb352c5b0e Mon Sep 17 00:00:00 2001 From: murashit Date: Tue, 16 Jun 2026 10:41:55 +0900 Subject: [PATCH] Fix composer height reset after send --- src/features/chat/ui/composer.tsx | 6 ++ .../chat/ui/renderers/composer.test.ts | 94 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/features/chat/ui/composer.tsx b/src/features/chat/ui/composer.tsx index 68e4e0e1..b9b11d96 100644 --- a/src/features/chat/ui/composer.tsx +++ b/src/features/chat/ui/composer.tsx @@ -93,6 +93,8 @@ export function ComposerShell({ const suggestionsRef = useRef(null); const selectedSuggestionRef = useRef(null); const previousDraftRef = useRef(draft); + const onHeightChangeRef = useRef(callbacks.onHeightChange); + onHeightChangeRef.current = callbacks.onHeightChange; const preservedSelection = preserveComposerSelection(composerRef.current, previousDraftRef.current, draft); useLayoutEffect(() => { const composer = composerRef.current; @@ -103,6 +105,10 @@ export function ComposerShell({ onComposer(null); }; }, [onComposer]); + useLayoutEffect(() => { + const composer = composerRef.current; + if (syncComposerHeight(composer)) onHeightChangeRef.current(); + }, [draft]); useLayoutEffect(() => { const container = suggestionsRef.current; const selected = selectedSuggestionRef.current; diff --git a/tests/features/chat/ui/renderers/composer.test.ts b/tests/features/chat/ui/renderers/composer.test.ts index bdf31a2c..3ae4a0a0 100644 --- a/tests/features/chat/ui/renderers/composer.test.ts +++ b/tests/features/chat/ui/renderers/composer.test.ts @@ -423,6 +423,100 @@ describe("ComposerShell decisions", () => { } }); + it("shrinks composer autogrow when the controlled draft clears after send", () => { + const parent = document.createElement("div"); + const callbacks = composerCallbacks(); + const onComposer = vi.fn(); + let scrollHeight = 120; + const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "scrollHeight"); + Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", { + get: () => scrollHeight, + configurable: true, + }); + + try { + renderUiRoot( + parent, + h(ComposerShell, { + viewId: "view", + draft: "line one\nline two", + busy: false, + canInterrupt: false, + normalPlaceholder: "Ask Codex to work on this task...", + suggestions: [], + selectedSuggestionIndex: 0, + callbacks, + meta: { + fatal: null, + context: { + cells: [ + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + ], + percent: "--%", + }, + statusSummary: "Context unavailable, plan off, auto-review off, fast off, model default, reasoning effort default", + model: "default", + effort: null, + planActive: false, + autoReviewActive: false, + fastActive: false, + }, + onComposer, + }), + ); + const composer = parent.querySelector(".codex-panel__composer-input"); + if (!composer) throw new Error("Expected composer shell elements to mount."); + expect(composer.style.height).toBe("120px"); + + scrollHeight = 56; + renderUiRoot( + parent, + h(ComposerShell, { + viewId: "view", + draft: "", + busy: false, + canInterrupt: false, + normalPlaceholder: "Ask Codex to work on this task...", + suggestions: [], + selectedSuggestionIndex: 0, + callbacks, + meta: { + fatal: null, + context: { + cells: [ + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + { text: "⣀", placeholder: true }, + ], + percent: "--%", + }, + statusSummary: "Context unavailable, plan off, auto-review off, fast off, model default, reasoning effort default", + model: "default", + effort: null, + planActive: false, + autoReviewActive: false, + fastActive: false, + }, + onComposer, + }), + ); + + expect(parent.querySelector(".codex-panel__composer-input")).toBe(composer); + expect(composer.style.height).toBe("56px"); + expect(callbacks.onHeightChange).toHaveBeenCalled(); + } finally { + if (descriptor) { + Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", descriptor); + } else { + Reflect.deleteProperty(HTMLTextAreaElement.prototype, "scrollHeight"); + } + } + }); + it("scrolls the selected composer suggestion fully into view below the viewport", () => { const { container, option } = composerSuggestionScrollFixture({ clientHeight: 100,