feat: add Retype Mode for remote-desktop/VDI dictation targets#562
feat: add Retype Mode for remote-desktop/VDI dictation targets#562gabornyergesX wants to merge 1 commit into
Conversation
|
The PR Policy check is blocking this PR because required template information is missing. Please update the PR description with:
Visual files detected:
Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template. If this remains incomplete for 48 hours after opening, the PR may be closed. |
78472d9 to
ff82784
Compare
ff82784 to
0dee66c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0dee66c6e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
0dee66c to
c86b85a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c86b85a108
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
c86b85a to
d8c3278
Compare
Types dictated text one character at a time with a warm-up pause and a slow-ramp-then-steady per-character delay, for remote-desktop/VDI targets where fast keystroke/unicode injection gets garbled or dropped because the remote keyboard channel needs a moment to sync session/layout state. Reuses the existing chunked-CGEvent posting infrastructure instead of adding a separate insertion path.
d8c3278 to
a1ea553
Compare

Description
Adds a new opt-in text insertion mode, "Retype Mode", that types dictated
text one character at a time with a warm-up pause and a slow-ramp-then-steady
per-character delay. This is for remote-desktop/VDI dictation targets where
the default instant-insertion path garbles or drops leading characters
because the remote keyboard channel needs a moment to sync session/layout
state before it reliably forwards fast key events. The existing "Clipboard
Paste" mode doesn't help in these sessions either, since some remote sessions
disable/block the clipboard.
Implementation reuses the existing chunked-
CGEventposting helper(
postUnicodeChunksinTypingService.swift) by parameterizing it with aconfigurable chunk size and inter-chunk delay, instead of duplicating the
event-creation logic in a separate code path. Warm-up delay, ramp length,
ramp pace, and steady pace are all user-tunable in Settings.
Type of Change
Related Issue or Discussion
#563
Testing
swiftlint --strict --config .swiftlint.yml Sourcesswiftformat --config .swiftformat Sourcesxcodebuild ... test— 90/90 tests pass (1 pre-existing, unrelated failure inDictationE2ETestspresent before this PR)Manually verified against a real remote-desktop session — with Retype Mode
enabled, previously-garbled leading characters are inserted correctly.
Debug build compiles cleanly (
xcodebuild -project Fluid.xcodeproj -scheme Fluid -configuration Debug build) andswiftlint --strictnow passes with0 violations across all 128 files (the original patch pushed
SettingsView'sstruct body over the 2000-line limit; the new
slowTypeTuningControls()helper was moved into the existing same-file
private extension SettingsViewblock, following this repo's established pattern for splitting large view
bodies — see
AISettingsView+AIConfiguration.swift).swiftformatwas runscoped to just this PR's 3 touched files: the only change it wanted inside
this PR's own code was demoting a floating (non-declaration-attached) doc
comment to a plain comment in
TypingService.swift, now applied. (Runningit unscoped across all of
Sourcesreformats ~55 unrelated pre-existingfiles — CI doesn't gate on
swiftformatat all, onlyswiftlint --strict,so that whole-repo drift is out of scope here.) No automated tests cover
TextInsertionMode/TypingServiceinsertion paths in this repo.Screenshots / Video
Attach screenshots or a video for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes.
The new "Retype Mode" entry in Settings → Text Insertion Mode, with its
warm-up/ramp/steady sliders.
Demo of Retype Mode inserting text character-by-character into a browser address bar (Chrome, Incognito): the sentence "This is a demo of the retyping feature." is typed in visibly one character at a time and lands correctly, with no dropped or scrambled leading characters.
Notes
Reuses existing
postUnicodeChunkschunking/CGEvent infrastructure ratherthan adding a parallel insertion code path — smaller diff, one path to
maintain.
Update: addressed both automated review findings from Codex
(1,
2):
unicodeChunkEnd's surrogate-pair guard now widens forward instead ofbacking off when backing off would produce an empty chunk (only reachable
at
chunkSize: 1, i.e. Retype Mode) — a surrogate pair (emoji, some CJKextensions) no longer gets split across two CGEvents.
slowTypeWarmupMs/slowTypeRampDelayMs/slowTypeSteadyDelayMsnow usethe same presence-check pattern
slowTypeRampCharCountalready used(
defaults.object(forKey:) as? NSNumber) instead ofdefaults.double(forKey:) > 0, so a user-set0mspersists instead ofbeing indistinguishable from "unset."
Added two regression test files covering both
(
Tests/FluidDictationIntegrationTests/RetypeModeChunkingTests.swift,RetypeModeSettingsTests.swift) — verified they fail against the pre-fixlogic and pass against the fix.
Update 2: addressed two more Codex findings
(3,
4):
TypingService.settleDelayMs(mode:preferredTargetPID:). RetypeMode was unconditionally skipping the pre-capture focus-settle delay,
reasoning its own warm-up covered the same need — but that warm-up runs
after
insertTextSlowlyresolvestargetPID, so a caller that hidesFluid's own window and relies on
preferredTargetPID == nil(e.g.RewriteModeService.acceptRewrite) could capture a stale PID and type intothe wrong app. The settle delay is now preserved whenever the target PID
isn't already known, matching the other modes.
TypingService.rampEnd(for:rampCharCount:). A Ramp Length of0(a valid, slider-reachable setting) still floored to a 1-unit ramp slice
via
unicodeChunkEnd's forward-progress guard — if the dictated textstarted with an emoji/surrogate pair, that 1-unit slice was a lone high
surrogate.
rampEndnow short-circuits to0before calling intounicodeChunkEndat all.Added
RetypeModeFocusSettleTests.swift(6 tests) and 4 more cases toRetypeModeChunkingTests.swiftcovering the ramp-boundary fix — 21regression tests total across the three new test files. Verified both fixes
the same way as Update 1: reverted just the fix logic, confirmed the new
tests fail, restored the fix.