Skip to content

feat: add Retype Mode for remote-desktop/VDI dictation targets#562

Open
gabornyergesX wants to merge 1 commit into
altic-dev:mainfrom
gabornyergesX:feature/retype-mode
Open

feat: add Retype Mode for remote-desktop/VDI dictation targets#562
gabornyergesX wants to merge 1 commit into
altic-dev:mainfrom
gabornyergesX:feature/retype-mode

Conversation

@gabornyergesX

@gabornyergesX gabornyergesX commented Jul 9, 2026

Copy link
Copy Markdown

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-CGEvent posting helper
(postUnicodeChunks in TypingService.swift) by parameterizing it with a
configurable 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

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

#563

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version:
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally: xcodebuild ... test — 90/90 tests pass (1 pre-existing, unrelated failure in DictationE2ETests present 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) and swiftlint --strict now passes with
0 violations across all 128 files (the original patch pushed SettingsView's
struct body over the 2000-line limit; the new slowTypeTuningControls()
helper was moved into the existing same-file private extension SettingsView
block, following this repo's established pattern for splitting large view
bodies — see AISettingsView+AIConfiguration.swift). swiftformat was run
scoped 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. (Running
it unscoped across all of Sources reformats ~55 unrelated pre-existing
files — CI doesn't gate on swiftformat at all, only swiftlint --strict,
so that whole-repo drift is out of scope here.) No automated tests cover
TextInsertionMode/TypingService insertion paths in this repo.

Screenshots / Video

Attach screenshots or a video for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes.

  • No UI/visual changes; screenshots/video are not applicable.
image

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.

output

Notes

Reuses existing postUnicodeChunks chunking/CGEvent infrastructure rather
than 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 of
    backing off when backing off would produce an empty chunk (only reachable
    at chunkSize: 1, i.e. Retype Mode) — a surrogate pair (emoji, some CJK
    extensions) no longer gets split across two CGEvents.
  • slowTypeWarmupMs/slowTypeRampDelayMs/slowTypeSteadyDelayMs now use
    the same presence-check pattern slowTypeRampCharCount already used
    (defaults.object(forKey:) as? NSNumber) instead of
    defaults.double(forKey:) > 0, so a user-set 0ms persists instead of
    being indistinguishable from "unset."

Added two regression test files covering both
(Tests/FluidDictationIntegrationTests/RetypeModeChunkingTests.swift,
RetypeModeSettingsTests.swift) — verified they fail against the pre-fix
logic and pass against the fix.

Update 2: addressed two more Codex findings
(3,
4):

  • Extracted TypingService.settleDelayMs(mode:preferredTargetPID:). Retype
    Mode was unconditionally skipping the pre-capture focus-settle delay,
    reasoning its own warm-up covered the same need — but that warm-up runs
    after insertTextSlowly resolves targetPID, so a caller that hides
    Fluid's own window and relies on preferredTargetPID == nil (e.g.
    RewriteModeService.acceptRewrite) could capture a stale PID and type into
    the wrong app. The settle delay is now preserved whenever the target PID
    isn't already known, matching the other modes.
  • Extracted TypingService.rampEnd(for:rampCharCount:). A Ramp Length of 0
    (a valid, slider-reachable setting) still floored to a 1-unit ramp slice
    via unicodeChunkEnd's forward-progress guard — if the dictated text
    started with an emoji/surrogate pair, that 1-unit slice was a lone high
    surrogate. rampEnd now short-circuits to 0 before calling into
    unicodeChunkEnd at all.

Added RetypeModeFocusSettleTests.swift (6 tests) and 4 more cases to
RetypeModeChunkingTests.swift covering the ramp-boundary fix — 21
regression 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.

@github-actions github-actions Bot added needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Screenshots / Video

Visual files detected:

  • Sources/Fluid/Persistence/SettingsStore.swift
  • Sources/Fluid/UI/SettingsView.swift

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.

@gabornyergesX gabornyergesX force-pushed the feature/retype-mode branch from 78472d9 to ff82784 Compare July 9, 2026 07:20
@github-actions github-actions Bot removed needs PR template Pull request is missing required template content. needs screenshots Pull request needs screenshot or video evidence. labels Jul 9, 2026
@gabornyergesX gabornyergesX force-pushed the feature/retype-mode branch from ff82784 to 0dee66c Compare July 9, 2026 07:36
@gabornyergesX

Copy link
Copy Markdown
Author
image

@gabornyergesX gabornyergesX marked this pull request as ready for review July 9, 2026 13:40

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 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".

Comment thread Sources/Fluid/Services/TypingService.swift
Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
@gabornyergesX gabornyergesX force-pushed the feature/retype-mode branch from 0dee66c to c86b85a Compare July 9, 2026 13:55

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 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".

Comment thread Sources/Fluid/Services/TypingService.swift Outdated
Comment thread Sources/Fluid/Services/TypingService.swift Outdated
@gabornyergesX gabornyergesX force-pushed the feature/retype-mode branch from c86b85a to d8c3278 Compare July 9, 2026 14:09
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.
@gabornyergesX gabornyergesX force-pushed the feature/retype-mode branch from d8c3278 to a1ea553 Compare July 9, 2026 14:39
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