From 28e74c74dbe1dc36afe42a0dfac9a92fa26fdf73 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 11 Jun 2026 00:09:41 -0700 Subject: [PATCH] FN-6212: cap chat input height on tablets Limit the chat composer growth on tablet viewports while preserving desktop autosizing. - Add a tablet-specific 200px max height for the chat input textarea. - Apply the same cap in ChatView autosize calculations when tablet mode is active. - Generalize chat input height helpers and cover the tablet cap in autosize tests. Files changed: packages/dashboard/app/components/ChatView.css | 9 +++++++++ packages/dashboard/app/components/ChatView.tsx | 20 +++++++++++++------- packages/dashboard/app/components/QuickChatFAB.tsx | 4 ++-- .../__tests__/ChatView.chat-input-autosize.test.tsx | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-6212 Fusion-Task-Lineage: b11cd658-8289-4795-8e39-7d03a1d51d41 --- .../dashboard/app/components/ChatView.css | 9 +++++++++ .../dashboard/app/components/ChatView.tsx | 20 ++++++++++++------- .../dashboard/app/components/QuickChatFAB.tsx | 4 ++-- .../ChatView.chat-input-autosize.test.tsx | 14 +++++++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index f1a510f0b8..558488c7c5 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -1476,6 +1476,15 @@ overscroll-behavior: contain; } +/* FN-6212: On tablet viewports (769–1024px), cap the composer to 200px + so it doesn't consume most of the visible thread. Desktop keeps 640px; + mobile is already flex-constrained by the chat-thread layout. */ +@media (min-width: 769px) and (max-width: 1024px) { + .chat-input-textarea { + max-height: 200px; + } +} + .chat-input-textarea:focus { outline: none; border-color: var(--accent); diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index 04b646e992..137369e103 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -60,19 +60,23 @@ export interface ChatViewProps { // Keep a generous cap so pasted multi-paragraph text stays visible while // still preventing the composer from overtaking the message pane on short viewports. const CHAT_INPUT_MAX_HEIGHT_PX = 640; +const TABLET_INPUT_MAX_HEIGHT_PX = 200; /** Canonical definition lives in packages/dashboard/src/chat.ts (ROOM_SKIP_SENTINEL). */ const ROOM_SKIP_SENTINEL = "__SKIP__"; let chatViewWasPreviouslyInactive = false; -export function resolveChatInputOverflowY(scrollHeight: number): "auto" | "hidden" { - return scrollHeight > CHAT_INPUT_MAX_HEIGHT_PX ? "auto" : "hidden"; +export function resolveChatInputOverflowY( + scrollHeight: number, + maxHeight: number = CHAT_INPUT_MAX_HEIGHT_PX, +): "auto" | "hidden" { + return scrollHeight > maxHeight ? "auto" : "hidden"; } -export function clampChatInputHeight(scrollHeight: number): number { +export function clampChatInputHeight(scrollHeight: number, maxHeight: number = CHAT_INPUT_MAX_HEIGHT_PX): number { // Floor matches QuickChat (clampQuickChatInputHeight) and the CSS min-height, // so a 0-scrollHeight measurement (e.g. before layout) still yields a // sensible inline height instead of collapsing the composer to 0. - return Math.max(40, Math.min(scrollHeight, CHAT_INPUT_MAX_HEIGHT_PX)); + return Math.max(40, Math.min(scrollHeight, maxHeight)); } function formatRelativeTime(dateStr: string, t: TFunction<"app">): string { @@ -1863,10 +1867,12 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView return; } + const effectiveMax = mode === "tablet" ? TABLET_INPUT_MAX_HEIGHT_PX : CHAT_INPUT_MAX_HEIGHT_PX; + composer.style.height = "auto"; - composer.style.height = `${clampChatInputHeight(composer.scrollHeight)}px`; - composer.style.overflowY = resolveChatInputOverflowY(composer.scrollHeight); - }, []); + composer.style.height = `${clampChatInputHeight(composer.scrollHeight, effectiveMax)}px`; + composer.style.overflowY = resolveChatInputOverflowY(composer.scrollHeight, effectiveMax); + }, [mode]); const handleComposerRef = useCallback((textarea: HTMLTextAreaElement | null) => { inputRef.current = textarea; diff --git a/packages/dashboard/app/components/QuickChatFAB.tsx b/packages/dashboard/app/components/QuickChatFAB.tsx index 626c515c58..adc28925f2 100644 --- a/packages/dashboard/app/components/QuickChatFAB.tsx +++ b/packages/dashboard/app/components/QuickChatFAB.tsx @@ -109,10 +109,10 @@ function formatModelTagName(modelInfo: ModelInfo | null, parsedSelection: Parsed .trim(); } -export function clampQuickChatInputHeight(scrollHeight: number): number { +export function clampQuickChatInputHeight(scrollHeight: number, maxHeight: number = 640): number { // Match ChatView's 640px cap so pasted multi-paragraph text remains visible, // while keeping an upper bound that protects message visibility on short screens. - return Math.max(40, Math.min(scrollHeight, 640)); + return Math.max(40, Math.min(scrollHeight, maxHeight)); } function truncateToolValue(value: string, maxLength: number): string { diff --git a/packages/dashboard/app/components/__tests__/ChatView.chat-input-autosize.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.chat-input-autosize.test.tsx index 41270d9196..3445bec171 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.chat-input-autosize.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.chat-input-autosize.test.tsx @@ -33,9 +33,19 @@ describe("ChatView chat input autosize", () => { expect(stopRule?.[0]).toContain("min-height: var(--chat-input-control-size)"); }); + it("caps textarea max-height at 200px on tablet viewports", () => { + const tabletRule = chatViewCss.match( + /@media \(min-width: 769px\) and \(max-width: 1024px\)\s*\{\s*\.chat-input-textarea\s*\{[^}]*\}\s*\}/, + ); + + expect(tabletRule).not.toBeNull(); + expect(tabletRule?.[0]).toContain("max-height: 200px"); + }); + it("clamps oversized textarea growth to the new max height", () => { expect(clampChatInputHeight(600)).toBe(600); expect(clampChatInputHeight(800)).toBe(640); + expect(clampChatInputHeight(800, 200)).toBe(200); expect(clampChatInputHeight(600)).not.toBe(120); }); @@ -45,7 +55,11 @@ describe("ChatView chat input autosize", () => { it("keeps overflow hidden until content exceeds the max height cap", () => { expect(resolveChatInputOverflowY(80)).toBe("hidden"); + expect(resolveChatInputOverflowY(200)).toBe("hidden"); + expect(resolveChatInputOverflowY(201)).toBe("hidden"); expect(resolveChatInputOverflowY(640)).toBe("hidden"); expect(resolveChatInputOverflowY(641)).toBe("auto"); + expect(resolveChatInputOverflowY(200, 200)).toBe("hidden"); + expect(resolveChatInputOverflowY(201, 200)).toBe("auto"); }); });