Skip to content

Add hands-free Spoken Send - #829

Open
altic-dev wants to merge 15 commits into
mainfrom
B/518-spoken-send
Open

Add hands-free Spoken Send#829
altic-dev wants to merge 15 commits into
mainfrom
B/518-spoken-send

Conversation

@altic-dev

@altic-dev altic-dev commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Description

Adds optional Spoken Send: say a configurable phrase at the end of dictation to remove the command phrase, stop recording, insert the text, and dispatch Enter, Shift+Enter, or Command+Enter. Delivery is suppressed for secure fields, terminal apps, held modifiers, changed focus targets, or unconfirmed insertion.

Type of Change

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

Related Issue or Discussion

Closes #518

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 27.0
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally: 245/245 passed, including 34 Spoken Send tests

Live dogfood through BlackHole 2ch and macOS speech:

  • The Codex composer removed trailing send it, inserted the remaining text, and dispatched Enter.
  • literal send it inserted the phrase without dispatching Enter.
  • send it in the middle of a sentence remained normal dictated text.

Screenshots / Video

Spoken Send settings were visually inspected in the installed FluidVoice 1.6.8 app.
Spoken Send settings

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

Notes

The feature is disabled by default. Audio-level monitoring is active only during the 1.5-second Spoken Send countdown. Text insertion and Enter dispatch remain bound to the exact captured field; Enter is suppressed unless insertion is confirmed.

@github-actions github-actions Bot added the needs screenshots Pull request needs screenshot or video evidence. label Aug 10, 2026
@github-actions

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/ContentView.swift
  • Sources/Fluid/Persistence/SettingsStore.swift
  • Sources/Fluid/Services/MenuBarManager.swift
  • Sources/Fluid/Services/NotchOverlayManager.swift
  • Sources/Fluid/UI/SettingsView.swift
  • Sources/Fluid/Views/BottomOverlayView.swift
  • Sources/Fluid/Views/NotchContentViews.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.

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

ℹ️ 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 Outdated
@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds configurable hands-free Spoken Send, including phrase parsing, countdown and overlay state, persisted settings, and guarded text insertion followed by a configurable Return action.

  • Captures the original focused field and recording session for delivery checks.
  • Adds verified exact-target Accessibility insertion and suppresses sending for unsafe targets or unconfirmed state.
  • Adds settings backup support, UI controls, and Spoken Send integration tests.

Confidence Score: 4/5

The PR does not yet appear safe to merge because Return can still be delivered to a different field in the captured application if focus changes after the final check.

The previously reported Return focus race remains: exact focus and field value are checked immediately before a PID-targeted event, but the event is ultimately routed to whichever element is focused when the target process receives it.

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

Reviews (12): Last reviewed commit: "isolate spoken send delivery sessions" | Re-trigger Greptile

Comment thread Sources/Fluid/Services/TypingService.swift Outdated
Comment thread Sources/Fluid/Services/TypingService.swift Outdated
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

FluidVoice PR build ready

Download FluidVoice-PR-829-457cbcad55ee

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-829.app.zip.

  2. Move FluidVoice Add hands-free Spoken Send #829.app into the /Applications folder.

  3. Open Terminal and remove the download quarantine marker:

    xattr -dr com.apple.quarantine "/Applications/FluidVoice #829.app"
    
  4. In Applications, Control-click FluidVoice Add hands-free Spoken Send #829.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

@github-actions github-actions Bot removed the needs screenshots Pull request needs screenshot or video evidence. label Aug 10, 2026
Comment thread Sources/Fluid/Services/TypingService.swift Outdated
Comment thread Sources/Fluid/Services/TypingService.swift Outdated

@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: 1b3201a757

ℹ️ 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 Outdated
Comment thread Sources/Fluid/Services/SpokenSendParser.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: 6cb9fe9979

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

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

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

@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: 626c953bb2

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

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

ℹ️ 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/ASRService+SpokenPunctuationFormatting.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: ffb7df0d08

ℹ️ 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 +560 to 564
let expectedActionValue: String? = if hasTextToInsert {
expectedInsertedValue
} else {
Self.nonemptyEditableDraftValue(requiredFocusTarget)
}

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 Snapshot existing drafts before waiting for modifiers

When the utterance is only the send phrase and a physical modifier remains held, the two-second wait occurs before nonemptyEditableDraftValue captures the draft. If the user or destination app changes the composer during that wait, releasing the modifier causes the new value to be accepted as the expected value and immediately submitted. Capture the existing draft before waiting and require that same value to remain present before dispatch.

Useful? React with 👍 / 👎.

@altic-dev

Copy link
Copy Markdown
Owner Author

@codex review

Comment on lines +838 to +843
guard Self.isExactFocusTargetActive(target),
self.getElementStringValue(target.element) == expectedValue
else {
return false
}
keyDown.postToPid(target.pid)

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 Return escapes captured focus

When focus moves to another field in the captured application after the final exact-focus check, keyDown.postToPid(target.pid) delivers Return to the newly focused field, causing that field to submit or activate its contents instead of the captured field.

Knowledge Base Used: Dictation Capture Flow

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/Fluid/Services/TypingService.swift
Line: 838-843

Comment:
**Return escapes captured focus**

When focus moves to another field in the captured application after the final exact-focus check, `keyDown.postToPid(target.pid)` delivers Return to the newly focused field, causing that field to submit or activate its contents instead of the captured field.

**Knowledge Base Used:** [Dictation Capture Flow](https://app.greptile.com/altic/-/custom-context/knowledge-base/altic-dev/fluidvoice/-/docs/dictation-capture-flow.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

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

ℹ️ 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 Outdated
Comment on lines +2485 to +2486
let targetMatchesRecordingFocus = typingTarget.pid != nil
&& typingTarget.pid == self.recordingFocusTarget?.pid

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 Snapshot the focus target before asynchronous post-processing

If another dictation starts while the previous one is doing AI post-processing, captureRecordingTargetContext() overwrites both recordingFocusTarget and recordingTargetPID; this comparison then sees two matching values from the new session and allows the old transcript to be inserted and submitted in the new session's field. The hotkey start path permits this once asr.stop() has returned, so a slow AI request can send a message to the wrong app or conversation. Preserve the stopped session's target in local state and use that snapshot throughout its delivery checks.

Useful? React with 👍 / 👎.

@altic-dev

Copy link
Copy Markdown
Owner Author

@codex review

@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: 457cbcad55

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

// Capture the focused target PID BEFORE any overlay/UI changes.
// Used to restore focus when the user interacts with overlay dropdowns.
let focusedPID = TypingService.captureSystemFocusedPID()
self.recordingSessionID = UUID()

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 Rotate the session before starting audio capture

When a new dictation is accepted while the previous one is still doing AI post-processing, the session remains unchanged until onCaptureStarted runs after first PCM. During that startup window, the previous operation passes canDeliverCompletedRecording, restores its old focus target, and can do so before the callback captures the new target; the new recording then captures that restored old field and may later insert or send into the wrong conversation. The fresh evidence in this revision is that the delivery snapshots are isolated, but the identifier is still rotated only here rather than synchronously when beginDictationRecording accepts the new session.

Useful? React with 👍 / 👎.

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.

[FEATURE] Auto-submit (simulate 'Enter') after pasting by holding a modifier key

1 participant