Skip to content

CPLAT-12413: make shortcuts reachable under a Korean input source - #167

Merged
gavin-jeong merged 1 commit into
masterfrom
CPLAT-12413-cjk-reachable-shortcuts
Sep 28, 2026
Merged

gavin-jeong merged 1 commit into
masterfrom
CPLAT-12413-cjk-reachable-shortcuts

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-12413

Why

Ten default bindings were a bare uppercase letter, which a user typing under a 2-set Korean layout cannot produce. Shift only yields a distinct character on the keys carrying a doubled consonant or ㅒ/ㅖ (q w e r t o p); every other key sends the same jamo either way, and this bubbletea version's tea.Key has no Shift field, so the distinction is gone before it reaches us.

Where a lowercase action shared the letter, the lowercase case sits earlier in the switch and swallowed both presses — Files "f" beat Fork "F", Move "m" beat ImportMem "M", foldAll "f" beat expandAll "F". That is the reported symptom: folding appeared to only ever fold, never expand.

Follow-up to CPLAT-10983. The langmap maps a jamo back to its Latin key correctly, but only ever to lowercase, so uppercase bindings stayed dead even with langmap working.

Rebinds

Lowercase, or ctrl+ where no letter was free (modifiers survive the IME):

Binding Was Now
Session.Views V v
Actions.Fork F b (branch off)
Actions.ImportMem M a (add memory)
Actions.RemoveMem X z
Conversation.ExecutionContexts A a
Conversation.RegionUp / RegionDown K / J ctrl+p / ctrl+n
Conversation.LiveToggle L ctrl+l
Conversation.Input I w
Preview.ExpandAll F u (matches Session.ExpandAll)
config/plugins reverse search N b (3 call sites)

Region nav takes ctrl+p/ctrl+n rather than ctrl+k/ctrl+j because ctrl+j is LF and terminals may deliver it as Enter.

Four session-list keys were hardcoded in the handler, so no keymap override reached them and no struct-level check could see them. The daily-view toggle was one, spelled "D" — unreachable, with the help overlay advertising it anyway. Now Keymap fields: fold_group, state_menu, daily_view (D → d), page_menu.

migrateKeymapDefaults moves an existing config.yaml off the old defaults, rewriting only values that still match an old default so a deliberately chosen key survives.

Also fixed

Found by checking the Keymap struct against every place that consumes it, rather than by reading:

  • The KEYMAPS config page hid 15 bindings. It listed fields by hand and had drifted, omitting most of the actions menu (fork, edit, tags, copy, changes, new, remote) — reporting keys as nonexistent that the menu answered to. Now walks the struct by reflection.
  • mergeKeymap ignored Actions.Tags and Actions.Edit, so those overrides never loaded.
  • KeymapsConfig had no Preview section at all, so every preview binding the config page offered was silently dropped on load.
  • fillKeymapDefaults skipped Actions.Changes/Copy/Tags, writing them to config.yaml as blanks.
  • Preview.FoldAll/ExpandAll were hardcoded inside FoldState.HandleKey, which has no keymap access. Split into FoldAll/ExpandAll methods dispatched by the caller.

Test plan

  • go build ./... && go vet ./... && go test ./... green on latest master
  • Five guard tests, each verified by reintroducing the defect and confirming it fails:
    • reachability and per-menu uniqueness over the Keymap by reflection — a new field is covered without anyone extending a list
    • a source scan for hardcoded uppercase dispatch, the only thing that catches a literal like key == "D" that never reaches the struct
    • config-page and defaults-fill round-trip coverage
  • Drove the real Update loop with the jamo a Korean IME actually sends (ㄹ, ㅕ, ㅍ, …) and confirmed each rebound action fires — not just asserting on the keymap
  • Confirmed the generated config.yaml carries the new preview: section and every new field
  • Not covered: no manual run with a real Korean IME in a terminal; reachability is proven at the jamo→Update level

Notes for reviewers

  • Existing users keep working keys via the migration, but muscle memory changes for V, x→F/M/X, and conversation-view A/K/J/L/I. Overridable in keymap.yaml.
  • README's session and inspector tables were stale on g/G/L/S/v/J independently of this change; corrected here.
  • Actions.Jump looked like a dead field during triage but is live at app.go:3943 — left alone.

Security checklist

  • No SecurityGroup rule changes
  • No 0.0.0.0/0 inbound
  • No public subnet resources
  • No IAM user changes
  • No secrets in code, commits, or logs

Ten default bindings were a bare uppercase letter, which a user typing
under a 2-set Korean layout cannot produce: Shift only yields a distinct
character on the keys carrying a doubled consonant or ㅒ/ㅖ (q w e r t o
p), every other key sends the same jamo either way, and this bubbletea
version's tea.Key has no Shift field, so the distinction is already gone
by the time we see it.

Where a lowercase action shared the letter, the lowercase case sits
earlier in the switch and swallowed both presses — Files "f" beat Fork
"F", Move "m" beat ImportMem "M", and fold-all "f" beat expand-all "F".
That is the reported symptom: folding appeared to only ever fold.

This is a follow-up to CPLAT-10983. The langmap maps a jamo back to its
Latin key correctly, but it can only map to lowercase, so uppercase
bindings stayed dead even with langmap working.

Rebound to lowercase or ctrl+ keys (modifiers survive the IME):

  Session.Views          V -> v
  Actions.Fork           F -> b      (branch off)
  Actions.ImportMem      M -> a      (add memory)
  Actions.RemoveMem      X -> z
  Conversation.ExecutionContexts  A -> a
  Conversation.RegionUp/Down      K/J -> ctrl+p / ctrl+n
  Conversation.LiveToggle         L -> ctrl+l
  Conversation.Input              I -> w
  Preview.ExpandAll               F -> u  (matches Session.ExpandAll)
  config/plugins reverse search    N -> b  (3 call sites)

Region nav takes ctrl+p/ctrl+n rather than ctrl+k/ctrl+j because ctrl+j
is LF and terminals may deliver it as Enter.

Four session-list keys were hardcoded in the handler, so no keymap
override reached them and no struct-level check could see them. The
daily-view toggle was one of them, spelled "D" — unreachable, and the
help overlay advertised it anyway. They are now Keymap fields:
fold_group, state_menu, daily_view (D -> d), page_menu.

migrateKeymapDefaults moves an existing config.yaml off the old
defaults, rewriting only values that still match the old default so a
deliberately chosen key survives.

Also fixed along the way, each found by checking the Keymap struct
against every place that consumes it rather than by reading:

- The KEYMAPS config page listed fields by hand and had drifted,
  omitting 15 bindings — most of the actions menu (fork, edit, tags,
  copy, changes, new, remote). It claimed keys did not exist that the
  menu answered to. Now walks the struct by reflection.
- mergeKeymap ignored Actions.Tags and Actions.Edit, so those overrides
  never loaded.
- KeymapsConfig had no Preview section at all, so every preview binding
  the config page offered was silently dropped on load.
- fillKeymapDefaults skipped Actions.Changes/Copy/Tags, writing them to
  config.yaml as blanks.
- Preview.FoldAll/ExpandAll were hardcoded inside FoldState.HandleKey,
  which has no access to the keymap. Split into FoldAll/ExpandAll
  methods dispatched by the caller.

Five guard tests, each verified by reintroducing the defect and
confirming it fails:

- reachability and per-menu uniqueness over the Keymap by reflection,
  so a new field is covered without anyone extending a list
- a source scan for hardcoded uppercase dispatch, which is the only
  thing that can catch a literal like key == "D" that never reaches
  the struct
- config-page and defaults-fill round-trip coverage

Verified by driving the real Update loop with the jamo a Korean IME
sends, not just by asserting on the keymap. README's session and
inspector tables were stale on g/G/L/S/v/J independently of this change
and are corrected.
@upwind-code-us

upwind-code-us Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Upwind Upwind Code Scan - ✅ Passed

0 newly introduced vulnerabilities · 0 resolved · 1 total in this PR vs master

Total breakdown: 🔶 1 High

View full analysis in Upwind Console

Scan completed in 8s

Scan history (1 scan)
Commit Scanned at New Resolved Net
29af0b1 < 2026-09-28 07:34 UTC 0 0 0

Last scanned: 29af0b1 · 2026-09-28 07:34 UTC

@upwind-code-us

upwind-code-us Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Upwind Upwind IaC Scan - ✅ Passed

0 newly introduced misconfigurations · 0 resolved · 0 total in this PR vs master

View full analysis in Upwind Console →

Scan completed in 5s

Scan history (1 scan)
Commit Scanned at New Resolved Net
29af0b1 < 2026-09-28 07:34 UTC 0 0 0

Last scanned: 29af0b1 · 2026-09-28 07:34 UTC

@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Sep 28, 2026

@jinsekim jinsekim 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.

LGTM!

@gavin-jeong
gavin-jeong merged commit 5911cfa into master Sep 28, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants