From db206b15ae3df2f17584d0590b4b1df552fcc116 Mon Sep 17 00:00:00 2001 From: Jiahao Zhu Date: Sat, 24 Jan 2026 00:42:56 +0800 Subject: [PATCH 1/4] fix: improve chat input UX and IME composition handling (revised) (#3) Chat Input UX Improvements: - Make entire input bubble clickable to focus the textarea - Add cursor-text style to indicate the bubble is an input area - Disable pointer events on send button when hidden (no text entered) - Only show tooltip on send button when it's visible - Remove focus outline from textarea (outer bubble shows focus state) IME Composition Handling: - Track composition state via compositionstart/compositionend events - Use skipNextEnter flag to ignore the Enter key that confirms IME selection - Fixes issue where pressing Enter during Chinese/Japanese/Korean input would send the message instead of confirming character selection --- .../src/app/components/session/composer.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/app/src/app/components/session/composer.tsx b/packages/app/src/app/components/session/composer.tsx index a357d9eb..189f1aeb 100644 --- a/packages/app/src/app/components/session/composer.tsx +++ b/packages/app/src/app/components/session/composer.tsx @@ -24,6 +24,11 @@ export default function Composer(props: ComposerProps) { let textareaRef: HTMLTextAreaElement | undefined; const [commandIndex, setCommandIndex] = createSignal(0); + // 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.startsWith("/") && !props.busy; }); @@ -48,8 +53,17 @@ 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 allows newline if (event.key === "Enter" && event.shiftKey) return; - if (event.isComposing && event.key !== "Enter") return; if (commandMenuOpen()) { const matches = props.commandMatches; @@ -187,6 +201,20 @@ export default function Composer(props: ComposerProps) { value={props.prompt} onInput={(e) => props.setPrompt(e.currentTarget.value)} onKeyDown={handleKeyDown} + onCompositionStart={() => { + setIsComposingIME(true); + // The Enter that confirms IME composition should be skipped + skipNextEnter = true; + }} + onCompositionEnd={() => { + setIsComposingIME(false); + // Clear the flag after current task completes and a small delay to allow the keydown event to fire + // If Enter confirmed IME, its keydown fires before this runs → skipped + // If mouse/touch/spacebar confirmed IME, this clears the flag → next Enter works + setTimeout(() => { + skipNextEnter = false; + }, 100); + }} placeholder="Ask OpenWork..." class="flex-1 bg-transparent border-none p-0 text-gray-12 placeholder-gray-6 focus:ring-0 text-[15px] leading-relaxed resize-none min-h-[24px]" /> From 8131445847d417279e43ea52b8e0a1c7591a68c8 Mon Sep 17 00:00:00 2001 From: Jiahao Zhu Date: Sat, 24 Jan 2026 00:54:02 +0800 Subject: [PATCH 2/4] Bump openwork crate to version 0.3.5 Updated the openwork dependency from version 0.3.4 to 0.3.5 in Cargo.lock. --- packages/desktop/src-tauri/Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index c1f765ee..ee9a3fec 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -2381,7 +2381,7 @@ dependencies = [ [[package]] name = "openwork" -version = "0.3.4" +version = "0.3.5" dependencies = [ "json5", "serde", From 3da35c273eb64282c8e2314417808c613d6b7448 Mon Sep 17 00:00:00 2001 From: Jiahao Zhu Date: Sat, 24 Jan 2026 02:48:57 +0800 Subject: [PATCH 3/4] fix: improve chat input UX with refined styling (#8) - Add click-to-focus on input container with cursor-text visual hint - Add outline-none to textarea for cleaner focus state - Add disabled:pointer-events-none to run button - Make run button title conditional on prompt content --- packages/app/src/app/components/session/composer.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/app/src/app/components/session/composer.tsx b/packages/app/src/app/components/session/composer.tsx index 189f1aeb..09743a9c 100644 --- a/packages/app/src/app/components/session/composer.tsx +++ b/packages/app/src/app/components/session/composer.tsx @@ -119,11 +119,12 @@ export default function Composer(props: ComposerProps) {
textareaRef?.focus()} >
@@ -216,14 +217,14 @@ export default function Composer(props: ComposerProps) { }, 100); }} placeholder="Ask OpenWork..." - class="flex-1 bg-transparent border-none p-0 text-gray-12 placeholder-gray-6 focus:ring-0 text-[15px] leading-relaxed resize-none min-h-[24px]" + class="flex-1 bg-transparent border-none outline-none p-0 text-gray-12 placeholder-gray-6 focus:ring-0 text-[15px] leading-relaxed resize-none min-h-[24px]" /> From 5b849a535b74e43bacce8878352c73ee18c2ccae Mon Sep 17 00:00:00 2001 From: Jiahao Zhu Date: Mon, 26 Jan 2026 13:27:06 +0800 Subject: [PATCH 4/4] Improve editor height sync for empty content Refactored the syncHeight function to allow CSS to handle sizing when the editor is empty, ensuring better handling of min-height and padding. This prevents unnecessary height adjustments and improves overflow behavior. --- .../app/src/app/components/session/composer.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/app/src/app/components/session/composer.tsx b/packages/app/src/app/components/session/composer.tsx index 41c22d24..55560123 100644 --- a/packages/app/src/app/components/session/composer.tsx +++ b/packages/app/src/app/components/session/composer.tsx @@ -347,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 = () => {