Skip to content

fix(type-text): force clipboard-paste for non-ASCII text - #660

Merged
sonichi merged 1 commit into
mainfrom
fix/typetext-unicode-keystroke
May 13, 2026
Merged

sonichi merged 1 commit into
mainfrom
fix/typetext-unicode-keystroke

Conversation

@sonichi

@sonichi sonichi commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • type_text was corrupting any non-ASCII char (emoji, em-dashes, curly quotes) because AppleScript's keystroke "..." routes through virtual-key codes that can't represent characters outside basic ASCII typing range
  • Fix: add hasNonAscii = /[^\x00-\x7f]/.test(text) to the existing conditional that already routes multi-line / long text through the clipboard-paste branch (which preserves bytes)

Root cause (from src/inline-tools.ts:268)

Before this PR, type_text only used the clipboard-paste branch when text had newlines or length > 80. Single-line short text fell to:

osascript -e 'tell application "System Events" to keystroke "${safeText}"'

This works for ASCII but butchers multi-byte UTF-8. Real failures Chi hit today:

  • — (em-dash, U+2014, UTF-8 E2 80 94) → reinterpreted as Mac Roman ‚Äî
  • 🤖 (robot emoji, UTF-8 F0 9F A4 96) → garbled output
  • ' → also got escape-leaked through Cursor's auto-pair, but that's a Cursor-side artifact (not in this PR's scope)

Test plan

  • Restart voice-agent after merge
  • Say "type robot emoji" or similar prompt → confirm 🤖 lands clean in the focused field
  • Try em-dash, curly quotes, accented chars (é, ñ, ü) — should all round-trip clean
  • Verify ASCII typing path still works for short strings ("hello", "yes") — keystroke fast path retained

Why the fix is minimal

The clipboard-paste branch already exists (lines 270-291 of inline-tools.ts) and is well-tested. This PR only widens the entry condition. Zero new code paths; zero behavior change for ASCII inputs.

🤖 Generated with Claude Code

AppleScript's `keystroke "..."` routes characters through virtual-key
codes that can't represent anything outside basic ASCII. Emoji (🤖),
em-dashes (—), curly quotes — all get corrupted: UTF-8 bytes get
reinterpreted as Mac Roman (e.g. em-dash bytes E2 80 94 → "‚Äî",
robot emoji 4-byte F0 9F A4 96 → garble).

Add a `hasNonAscii` check (/[^\x00-\x7f]/) to the same conditional
that already routes multi-line / long text through the clipboard-paste
branch. The paste branch round-trips through pbcopy → System Events
Cmd-V, which preserves bytes losslessly.

Per Chi 2026-05-13: typed the Mini DeepMind X-post draft into Cursor
via voice, em-dashes got mojibaked, then 🤖 got garbled. Workaround
of "I'll put it in your clipboard manually" wasn't a fix — this is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonichi
sonichi merged commit 09f35f6 into main May 13, 2026
1 check passed
@sonichi
sonichi deleted the fix/typetext-unicode-keystroke branch May 13, 2026 16:04
sonichi added a commit that referenced this pull request May 13, 2026
Follow-up to #660. The first fix routed emoji + non-ASCII text to
the paste branch, but pbcopy still corrupted multi-byte UTF-8 in the
voice-agent's launchd context — Chi reported 🤖 still didn't make it
to Cursor after restart.

Root cause: voice-agent runs under launchd which doesn't inherit the
terminal's LANG/LC_CTYPE. The child shell that ran `pbcopy < tmpClip`
defaulted to POSIX/C locale; pbcopy in that locale doesn't pass
multi-byte sequences through cleanly — they end up as "??" on the
pasteboard.

Two changes:
1. Set `LANG=en_US.UTF-8` + `LC_ALL=en_US.UTF-8` on every execSync call
   in the paste branch via the `env:` option.
2. Switch from `writeFileSync` + `pbcopy < file` to `execSync('pbcopy',
   { input: pasteText })` — bypasses the shell redirect entirely and
   pipes bytes directly to pbcopy's stdin. Same approach for the
   clipboard restore step. Eliminates a class of locale/encoding bugs.

Side benefit: no /tmp files leaked, no unlinkSync needed.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
AppleScript's `keystroke "..."` routes characters through virtual-key
codes that can't represent anything outside basic ASCII. Emoji (🤖),
em-dashes (—), curly quotes — all get corrupted: UTF-8 bytes get
reinterpreted as Mac Roman (e.g. em-dash bytes E2 80 94 → "‚Äî",
robot emoji 4-byte F0 9F A4 96 → garble).

Add a `hasNonAscii` check (/[^\x00-\x7f]/) to the same conditional
that already routes multi-line / long text through the clipboard-paste
branch. The paste branch round-trips through pbcopy → System Events
Cmd-V, which preserves bytes losslessly.

Per Chi 2026-05-13: typed the Mini DeepMind X-post draft into Cursor
via voice, em-dashes got mojibaked, then 🤖 got garbled. Workaround
of "I'll put it in your clipboard manually" wasn't a fix — this is.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
Follow-up to sonichi#660. The first fix routed emoji + non-ASCII text to
the paste branch, but pbcopy still corrupted multi-byte UTF-8 in the
voice-agent's launchd context — Chi reported 🤖 still didn't make it
to Cursor after restart.

Root cause: voice-agent runs under launchd which doesn't inherit the
terminal's LANG/LC_CTYPE. The child shell that ran `pbcopy < tmpClip`
defaulted to POSIX/C locale; pbcopy in that locale doesn't pass
multi-byte sequences through cleanly — they end up as "??" on the
pasteboard.

Two changes:
1. Set `LANG=en_US.UTF-8` + `LC_ALL=en_US.UTF-8` on every execSync call
   in the paste branch via the `env:` option.
2. Switch from `writeFileSync` + `pbcopy < file` to `execSync('pbcopy',
   { input: pasteText })` — bypasses the shell redirect entirely and
   pipes bytes directly to pbcopy's stdin. Same approach for the
   clipboard restore step. Eliminates a class of locale/encoding bugs.

Side benefit: no /tmp files leaked, no unlinkSync needed.

Co-authored-by: Claude Opus 4.7 (1M context) <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.

1 participant