fix(droid-control): preserve text during native typing - #50
Merged
Merged
Conversation
Bulk wtype assigns distinct characters consecutive evdev keycodes. Ghostty interprets positions 14 and 15 as Backspace and Tab, dropping punctuation and replacing letters before input reaches the application. Reuse single-character native keymaps for literal typing and character presses. Keep chord modifier release and tuistory bulk input unchanged. Add deterministic boundary tests and an opt-in real Ghostty byte probe; the original wrapper fails the reported prompt in plain and Kitty modes. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
factory-davidgu
approved these changes
Sep 15, 2026
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.
Description
What
Preserves literal text sent through
tctl typeon native Linux Wayland. Real Ghostty byte captures reproduce the reported dropped:andQ→Tab substitution on the baseline and pass on this patch. The fix costs one process per character: a warm 52-character prompt measured 403 ms instead of 223 ms.Why
wtypeallocates sequential evdev keycodes for distinct characters. InReply with exactly: QA ready. Do not call any tools., the first unintended assignment is::Q0x09Swapping the 14th and 15th distinct characters moves the corruption with their keycodes. A direct raw-stdin probe reproduces it without Droid or the diagnostic wrapper.
How
Routes literal typing and single-character presses through fresh single-character
wtypekeymaps.--preserves dash-prefixed text. Chords keep their existing explicit modifier releases; tuistory keeps bulk typing. No clipboard path, custom keyboard encoder, dependency, or pacing delay is added.Repro Recipe
With Ghostty, cage, and wtype installed, run from the repository root:
Expect the exact prompt. The baseline drops the colon and substitutes Tab for Q.
Related Issue
Discovered during CLI-1964 QA. This independently fixes the plugin input driver; it does not change CLI startup or close that work.
Reviewer Guide
Review depth: Standard.
plugins/droid-control/bin/tctl: one literal-input owner replaces the bulk call and duplicate character-press branch.tests/test_tctl.py: deterministic process-boundary contract and opt-in real Ghostty bytes.tctlchanges.Risk & Impact
typecommand against each implementation after warmup.Verification
Behavior verified @
7d58d672a6e083a457eed3229a687177b918d3d3:Coverage includes punctuation/capitals, accented/CJK/emoji/combining text, consecutive calls, Ctrl/Shift chords followed by unmodified text, whitespace, literal dashes, warmup, and stopping on injector failure.
Sentinel test:
TctlGhosttyTest.test_native_bytes_across_text_calls_and_chordschecks real incoming bytes, not screen reconstruction.Standard validators: scoped Python unittest suite, Bash syntax, and staged whitespace checks pass. CI runs deterministic boundary tests; native tests require
TCTL_NATIVE_E2E=1. All test-created terminal sessions were closed.