Skip to content

fix(tao): stop key roll-over from being read as a PressAndHold accent pick - #611

Closed
takke wants to merge 1 commit into
NucleusFramework:mainfrom
takke:fix/pressandhold-rollover-typing
Closed

fix(tao): stop key roll-over from being read as a PressAndHold accent pick#611
takke wants to merge 1 commit into
NucleusFramework:mainfrom
takke:fix/pressandhold-rollover-typing

Conversation

@takke

@takke takke commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Typing quickly on macOS silently drops characters. In our app (ZonePane, Compose text fields) typing Xcode at normal speed reliably lands as Xcde — one character short, and the previous character is the one that disappears.

This is not IME-specific: it hits plain ASCII typing, so every macOS Nucleus user is affected.

What happens

g_letter_key_down is only cleared on the base key's keyUp (nucleus_key_up). Fast typists roll over — the previous key is still physically down when the next one goes down — so during ordinary typing that flag is YES for long stretches, with a different key already being processed.

Any selectedRange / attributedSubstringForProposedRange query landing in that window runs nucleus_note_press_and_hold_query(), which checks only g_did_insert_base && g_letter_key_down and arms g_press_and_hold_queried. The next insertText: then matches g_press_and_hold_queried && !sameAsBase && length > 0 and is routed into the replace-commit path — deleteSurroundingTextInCodePoints(1, 0) eats the character that was just committed, and the new one takes its place.

X  keyDown → insertText("X")   base="X"
c  keyDown → insertText("c")   base="c"
o  keyDown → insertText("o")   base="o", g_letter_key_down = YES
   ── Compose updates → IMKit queries selectedRange → picker "detected" ──
d  keyDown (o still down)      → hijacked: delete "o", commit "d"
e  keyDown → insertText("e")
                                → "Xcde"

The existing reset

if (!g_letter_key_down && isLetterDown && !isRepeat) { g_press_and_hold_queried = NO; }

cannot help: g_letter_key_down is precisely the flag that stays YES through a roll-over.

Fix

Two guards, both resting on the same invariant — PressAndHold only ever continues on the same physical key, and its accent is picked with a number key, the arrows or the mouse, never with a letter:

  • nucleus_note_press_and_hold_query() ignores queries that arrive while a different key is being processed (event.keyCode != g_base_key_code), so ordinary typing no longer arms the picker.
  • nucleus_insert_text() drops any pending picker state when a different letter key goes down, so even a stale flag cannot hijack a commit.

The genuine flow is untouched: the picker engages on the base key's repeats (isRepeat, same keyCode) and the accent arrives on a non-letter event.

Testing

Related: #595, #596, #599.

… pick

Typing quickly on macOS silently dropped characters — "Xcode" landed as
"Xcde". Fast typists roll over: the previous key is still physically down
when the next one goes down, and `g_letter_key_down` only clears on the
*base* key's keyUp. Any selectedRange / attributedSubstring query arriving
in that window armed `g_press_and_hold_queried`, so the next `insertText:`
was routed into the replace-commit path — `deleteSurroundingTextInCodePoints(1, 0)`
ate the character that had just been committed and the new one took its
place.

The existing `!g_letter_key_down` reset never fired here: that flag is
precisely the one that stays YES through a roll-over.

Two guards, both resting on the same invariant — PressAndHold only ever
continues on the *same* physical key:

- `nucleus_note_press_and_hold_query` ignores queries that arrive while a
  different key is being processed (`event.keyCode != g_base_key_code`).
- `nucleus_insert_text` drops any pending picker state when a different
  letter key goes down. An accent is picked with a number key, the arrows
  or the mouse — never a letter — so the real flow (repeat keyDown, then a
  non-letter pick) is untouched.

Verified on a real Mac: "Xcode", repeated letters ("aaa"), and Japanese
IME input (ATOK / Kotoeri, NucleusFramework#595) all type cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants