From 26276b8f1726fb5916a2f20ef74355ae0e3ccc18 Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 10 Mar 2026 08:49:03 -0700 Subject: [PATCH 1/4] Improve chat scrolling and multiline composer --- desktop/src/app/AppShell.tsx | 1 + .../features/messages/ui/MessageComposer.tsx | 94 ++++-- .../features/messages/ui/MessageTimeline.tsx | 295 ++++++++++++++++-- desktop/src/shared/ui/textarea.tsx | 22 ++ desktop/tests/e2e/smoke.spec.ts | 115 +++++++ desktop/tests/e2e/stream.spec.ts | 210 ++++++++++++- 6 files changed, 688 insertions(+), 49 deletions(-) create mode 100644 desktop/src/shared/ui/textarea.tsx diff --git a/desktop/src/app/AppShell.tsx b/desktop/src/app/AppShell.tsx index 35667a6..83e9bcf 100644 --- a/desktop/src/app/AppShell.tsx +++ b/desktop/src/app/AppShell.tsx @@ -162,6 +162,7 @@ export function AppShell() { : "No channel selected" } isLoading={messagesQuery.isLoading} + key={activeChannel?.id ?? "no-channel"} messages={timelineMessages} /> (null); + const shortcutHint = React.useMemo(() => { + const platform = typeof navigator === "undefined" ? "" : navigator.platform; + return /(Mac|iPhone|iPad|iPod)/i.test(platform) ? "⌘↵" : "Ctrl↵"; + }, []); + const shortcutTitle = React.useMemo( + () => (shortcutHint === "⌘↵" ? "Cmd+Enter" : "Ctrl+Enter"), + [shortcutHint], + ); + + const submitMessage = React.useCallback(async () => { + const trimmed = content.trim(); + if (!trimmed || disabled || isSending) { + return; + } + + setContent(""); + + try { + await onSend(trimmed); + } catch { + setContent(trimmed); + } + }, [content, disabled, isSending, onSend]); const handleSubmit = React.useCallback( - async (event: React.FormEvent) => { + (event: React.FormEvent) => { event.preventDefault(); + void submitMessage(); + }, + [submitMessage], + ); - const trimmed = content.trim(); - if (!trimmed || disabled || isSending) { + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key !== "Enter" || (!event.metaKey && !event.ctrlKey)) { return; } - setContent(""); - - try { - await onSend(trimmed); - } catch { - setContent(trimmed); - } + event.preventDefault(); + void submitMessage(); }, - [content, disabled, isSending, onSend], + [submitMessage], ); + React.useLayoutEffect(() => { + const textarea = textareaRef.current; + if (!textarea) { + return; + } + + const lineHeight = + Number.parseFloat(window.getComputedStyle(textarea).lineHeight) || 24; + const maxHeight = lineHeight * MAX_TEXTAREA_ROWS; + + textarea.style.height = "auto"; + const nextHeight = Math.max( + lineHeight, + Math.min(textarea.scrollHeight, maxHeight), + ); + textarea.style.height = `${nextHeight}px`; + textarea.style.overflowY = + textarea.scrollHeight > maxHeight ? "auto" : "hidden"; + }); + return (