Skip to content

Fix keyboard shortcuts not working with Korean input mode - #1913

Merged
lawrencecchen merged 1 commit into
manaflow-ai:mainfrom
centraldogma99:fix/korean-input-keyboard-shortcuts
Mar 23, 2026
Merged

lawrencecchen merged 1 commit into
manaflow-ai:mainfrom
centraldogma99:fix/korean-input-keyboard-shortcuts

Conversation

@centraldogma99

@centraldogma99 centraldogma99 commented Mar 21, 2026 •

Copy link
Copy Markdown

Summary

  • Keyboard shortcuts (Cmd+T, Cmd+D, Cmd+1-9, Ctrl+N/P, etc.) fail when a non-Latin input source like Korean 두벌식 is active
  • Root cause: KeyboardLayout.character(forKeyCode:modifierFlags:) assumed CJK input sources lack kTISPropertyUnicodeKeyLayoutData, but Korean 두벌식 has it — UCKeyTranslate returned Hangul characters and the ASCII fallback was never reached
  • Additionally, handleCustomShortcut compared raw event.charactersIgnoringModifiers (which returns Korean chars like ㅅ for T) against Latin shortcut keys
  • Ghostty handles this correctly by matching on event.keyCode (hardware scan codes) which are input-method-invariant

Changes

  • KeyboardLayout.swift: character(forKeyCode:modifierFlags:) now checks if the result is ASCII before accepting; non-ASCII results fall through to TISCopyCurrentASCIICapableKeyboardInputSource(). Added normalizedCharacters(for:) helper for reusable event character normalization.
  • AppDelegate.swift: handleCustomShortcut uses KeyboardLayout.normalizedCharacters(for:) instead of raw event.charactersIgnoringModifiers — fixes Cmd+1-9 workspace switching, Ctrl+1-9 surface selection, omnibar Cmd/Ctrl+N/P, command palette shortcuts, and all matchShortcut-based shortcuts.
  • BrowserPanelView.swift: Omnibar key handler uses normalized characters for Cmd/Ctrl+N/P navigation.
  • BrowserPopupWindowController.swift: Popup Cmd+W close handler uses normalized characters.

Test plan

  • Switch to Korean 두벌식 input mode
  • Verify Cmd+T (new tab), Cmd+D (split right), Cmd+W (close tab) work
  • Verify Cmd+1-9 workspace switching works
  • Verify Ctrl+N/P navigation works in command palette and browser omnibar
  • Verify Cmd+W closes browser popup windows
  • Switch back to English and verify all shortcuts still work
  • Test with Japanese and Chinese input methods if available
  • Verify Dvorak/other non-QWERTY Latin layouts are not affected

🤖 Generated with Claude Code


Summary by cubic

Fixes character-based shortcuts failing when Korean 두벌식 or other non‑Latin input sources are active. Shortcuts now normalize to ASCII so Cmd+T, Cmd+D, Cmd+1–9, Ctrl+N/P, and Cmd+W work regardless of input method.

  • Bug Fixes
    • In KeyboardLayout: accept only ASCII results; fall back to the ASCII-capable input source; added normalizedCharacters(for:).
    • Switched shortcut handlers to use normalizedCharacters in AppDelegate, omnibar, and popup close.
    • Shortcuts now work across Korean/JP/CN IMEs and still respect layouts like “Dvorak – QWERTY Command.”

Written for commit 8cd9cd9. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved keyboard shortcut recognition when using non-Latin keyboard layouts (e.g., CJK, Korean)
    • Enhanced character normalization in keyboard input handling for consistent behavior across different keyboard sources
    • Fixed navigation shortcuts and special key handling to work reliably with international input methods

When a non-Latin input source like Korean 두벌식 is active,
event.charactersIgnoringModifiers returns Hangul characters (e.g. ㅅ
for T key) instead of Latin letters. This caused all character-based
shortcut matching to fail — Cmd+T, Cmd+D, Cmd+1-9, Ctrl+N/P, etc.

Root cause: KeyboardLayout.character(forKeyCode:modifierFlags:) assumed
CJK input sources lack kTISPropertyUnicodeKeyLayoutData, but Korean
두벌식 has it. UCKeyTranslate returned Korean characters and the ASCII
fallback was never reached.

Fix:
- KeyboardLayout.character(): check result is ASCII before accepting;
  fall through to TISCopyCurrentASCIICapableKeyboardInputSource() when
  the current source returns non-ASCII characters
- Add KeyboardLayout.normalizedCharacters(for:) helper that normalizes
  event.charactersIgnoringModifiers for shortcut comparison
- Apply normalization in handleCustomShortcut (AppDelegate),
  BrowserPanelView omnibar key handler, and BrowserPopupWindowController
  Cmd+W handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 21, 2026

Copy link
Copy Markdown

@JunyeongChoi0 is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 21, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63ffb4b1-d7bb-46cb-b545-53f82d77d74d

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff8157 and 8cd9cd9.

📒 Files selected for processing (4)
  • Sources/AppDelegate.swift
  • Sources/KeyboardLayout.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/BrowserPopupWindowController.swift

📝 Walkthrough

Walkthrough

This change introduces keyboard input normalization improvements by adding a new normalizedCharacters(for:) API to KeyboardLayout and updating shortcut handlers to use it. The API normalizes non-Latin keyboard input by attempting ASCII conversion via layout translation. All three shortcut handlers now derive input characters through this normalized path instead of raw character operations.

Changes

Cohort / File(s) Summary
Keyboard layout normalization core logic
Sources/KeyboardLayout.swift
Added public normalizedCharacters(for:) method that lowercases character input and attempts ASCII-layout conversion for non-ASCII strings. Modified character(forKeyCode:modifierFlags:) to require ASCII-only results from primary input source; otherwise falls back to ASCII-capable input source, addressing CJK cases where Unicode layouts produce non-ASCII output.
Shortcut handler updates
Sources/AppDelegate.swift, Sources/Panels/BrowserPanelView.swift, Sources/Panels/BrowserPopupWindowController.swift
Replaced raw charactersIgnoringModifiers.lowercased() operations with KeyboardLayout.normalizedCharacters(for:) calls in custom shortcut, navigation, and close-window handlers to ensure layout-aware character derivation under active non-Latin input sources.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Keys now dance in layouts true,
ASCII fallbacks guide the way through,
From Hangul halls to Nordic keys,
Each shortcut flows with gentle ease. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the problem, root cause, changes made, and provides a test plan, but the 'Testing' section with manual verification details and 'Demo Video' are missing or incomplete as required by the template. Clarify the manual testing approach (which tests were performed and results) and add a demo video or confirm none is needed for this backend/logic fix.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: enabling keyboard shortcuts to work with Korean input mode, which is the primary problem and solution addressed across all changed files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/KeyboardLayout.swift">

<violation number="1" location="Sources/KeyboardLayout.swift:47">
P2: Non-ASCII fallback in shortcut normalization drops modifier flags, breaking command-aware keyboard layout translation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

static func normalizedCharacters(for event: NSEvent) -> String {
let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
if raw.allSatisfy(\.isASCII) { return raw }
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {

@cubic-dev-ai cubic-dev-ai Bot Mar 21, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Non-ASCII fallback in shortcut normalization drops modifier flags, breaking command-aware keyboard layout translation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/KeyboardLayout.swift, line 47:

<comment>Non-ASCII fallback in shortcut normalization drops modifier flags, breaking command-aware keyboard layout translation.</comment>

<file context>
@@ -15,25 +15,41 @@ class KeyboardLayout {
+    static func normalizedCharacters(for event: NSEvent) -> String {
+        let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
+        if raw.allSatisfy(\.isASCII) { return raw }
+        if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
+            return layoutChar
+        }
</file context>
Suggested change
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: event.modifierFlags) {
Fix with Cubic

@greptile-apps

greptile-apps Bot commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes keyboard shortcuts (Cmd+T, Cmd+W, Ctrl+N/P, Cmd+1-9, etc.) failing when a Korean 두벌식 (or other CJK) input source is active. The root cause was two-fold: KeyboardLayout.character(forKeyCode:) would return a Hangul character from UCKeyTranslate without checking whether it was ASCII, and shortcut-matching code compared raw event.charactersIgnoringModifiers (which yields Korean glyphs like ㅅ for T) against Latin key names.

Key changes:

  • KeyboardLayout.swift: character(forKeyCode:) now validates result.allSatisfy(\.isASCII) before returning; non-ASCII results fall through to TISCopyCurrentASCIICapableKeyboardInputSource(). A new normalizedCharacters(for:) helper encapsulates this logic for event-level normalization.
  • AppDelegate.swift: handleCustomShortcut uses normalizedCharacters(for:) for chars, fixing Cmd+1-9 / Ctrl+1-9 / command-palette N/P navigation. matchShortcut is indirectly fixed via the updated character(forKeyCode:) that shortcutLayoutCharacterProvider calls.
  • BrowserPanelView.swift and BrowserPopupWindowController.swift: Omnibar N/P navigation and popup Cmd+W both use normalizedCharacters(for:).

One minor observation: BrowserPopupWindowController previously compared event.charactersIgnoringModifiers == "w" (no lowercasing), so Caps Lock would have silently broken Cmd+W in popup windows. The new normalizedCharacters(for:) always returns lowercase, incidentally fixing this edge case.

Confidence Score: 5/5

  • Safe to merge — fix is narrowly scoped to keyboard event normalization with no changes to shortcut dispatch logic or data paths.
  • The fix correctly identifies the root cause (UCKeyTranslate returning non-ASCII for Korean 두벌식), applies a minimal ASCII guard in the right place, and adds a well-named helper used consistently across all affected call sites. Latin and Dvorak layouts are unaffected because their characters are already ASCII and take the fast-return path. The only note is a minor documentation gap around the vacuous allSatisfy on empty strings, which is a style suggestion rather than a correctness concern.
  • No files require special attention.

Important Files Changed

Filename Overview
Sources/KeyboardLayout.swift Core fix: adds result.allSatisfy(\.isASCII) guard to character(forKeyCode:) so Korean 두벌식 (which has layout data but produces Hangul via UCKeyTranslate) falls back to the ASCII-capable source. Adds normalizedCharacters(for:) helper that short-circuits for ASCII events (fast path) and applies the layout fallback only for non-ASCII raw characters.
Sources/AppDelegate.swift Replaces raw (event.charactersIgnoringModifiers ?? "").lowercased() with KeyboardLayout.normalizedCharacters(for:) in handleCustomShortcut, fixing Cmd+1-9 workspace switching, Ctrl+1-9 surface selection, and command palette Ctrl+N/P navigation when a CJK input source is active. matchShortcut itself is not changed but benefits indirectly from the updated character(forKeyCode:) ASCII check via shortcutLayoutCharacterProvider.
Sources/Panels/BrowserPanelView.swift Omnibar key handler now uses KeyboardLayout.normalizedCharacters(for:) instead of event.charactersIgnoringModifiers?.lowercased(), fixing Cmd/Ctrl+N/P suggestion navigation when a CJK input method is active.
Sources/Panels/BrowserPopupWindowController.swift Popup Cmd+W now uses KeyboardLayout.normalizedCharacters(for:). As a beneficial side-effect, the new helper always lowercases the result, meaning the pre-existing comparison was case-sensitive (could miss Caps Lock), while the new code handles both cases correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NSEvent key press] --> B[normalizedCharacters for event]
    B --> C{charactersIgnoringModifiers\nis ASCII or empty?}
    C -- Yes --> D[Return lowercased raw string\nfast path - no TIS calls]
    C -- No\nCJK char e.g. ㅅ --> E[character forKeyCode modifierFlags empty]
    E --> F[TISCopyCurrentKeyboardInputSource\ne.g. Korean 두벌식]
    F --> G[characterFromInputSource\nUCKeyTranslate]
    G --> H{result.allSatisfy\nisASCII?}
    H -- Yes e.g. Dvorak --> I[Return ASCII char]
    H -- No e.g. Hangul --> J[TISCopyCurrentASCIICapableKeyboardInputSource\ne.g. US QWERTY]
    J --> K[characterFromInputSource\nUCKeyTranslate on ASCII layout]
    K --> L[Return t for T keyCode]
    L --> M[Shortcut match succeeds\nCmd+T, Ctrl+N, Cmd+W, etc.]
    D --> M
    I --> M
Loading

Last reviewed commit: "Fix keyboard shortcu..."

Comment on lines +44 to +51
static func normalizedCharacters(for event: NSEvent) -> String {
let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
if raw.allSatisfy(\.isASCII) { return raw }
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
return layoutChar
}
return raw
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Vacuous allSatisfy on nil/empty input

"".allSatisfy(\.isASCII) is vacuously true, so when event.charactersIgnoringModifiers is nil (some synthetic events, special keys), normalizedCharacters returns "" and never reaches the character(forKeyCode:) fallback. This is intentional — all callers already have independent keyCode-based fallbacks for this case — but the asymmetry (non-ASCII triggers the lookup, empty string does not) is a subtle trap for future callers who may not expect "" when a valid keyCode is present.

A clarifying comment or a guard would improve long-term readability:

Suggested change
static func normalizedCharacters(for event: NSEvent) -> String {
let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
if raw.allSatisfy(\.isASCII) { return raw }
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
return layoutChar
}
return raw
}
static func normalizedCharacters(for event: NSEvent) -> String {
let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
// Fast path: ASCII (or empty/nil) events need no normalization.
// Note: nil charactersIgnoringModifiers yields "" here intentionally;
// callers that need a keyCode-based lookup for nil events should call
// character(forKeyCode:modifierFlags:) directly.
if raw.allSatisfy(\.isASCII) { return raw }
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
return layoutChar
}
return raw
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@lawrencecchen
lawrencecchen merged commit 4c92271 into manaflow-ai:main Mar 23, 2026
14 of 15 checks passed
@lawrencecchen

Copy link
Copy Markdown
Contributor

Thank you for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants