fix(settings): prevent IME composition cancellation in repository Display Name and Worktree Location inputs#4632
Merged
Jinwoo-H merged 2 commits intoJun 4, 2026
Conversation
…play Name and Worktree Location inputs Controlled inputs bound directly to the zustand store caused IME composition to be cancelled on every keystroke because React re-rendered from the stale store value before the async IPC round-trip completed. On slow typing this yielded bare jamo decomposition (가나다 → ㄱㅏㄴㅏㄷㅏ); on fast typing characters were silently dropped. Introduce RepoSettingsDraftInput, a wrapper that keeps keystrokes in local draft state so the input is always synchronously controlled. The store is still written on every keystroke in the background so the sidebar stays live. Draft resets only when the active repo changes, so async IPC echoes cannot clobber newer draft text. Apply RepoSettingsDraftInput to both the Display Name and the Worktree Location inputs (identical store-bound binding, same root cause). Also link the Display Name Label to its input via htmlFor/id for a11y and test-locator clarity. Tests added: - RepositoryPaneDraftInput.test.tsx: unit tests (happy-dom) — draft survives stale store re-render and stale IPC echo; resets on repo switch; persists on every keystroke. - tests/e2e/settings-display-name-ime.spec.ts: Playwright e2e driving Blink's real IME pipeline via CDP Input.imeSetComposition / Input.insertText with an adaptive 2-set Korean IME emulator. Fails with 'ㄱㅏㄴㅏㄷㅏ' on the pre-fix code; passes on the fix.
Co-authored-by: Orca <[email protected]>
Jinwoo-H
approved these changes
Jun 4, 2026
Jinwoo-H
left a comment
Contributor
There was a problem hiding this comment.
Thanks. I pushed one maintainer follow-up so the draft input still accepts same-repo store updates that did not originate from the input, such as clearing Worktree Location via "Use Global," while continuing to ignore stale async echoes from prior keystrokes.
Verified with typecheck, lint, focused renderer tests, and the Electron IME e2e for Hangul composition. The e2e confirms both the visible Display Name input and persisted store value are 가나다.
thomaszdxsn
pushed a commit
to thomaszdxsn/orca
that referenced
this pull request
Jun 15, 2026
…play Name and Worktree Location inputs (stablyai#4632) * fix(settings): prevent IME composition cancellation in repository Display Name and Worktree Location inputs Controlled inputs bound directly to the zustand store caused IME composition to be cancelled on every keystroke because React re-rendered from the stale store value before the async IPC round-trip completed. On slow typing this yielded bare jamo decomposition (가나다 → ㄱㅏㄴㅏㄷㅏ); on fast typing characters were silently dropped. Introduce RepoSettingsDraftInput, a wrapper that keeps keystrokes in local draft state so the input is always synchronously controlled. The store is still written on every keystroke in the background so the sidebar stays live. Draft resets only when the active repo changes, so async IPC echoes cannot clobber newer draft text. Apply RepoSettingsDraftInput to both the Display Name and the Worktree Location inputs (identical store-bound binding, same root cause). Also link the Display Name Label to its input via htmlFor/id for a11y and test-locator clarity. Tests added: - RepositoryPaneDraftInput.test.tsx: unit tests (happy-dom) — draft survives stale store re-render and stale IPC echo; resets on repo switch; persists on every keystroke. - tests/e2e/settings-display-name-ime.spec.ts: Playwright e2e driving Blink's real IME pipeline via CDP Input.imeSetComposition / Input.insertText with an adaptive 2-set Korean IME emulator. Fails with 'ㄱㅏㄴㅏㄷㅏ' on the pre-fix code; passes on the fix. * fix(settings): preserve external repo setting updates Co-authored-by: Orca <[email protected]> --------- Co-authored-by: Jinwoo-H <[email protected]> Co-authored-by: Orca <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In Settings → Repository pane, typing Korean (Hangul) into the Display Name input caused syllables to decompose into bare jamo (가나다 → ㄱㅏㄴㅏㄷㅏ). The Worktree Location input had the same bug with the same root cause but manifested as dropped characters at fast typing speed.
Root cause: Both inputs were controlled inputs bound directly to the zustand store (
value={repo.displayName}).updateRepoonly writes to the store after an awaited IPC round-trip (repos:update). On every keystroke React synchronously restored the input to the stale store value, which silently killed the active IME composition (nocompositionendfires). The async IPC echo then re-inserted the cancelled jamo as committed text.Fix: Introduce a
RepoSettingsDraftInputwrapper component that keeps keystrokes in local draft state so the input is always synchronously controlled. The store is still written on every keystroke in the background so the sidebar stays live. The draft resets only when the pane switches to a different repo, so async IPC echoes can never clobber newer draft text.Applied to both the Display Name and Worktree Location inputs. Also linked the Display Name
Labelto its input viahtmlFor/id(a11y + test locator).Screenshots
No visual change — the fix is entirely behavioral (correct IME composition in Korean and other CJK input methods).
Testing
pnpm lintpnpm typecheckpnpm testpnpm buildUnit tests (
RepositoryPaneDraftInput.test.tsx, happy-dom, 7 cases):onUpdatefires on every keystroke (per-keystroke persistence)Playwright e2e (
tests/e2e/settings-display-name-ime.spec.ts):Input.imeSetComposition/Input.insertTextwith an adaptive 2-set Korean IME emulatorReceived: "ㄱㅏㄴㅏㄷㅏ"on the pre-fix code; passes with 가나다 on the fixAI Review Report
Reviewed the fix for correctness across the IME composition lifecycle. Key risks checked:
repo.id) rather than a deep-equality check, which is fast and correct — repo identity is stable within a single pane view.onUpdateis called on every draft keystroke. Confirmed the IPC handler is idempotent and debounce is not needed given the existing async behavior.RepoSettingsDraftInputis scoped to the two affected fields; no other inputs inRepositoryPanewere changed.No flags raised that required code changes beyond the initial implementation.
Security Audit
onUpdatepasses draft string values through the existingrepos:updateIPC handler — no change to input sanitization surface.Notes
--remote-debugging-port). The test file documents the required launch flags and will be skipped gracefully if CDP is unavailable.test-results/artifacts from local Playwright runs are intentionally not committed.