feat(FN-4189): lift chat input height cap and add autosize behavior

Lifts the chat input height cap and enables auto-resize as the user types, improving the chat UX in `@fusion/dashboard`. Includes test coverage for the autosize behavior and a changeset for the `@runfusion/fusion` package.

Fusion-Task-Id: FN-4189
This commit is contained in:
Fusion
2026-05-12 14:36:42 -07:00
committed by gsxdsm
parent 0793422581
commit bbfd6a9740
4 changed files with 42 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Raise the dashboard chat composer autosize cap so longer drafts stay readable before scrolling.

View File

@@ -1302,8 +1302,11 @@
font-size: 14px; font-size: 14px;
font-family: inherit; font-family: inherit;
line-height: 1.4; line-height: 1.4;
max-height: 120px; max-height: 320px;
min-height: 40px; min-height: 40px;
/* Mobile keeps the desktop cap because the surrounding chat-input-area
and thread flex bounds already prevent the composer from overtaking
the message pane before textarea scrolling engages. */
/* touch-action: manipulation allows tap-to-focus (so iOS doesn't /* touch-action: manipulation allows tap-to-focus (so iOS doesn't
auto-dismiss the keyboard between gesture frames) and blocks auto-dismiss the keyboard between gesture frames) and blocks
double-tap zoom + pan/zoom gestures. We previously tried `none` double-tap zoom + pan/zoom gestures. We previously tried `none`

View File

@@ -49,6 +49,12 @@ export interface ChatViewProps {
experimentalFeatures?: Record<string, boolean>; experimentalFeatures?: Record<string, boolean>;
} }
const CHAT_INPUT_MAX_HEIGHT_PX = 320;
export function clampChatInputHeight(scrollHeight: number): number {
return Math.min(scrollHeight, CHAT_INPUT_MAX_HEIGHT_PX);
}
function formatRelativeTime(dateStr: string): string { function formatRelativeTime(dateStr: string): string {
const date = new Date(dateStr); const date = new Date(dateStr);
const now = new Date(); const now = new Date();
@@ -1526,7 +1532,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (!inputRef.current) return; if (!inputRef.current) return;
inputRef.current.style.height = "auto"; inputRef.current.style.height = "auto";
inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`; inputRef.current.style.height = `${clampChatInputHeight(inputRef.current.scrollHeight)}px`;
inputRef.current.focus(); inputRef.current.focus();
}); });
@@ -1565,7 +1571,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (!inputRef.current) return; if (!inputRef.current) return;
inputRef.current.style.height = "auto"; inputRef.current.style.height = "auto";
inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`; inputRef.current.style.height = `${clampChatInputHeight(inputRef.current.scrollHeight)}px`;
inputRef.current.focus(); inputRef.current.focus();
inputRef.current.setSelectionRange(nextCursorPos, nextCursorPos); inputRef.current.setSelectionRange(nextCursorPos, nextCursorPos);
}); });
@@ -1725,7 +1731,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
} }
textarea.style.height = "auto"; textarea.style.height = "auto";
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`; textarea.style.height = `${clampChatInputHeight(textarea.scrollHeight)}px`;
}, [updateMentionState]); }, [updateMentionState]);
const handleInputSelectionChange = useCallback( const handleInputSelectionChange = useCallback(

View File

@@ -0,0 +1,24 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { clampChatInputHeight } from "../ChatView";
const chatViewCss = readFileSync(resolve(__dirname, "../ChatView.css"), "utf8");
describe("ChatView chat input autosize", () => {
it("keeps the textarea CSS max-height aligned with the raised autosize cap", () => {
const textareaRule = chatViewCss.match(/\.chat-input-textarea\s*\{[^}]*\}/);
expect(textareaRule).not.toBeNull();
expect(textareaRule?.[0]).toContain("max-height: 320px");
});
it("clamps oversized textarea growth to the new max height", () => {
expect(clampChatInputHeight(600)).toBe(320);
expect(clampChatInputHeight(600)).not.toBe(120);
});
it("preserves smaller textarea heights below the cap", () => {
expect(clampChatInputHeight(80)).toBe(80);
});
});