-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
studio/chat: release stuck IME flag when compositionend never fires #5551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
ec2d407
4d85dc3
357402c
2c3c979
a2fd555
597af0d
87365b5
8d9b73d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -353,16 +353,58 @@ function isNativeComposing(event: Event) { | |
| return "isComposing" in event && (event as InputEvent).isComposing === true; | ||
| } | ||
|
|
||
| // Fallback timeout for stuck IME composition. When Chrome on Windows talks | ||
| // to a WSL-hosted Studio (issue #5546), `compositionend` never fires after | ||
| // the candidate is committed, so `composingRef` stays true and Send stays | ||
| // disabled. Every compositionupdate / non-composing input resets the timer; | ||
| // only a true gap-after-commit lets it fire. 2500ms is well above a normal | ||
| // candidate-window pause but short enough to recover before the user | ||
| // notices the Send button is stuck. | ||
| const IME_STUCK_TIMEOUT_MS = 2500; | ||
|
|
||
| function useImeComposerInputHandlers() { | ||
| const aui = useAui(); | ||
| const composingRef = useRef(false); | ||
| const [isComposing, setIsComposing] = useState(false); | ||
| const stuckTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
|
|
||
| const setCompositionState = useCallback((next: boolean) => { | ||
| composingRef.current = next; | ||
| setIsComposing(next); | ||
| const clearStuckTimer = useCallback(() => { | ||
| if (stuckTimerRef.current) { | ||
| clearTimeout(stuckTimerRef.current); | ||
| stuckTimerRef.current = null; | ||
| } | ||
| }, []); | ||
|
|
||
| const setCompositionState = useCallback( | ||
| (next: boolean) => { | ||
| composingRef.current = next; | ||
| setIsComposing(next); | ||
| clearStuckTimer(); | ||
| if (next) { | ||
| stuckTimerRef.current = setTimeout(() => { | ||
| stuckTimerRef.current = null; | ||
| composingRef.current = false; | ||
| setIsComposing(false); | ||
| }, IME_STUCK_TIMEOUT_MS); | ||
|
Comment on lines
+387
to
+389
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The watchdog callback clears Useful? React with 👍 / 👎. |
||
| } | ||
| }, | ||
| [clearStuckTimer], | ||
| ); | ||
|
|
||
| const refreshStuckTimer = useCallback(() => { | ||
| if (!composingRef.current) { | ||
| return; | ||
| } | ||
| clearStuckTimer(); | ||
| stuckTimerRef.current = setTimeout(() => { | ||
| stuckTimerRef.current = null; | ||
| composingRef.current = false; | ||
| setIsComposing(false); | ||
| }, IME_STUCK_TIMEOUT_MS); | ||
| }, [clearStuckTimer]); | ||
|
|
||
| useEffect(() => clearStuckTimer, [clearStuckTimer]); | ||
|
|
||
| const setComposerText = useCallback( | ||
| (value: string) => { | ||
| const composer = aui.composer(); | ||
|
|
@@ -380,6 +422,10 @@ function useImeComposerInputHandlers() { | |
| setCompositionState(true); | ||
| }, [setCompositionState]); | ||
|
|
||
| const onCompositionUpdate = useCallback(() => { | ||
| refreshStuckTimer(); | ||
| }, [refreshStuckTimer]); | ||
|
|
||
| const onCompositionEnd = useCallback( | ||
| (e: CompositionEvent<HTMLTextAreaElement>) => { | ||
| setCompositionState(false); | ||
|
|
@@ -399,6 +445,7 @@ function useImeComposerInputHandlers() { | |
| return { | ||
| inputProps: { | ||
| onCompositionStart, | ||
| onCompositionUpdate, | ||
| onCompositionEnd, | ||
| onChange, | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In an active IME session where the user pauses for more than 2.5s while choosing a candidate, this timer flips
composingRef/isComposingto false even though nocompositionendor commit signal has arrived. Because the Send/Edit guards rely on those flags, the UI can re-enable submission during a still-active composition and allow the current preedit/candidate text to be sent or saved; the watchdog should only be armed after a commit-like input or otherwise verify composition has actually ended. The same silence-based timeout is mirrored inSharedComposer.Useful? React with 👍 / 👎.