Skip to content

Make clipboard paste the default - #811

Open
grohith327 wants to merge 12 commits into
mainfrom
rohith/reliable-paste
Open

Make clipboard paste the default#811
grohith327 wants to merge 12 commits into
mainfrom
rohith/reliable-paste

Conversation

@grohith327

Copy link
Copy Markdown
Collaborator

Description

  • Override the default to "Clipboard paste" as the recommended option
  • Simulate Cmd+v in order to insert the text reliably into the app

Type of Change

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

Related Issue or Discussion

#802

Testing

  • Tested on Apple Silicon Mac
  • Tested on macOS version:
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran tests locally: insertion across different apps and varying lenghts

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.

Notes

Add reviewer context, rollout notes, or known tradeoffs here.

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR makes clipboard-based paste the default insertion method and adds coordinated focus restoration, delivery-failure feedback, clipboard preservation, latency analytics, and integration tests.

  • Adds a serialized clipboard-paste coordinator that snapshots and restores pasteboard contents.
  • Routes dictation, retry, spoken-send, and rewrite delivery through asynchronous result handling.
  • Migrates insertion settings to the recommended clipboard mode and adds delivery latency aggregation.

Confidence Score: 4/5

The PR is not yet safe to merge because a failed asynchronous paste command can overwrite clipboard content copied after Fluid’s temporary write.

The previously reported failure remains at current HEAD: the normal settlement path verifies clipboard ownership, but restoreAfterFailure still restores the old snapshot unconditionally after the awaited paste-command operation fails.

Files Needing Attention: Sources/Fluid/Services/PasteDeliveryCoordinator.swift

Reviews (10): Last reviewed commit: "add analytics to measure latency" | Re-trigger Greptile

Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift

@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: 719f3de47b

ℹ️ 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/RewriteModeService.swift
Comment thread Sources/Fluid/Views/RewriteModeView.swift
@grohith327

Copy link
Copy Markdown
Collaborator Author

@greptileai review again based on my replies

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

FluidVoice PR build ready

Download FluidVoice-PR-811-2efaa124fdc4

The artifact contains the ad-hoc-signed app ZIP, Xcode archive, build manifest, and installation instructions. It expires 5 days after the build.

Install the app

  1. Extract the downloaded artifact, then extract FluidVoice-PR-811.app.zip.

  2. Move FluidVoice Make clipboard paste the default #811.app into the /Applications folder.

  3. Open Terminal and remove the download quarantine marker:

    xattr -dr com.apple.quarantine "/Applications/FluidVoice #811.app"
    
  4. In Applications, Control-click FluidVoice Make clipboard paste the default #811.app and choose Open.

  5. If macOS still blocks it, open System Settings → Privacy & Security, click Open Anyway, and confirm.

This build has its own app identity, so its permissions are separate from the release version of FluidVoice.

View workflow run

Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift

@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: c6542d82fc

ℹ️ 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".


@MainActor
final class SystemPasteCommandPoster: PasteCommandPosting {
private static let vKeyCode: CGKeyCode = 0x09

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve the paste key using the active keyboard layout

On keyboard layouts where virtual key code 0x09 does not produce “v”, this posts Command plus a different character instead of Command+V. The previous implementation resolved the key code through the current TIS layout, but this hard-coded replacement still returns success after posting, so the coordinator reports the transcript as delivered even though no paste occurred. Restore layout-aware lookup, including reevaluation after runtime layout switches.

Useful? React with 👍 / 👎.

@MainActor
final class PasteDeliveryCoordinator {
static let shared = PasteDeliveryCoordinator()
nonisolated static let defaultSettlementDelayNanoseconds: UInt64 = 500_000_000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the temporary clipboard available for slow paste targets

When a busy or asynchronous target handles the posted Command+V more than 500 ms later, settlement has already restored the original clipboard, so the target pastes the old contents instead of the transcript while delivery was reported successful. The newly added testDefaultSettlementDelayIsLongEnoughForSlowPasteTargets at Tests/FluidDictationIntegrationTests/PasteDeliveryCoordinatorTests.swift:228-230 explicitly requires 1.5 seconds, so the current constant also makes that test fail.

Useful? React with 👍 / 👎.

@grohith327
grohith327 force-pushed the rohith/reliable-paste branch from c6542d8 to 6169400 Compare August 11, 2026 04:32

@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: 6169400fd6

ℹ️ 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/PasteDeliveryCoordinator.swift
Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift
Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift

@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: ad2a01b3a0

ℹ️ 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/ContentView.swift
Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift
Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift

@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: 5dd3ea1ff0

ℹ️ 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 on lines +1666 to +1668
let isStillFocused = context.pid == TypingService.currentFocusedPID() &&
(context.element == nil || TypingService.isCapturedFocusStillActive(context))
return (context.pid, !isStillFocused)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the exact captured field before delivery

When focus moves to another editable element in the same application during recording, this can incorrectly decide that the original field is still focused: isCapturedFocusStillActive ultimately accepts any text-like AX element owned by the same PID, even when it is not CFEqual to the captured element. The restore is then skipped and the global Cmd+V lands in the newly focused field rather than the recorded destination; require exact element identity here or always prepare the captured target when the focused element changed.

Useful? React with 👍 / 👎.

Comment on lines +158 to +159
private static func restoreExactTarget(_ context: RecordingTargetContext) async -> Bool {
guard AXIsProcessTrusted(), context.window != nil, context.element != nil else { return false }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow focus restoration without captured window metadata

For applications that expose the focused AX element but not kAXFocusedWindowAttribute or kAXMainWindowAttribute, the captured context legitimately has window == nil; this guard rejects it before attempting to set kAXFocusedAttribute on the usable element, and the recovery guard rejects it again. After any focus displacement, delivery therefore reports targetRestoreFailed even though the exact field could still be restored; require the element and treat the window raise as optional, as the previous restoration path did.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown

This pull request has been marked stale because it has had no activity for 5 days. It will be closed in 2 days if there is no further activity. Add the keep-open or pinned label to prevent automatic closure.

@github-actions github-actions Bot added the stale label Aug 17, 2026
@grohith327 grohith327 removed the stale label Aug 17, 2026
@grohith327
grohith327 force-pushed the rohith/reliable-paste branch from 5dd3ea1 to f3c8401 Compare August 17, 2026 00:19

@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: f3c84014ee

ℹ️ 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 on lines +2634 to +2635
if case let .recoverableFailure(failure) = result {
self.showTextDeliveryFailure(failure, transcript: text)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show recovery UI for Paste Last failures

When Paste Last is invoked through its global shortcut while the recording overlay is hidden, this failure path only stores the error and calls finishProcessingKeepingOverlayVisible(). That method does not present an idle overlay—NotchOverlayManager.setProcessing(false) only updates state—so the retry/copy controls remain invisible and the failed paste appears to do nothing. Explicitly show the overlay before reporting the recoverable failure, or provide feedback through a surface that is already visible.

Useful? React with 👍 / 👎.

@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: 77261b6ef9

ℹ️ 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 on lines +676 to +677
let didWrite = self.pasteboard.writeIntentionalText(lease.text)
self.log("intentional_copy_settled generation=\(generation) success=\(didWrite)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recover when the intentional clipboard copy fails

When “Copy transcription to clipboard” is enabled and the settlement write fails, writeIntentionalText may already have cleared the general pasteboard before returning false, but this result is only logged after self.lease discarded the original snapshot. A transient pasteboard/server write failure can therefore leave the clipboard empty—losing both the user's previous contents and the requested transcript—even though insertion was reported successful; retain the lease and restore or retry when ownership has not moved to an external copy.

Useful? React with 👍 / 👎.

Comment thread Sources/Fluid/Services/PasteDeliveryCoordinator.swift
@grohith327
grohith327 force-pushed the rohith/reliable-paste branch from 77261b6 to 4cb6683 Compare August 25, 2026 03:45

@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: 4cb6683f37

ℹ️ 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 on lines +562 to +564
try? await Task.sleep(nanoseconds: 50_000_000)
guard Self.isExactFocusTargetActive(requiredFocusTarget),
self.postReturnKey(postInsertionKey, targetPID: preferredTargetPID)
await self.postReturnKey(postInsertionKey, targetPID: preferredTargetPID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wait for paste completion before dispatching Spoken Send

When Spoken Send is used with the new default clipboard path, typeOutputPlanInstantly returns as soon as Cmd+V is posted, so this fixed 50 ms delay can expire before an asynchronous target has inserted the clipboard payload. The Return event can then send the pre-existing draft, with the dictated text appearing afterward in a new draft; confirm that the paste was consumed before dispatching the post-insertion key.

Useful? React with 👍 / 👎.

Comment on lines +2420 to 2421
if shouldCopyToClipboard, !shouldTypeExternally {
ClipboardService.copyToClipboard(finalText)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve automatic clipboard backup when insertion fails

When “Copy to Clipboard” is enabled for an external dictation, shouldCopyToClipboard implies shouldTypeExternally, so this branch never performs the advertised automatic backup. The transcript is only made permanent later by a successful delivery settlement; if focus restoration, Accessibility, snapshot capture, or the temporary write fails, the old implementation's clipboard backup is lost and the user must manually press the recovery copy button.

Useful? React with 👍 / 👎.

@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: 2efaa124fd

ℹ️ 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 on lines +532 to +533
vDown.post(tap: .cghidEventTap)
vUp.post(tap: .cghidEventTap)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep paste commands bound to the prepared target

When focus changes after ContentView prepares the recorded field but before this event is posted—for example, while the clipboard snapshot is being materialized—these HID-tap events go to whichever application is currently focused. The coordinator neither receives the preferred target PID nor revalidates the captured element immediately before dispatch, so it can paste the transcript into an unintended application and still return .commandPosted; post to the prepared target and verify its exact focus at dispatch time.

Useful? React with 👍 / 👎.

Comment on lines +2476 to +2478
deliveryResult = deliveryOutcome.didInsert || deliveryOutcome.didDispatchAction
? .commandPosted
: .recoverableFailure(.pasteCommandFailed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the actual Spoken Send delivery failure

When Spoken Send insertion fails before dispatch because Accessibility is unavailable, clipboard capture/write fails, or target preparation fails, this conversion collapses every failure into .pasteCommandFailed. The recovery UI consequently shows “Could not send the paste command” instead of actionable guidance such as enabling Accessibility or handling an unpreservable clipboard; carry the underlying TextDeliveryFailure through DeliveryOutcome rather than fabricating one here.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

This pull request has been marked stale because it has had no activity for 5 days. It will be closed in 2 days if there is no further activity. Add the keep-open or pinned label to prevent automatic closure.

@github-actions github-actions Bot added the stale label Sep 1, 2026
@grohith327 grohith327 removed the stale label Sep 2, 2026
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