diff --git a/packages/app/src/app/components/session/composer.tsx b/packages/app/src/app/components/session/composer.tsx index 8f1352b7..55560123 100644 --- a/packages/app/src/app/components/session/composer.tsx +++ b/packages/app/src/app/components/session/composer.tsx @@ -275,6 +275,11 @@ export default function Composer(props: ComposerProps) { const [variantMenuOpen, setVariantMenuOpen] = createSignal(false); const activeVariant = createMemo(() => props.modelVariant ?? "none"); + // Track IME composition state via events (more reliable than event.isComposing) + const [isComposingIME, setIsComposingIME] = createSignal(false); + // Flag to skip the Enter key that confirms IME composition + let skipNextEnter = false; + const commandMenuOpen = createMemo(() => { return props.prompt.trim().startsWith("/") && !props.busy && mode() === "prompt" && !mentionOpen(); }); @@ -342,14 +347,21 @@ export default function Composer(props: ComposerProps) { mentionSections().flatMap((section: MentionSection) => section.options) ); + // Sync editor height based on content. When empty, let CSS handle sizing. const syncHeight = () => { if (!editorRef) return; + const isEmpty = !editorRef.textContent?.trim(); + if (isEmpty) { + // Let CSS min-height and padding handle empty state + editorRef.style.height = ""; + editorRef.style.overflowY = "hidden"; + return; + } editorRef.style.height = "auto"; - const baseHeight = 24; - const scrollHeight = editorRef.scrollHeight || baseHeight; - const nextHeight = Math.min(Math.max(scrollHeight, baseHeight), 160); + const scrollHeight = editorRef.scrollHeight; + const nextHeight = Math.min(Math.max(scrollHeight, 24), 160); editorRef.style.height = `${nextHeight}px`; - editorRef.style.overflowY = editorRef.scrollHeight > 160 ? "auto" : "hidden"; + editorRef.style.overflowY = scrollHeight > 160 ? "auto" : "hidden"; }; const emitDraftChange = () => { @@ -590,13 +602,22 @@ export default function Composer(props: ComposerProps) { }; const handleKeyDown = (event: KeyboardEvent) => { + // During IME composition, let the browser handle all keys + if (isComposingIME() || event.isComposing) return; + + // Skip the Enter key that was used to confirm IME composition + // (flag is cleared by onCompositionEnd's setTimeout) + if (event.key === "Enter" && skipNextEnter) { + return; + } + + // Shift+Enter inserts a newline if (event.key === "Enter" && event.shiftKey) { event.preventDefault(); document.execCommand("insertLineBreak"); emitDraftChange(); return; } - if (event.isComposing && event.key !== "Enter") return; if (mentionOpen()) { const options = mentionOptions(); @@ -789,11 +810,17 @@ export default function Composer(props: ComposerProps) {