Skip to content

Improve overlay feedback, Edit Mode capture, and Codex command routing - #408

Open
tarushvkodes wants to merge 35 commits into
altic-dev:mainfrom
tarushvkodes:codex/lower-visualizer-threshold
Open

Improve overlay feedback, Edit Mode capture, and Codex command routing#408
tarushvkodes wants to merge 35 commits into
altic-dev:mainfrom
tarushvkodes:codex/lower-visualizer-threshold

Conversation

@tarushvkodes

@tarushvkodes tarushvkodes commented Jun 23, 2026

Copy link
Copy Markdown

Summary

This PR includes a focused set of user-facing improvements to FluidVoice's overlay, Edit Mode, and Command Mode.

Overlay feedback

  • Lowers the default visualizer noise threshold from 0.4 to 0.12.
  • This makes the waveform respond to quieter but valid microphone input, so the overlay no longer looks inactive while FluidVoice is listening.
  • Adds an integration test covering the more sensitive default.

Edit Mode reliability

  • Improves selected-text capture when apps do not expose highlighted text through Accessibility APIs.
  • Adds a clipboard-preserving Cmd+C fallback after AXSelectedText / selected-range capture fails.
  • Extends the fallback wait window for Google Docs and similar browser-based rich editors, which can update the pasteboard more slowly.
  • Tracks the original target app PID when Edit Mode captures selection context.
  • Routes accepted rewrites through a reliable paste path so rich web editors, especially Google Docs, replace the selected text more consistently than with synthetic direct typing.

Command Mode to Codex

  • Adds an optional CommandModeRouteToCodex setting, defaulting off, so existing Command Mode behavior remains unchanged unless enabled.
  • Adds two Codex handoff styles:
    • Notch: keeps FluidVoice's Command Mode notch UX, runs the bundled Codex CLI in the background, and displays the result in the notch without focusing the Codex app.
    • App: activates the Codex desktop app and pastes/submits the spoken command there.
  • Exposes compact Codex controls in the Command Mode UI.
  • Bypasses chat-provider readiness checks when Codex routing is enabled.
  • Stabilizes expanded command-notch presentation so the notch opens smoothly and stays visible for Codex output.

Why

A few real-world workflows were failing or feeling confusing:

  • Quiet microphone input could be heard by the app, but the waveform barely moved, making the overlay look broken.
  • In Google Docs and other rich/browser editors, Edit Mode could fail to see selected text or fail to replace the selected text cleanly.
  • Command Mode is useful as a voice handoff into Codex, but focusing the Codex app can be intrusive. The notch handoff keeps the interaction in FluidVoice's overlay instead.

The changes are preference-backed and conservative: normal dictation and default Command Mode behavior are preserved unless the user enables the Codex route.

Validation

Validated locally on macOS with Xcode:

  • swiftc -parse $(rg --files Sources/Fluid -g '*.swift')
  • git diff --check
  • plutil -lint Fluid.xcodeproj/project.pbxproj Info.plist Fluid.entitlements
  • xcodebuild clean build -project Fluid.xcodeproj -scheme Fluid -destination 'platform=macOS,arch=arm64' CODE_SIGN_IDENTITY='' CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
  • Manual install/sign/run of /Applications/FluidVoice.app
  • Manual Codex CLI smoke test with the bundled Codex binary

Notes

  • This branch has been rebased onto upstream main at v1.6.1 (49e91c0).
  • The earlier Package.resolved-only MCP SDK bump was dropped during rebase because upstream removed that dependency.
  • GitHub Actions may require upstream approval to run on the fork PR.

@tarushvkodes tarushvkodes changed the title [codex] Lower default visualizer threshold [codex] Improve overlay capture and Codex command routing Jun 24, 2026
@tarushvkodes

Copy link
Copy Markdown
Author

Update: improved Edit Mode replacement for Google Docs and similar browser editors.\n\n- Captures the original target PID when selected text is captured, so accepting a rewrite can return to the document instead of aiming at the FluidVoice overlay.\n- Routes Edit Mode acceptance through the reliable paste path, which web rich-text editors handle better than synthetic direct typing.\n- Gives clipboard-based selection capture a longer window for Google Docs/browser editors that update the pasteboard more slowly after Cmd+C.\n\nValidated locally:\n- swiftc parse over Sources/Fluid Swift files\n- git diff --check\n- plutil lint for project/Info/entitlements\n- xcodebuild clean build for macOS arm64\n- installed and ad-hoc signed local /Applications/FluidVoice.app

@tarushvkodes
tarushvkodes force-pushed the codex/lower-visualizer-threshold branch from 003a852 to 314f9c7 Compare June 28, 2026 15:25
@tarushvkodes

Copy link
Copy Markdown
Author

Update: rebased this branch onto upstream main at v1.6.1 (49e91c0) and reinstalled a fresh local build.\n\nNotes:\n- Dropped the old Package.resolved-only MCP SDK bump during rebase because upstream removed that dependency.\n- Preserved the visualizer threshold, Codex command mode, notch output, and Google Docs/Edit Mode replacement changes.\n- PR head is now 314f9c7 and reports mergeable.\n\nValidated locally:\n- swiftc parse over Sources/Fluid Swift files\n- git diff --check\n- plutil lint for project/Info/entitlements\n- xcodebuild clean build for macOS arm64\n- installed and ad-hoc signed /Applications/FluidVoice.app

@tarushvkodes tarushvkodes changed the title [codex] Improve overlay capture and Codex command routing Improve overlay feedback, Edit Mode capture, and Codex command routing Jun 28, 2026
@tarushvkodes
tarushvkodes marked this pull request as ready for review June 28, 2026 15:26
Copilot AI review requested due to automatic review settings June 28, 2026 15:26

Copilot AI 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.

Pull request overview

This PR improves FluidVoice’s user-facing feedback and reliability across three flows: overlay waveform responsiveness, Edit Mode selection capture + rewrite insertion, and optional Command Mode routing to Codex (notch or app handoff).

Changes:

  • Makes the overlay visualizer respond to quieter mic input by lowering the default noise threshold and adds an integration test for the new default.
  • Improves Edit Mode reliability by adding clipboard-based selected-text capture fallback and forcing a “reliable paste” insertion path for accepted rewrites, with PID tracking.
  • Adds an opt-in Command Mode “Route to Codex” setting with notch/app handoff styles, updates UI controls, and stabilizes expanded notch output behavior.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Tests/FluidDictationIntegrationTests/DictationE2ETests.swift Adds an integration test asserting the new, more sensitive default visualizer noise threshold.
Sources/Fluid/Views/CommandModeView.swift Adds Codex routing UI controls and adjusts readiness/notch-output behavior based on routing settings.
Sources/Fluid/UI/SettingsView.swift Updates the visualizer threshold reset behavior to use the new default constant.
Sources/Fluid/Services/TypingService.swift Introduces a “force reliable paste” path (typeTextReliably) and plumbs it through the insertion pipeline.
Sources/Fluid/Services/TextSelectionService.swift Adds clipboard-preserving Cmd+C fallback for selected-text capture when AX APIs fail.
Sources/Fluid/Services/RewriteModeService.swift Tracks target PID for Edit Mode context and routes accepted rewrites via the reliable paste path.
Sources/Fluid/Services/NotchOverlayManager.swift Stabilizes expanded command-notch presentation state transitions and cleanup behavior.
Sources/Fluid/Services/CommandModeService.swift Adds Codex routing branch and notch sync behavior for Codex “notch” handoff.
Sources/Fluid/Services/CodexHandoffService.swift New service that runs Codex CLI for notch output or activates/pastes into the Codex app.
Sources/Fluid/Persistence/SettingsStore+CommandMode.swift Bypasses command readiness issues when Codex routing is enabled.
Sources/Fluid/Persistence/SettingsStore.swift Adds default visualizer threshold constant and persists Codex routing + handoff-style settings.
Sources/Fluid/ContentView.swift Routes rewrite acceptance through RewriteModeService.acceptRewrite(...) with PID targeting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

private func pasteAndSubmit(_ text: String) async -> Bool {
Self.pasteboardSessionSemaphore.wait()
Comment on lines +175 to +197
pasteboard.clearContents()
guard pasteboard.setString(text, forType: .string) else {
self.restorePasteboardSnapshot(snapshot, to: pasteboard)
Self.pasteboardSessionSemaphore.signal()
return false
}

guard self.sendCommandKey("v") else {
self.restorePasteboardSnapshot(snapshot, to: pasteboard)
Self.pasteboardSessionSemaphore.signal()
return false
}

try? await Task.sleep(nanoseconds: 100_000_000)
guard self.sendReturnKey() else {
self.restorePasteboardSnapshot(snapshot, to: pasteboard)
Self.pasteboardSessionSemaphore.signal()
return false
}

try? await Task.sleep(nanoseconds: 1_000_000_000)
self.restorePasteboardSnapshot(snapshot, to: pasteboard)
Self.pasteboardSessionSemaphore.signal()
Comment on lines +170 to +188
let deadline = Date().addingTimeInterval(1.0)
var copiedText: String?
repeat {
if pasteboard.changeCount != clearedChangeCount,
let text = pasteboard.string(forType: .string),
!text.isEmpty
{
copiedText = text
break
}
usleep(15_000)
} while Date() < deadline

self.restorePasteboardSnapshot(snapshot, to: pasteboard)

guard let copiedText else {
self.diag("Clipboard fallback failed: clipboard did not receive selected text")
return nil
}
func captureSelectedText() -> Bool {
let targetPID = TypingService.captureSystemFocusedPID()
?? NSWorkspace.shared.frontmostApplication?.processIdentifier
if let text = textSelectionService.getSelectedText(), !text.isEmpty {
.outputDelivered,
properties: [
"mode": AnalyticsMode.rewrite.rawValue,
"method": AnalyticsOutputMethod.typed.rawValue,
self.asr.typeTextToActiveField(
self.rewriteModeService.rewrittenText,
preferredTargetPID: typingTarget.pid
self.rewriteModeService.acceptRewrite(

@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: 314f9c7e81

ℹ️ 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 +371 to +373
if self.shouldSyncCommandNotchState {
NotchContentState.shared.addCommandMessage(role: result.success ? .assistant : .status, content: result.message)
NotchContentState.shared.setCommandProcessing(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 Capture notch sync state for Codex cleanup

When a Codex notch handoff starts, the earlier block can set isCommandProcessing to true, but this completion block recomputes shouldSyncCommandNotchState after the async Codex run. If the user toggles Codex routing off or changes the handoff style while Codex is still running, cleanup is skipped and the expanded notch can remain stuck in a processing/disabled state even though isProcessing is false; capture the initial sync decision or always clear command processing on completion.

Useful? React with 👍 / 👎.

@tarushvkodes
tarushvkodes force-pushed the codex/lower-visualizer-threshold branch from 314f9c7 to 8258e3c Compare July 6, 2026 14:01
@tarushvkodes

Copy link
Copy Markdown
Author

Update: rebased this branch onto upstream main at v1.6.2 (941eedb) and reinstalled a fresh local build.

Notes:

  • Resolved the visualizer threshold conflict by keeping upstream's new private-AI settings constants plus the 0.12 waveform default.
  • Resolved the TypingService conflict by keeping upstream's DictationLiteralOutputPlan path and layering Edit Mode's force-reliable-paste path on top.
  • Preserved the visualizer threshold, Codex command mode, notch output, and Google Docs/Edit Mode replacement changes.

Validated locally:

  • swiftc parse over Sources/Fluid Swift files
  • git diff --check
  • plutil lint for project/Info/entitlements
  • xcodebuild clean build for macOS arm64
  • installed and ad-hoc signed /Applications/FluidVoice.app

@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: 8258e3c366

ℹ️ 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 +135 to +137
process.terminationHandler = { _ in
resume(true)
}

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 Check for already-exited Codex processes

When the Codex CLI exits before this handler is installed (for example because the flags are invalid, auth/config fails, or the executable returns immediately), setting terminationHandler after the fact does not resume the continuation. In that scenario the UI waits for the full 120-second timeout and reports a timeout instead of the real Codex failure; check process.isRunning/terminationStatus before waiting or install the handler before starting the process.

Useful? React with 👍 / 👎.


keyDown.flags = .maskCommand
keyUp.flags = .maskCommand
keyDown.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.

P2 Badge Verify Codex focus before pasting

In app handoff, these events are posted globally after only a fixed 250 ms activation delay. On a cold launch or slow activation where Codex is not yet frontmost/focused, Cmd+V and Return go to whichever app still owns focus, leaking/submitting the command text there while this path still reports success; wait until the Codex app/input is actually focused or post the events to the Codex PID.

Useful? React with 👍 / 👎.

@tarushvkodes

Copy link
Copy Markdown
Author

Update: added one more small Google Docs/Edit Mode guard.

When Right Option is already active as a FluidVoice modifier-only hotkey, macOS printable Option chords like Option+R can otherwise leak through to web editors as characters such as ® if the chord misses the exact shortcut route. The new guard only applies while the physical Right Option key is the active FluidVoice modifier-only shortcut, so normal left-Option text input remains untouched.

Validated locally with swiftc parse, git diff --check, xcodebuild macOS arm64 build, and a freshly installed/ad-hoc signed /Applications/FluidVoice.app.

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

ℹ️ 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 +194 to +195
guard let copyDown = CGEvent(keyboardEventSource: nil, virtualKey: 8, keyDown: true),
let copyUp = CGEvent(keyboardEventSource: nil, virtualKey: 8, keyDown: 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 Resolve Copy shortcut using the active keyboard layout

When AX selected-text lookup fails, this fallback now relies on a hard-coded US keyboard key code for Cmd+C. On non-QWERTY layouts that key code can generate a different shortcut, so the clipboard fallback never captures the selection and Edit Mode incorrectly starts without context; resolve the key code from the current layout before posting.

Useful? React with 👍 / 👎.

Comment on lines +195 to +196
try? await Task.sleep(nanoseconds: 1_000_000_000)
self.restorePasteboardSnapshot(snapshot, to: pasteboard)

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 clipboard changes during Codex app handoff

When using App handoff, this sleeps for a full second after submitting and then unconditionally restores the pasteboard snapshot. If the user or Codex copies anything during that second, the new clipboard contents are overwritten by FluidVoice's pre-handoff clipboard; check the pasteboard change count/current string before restoring as the typing paste path does.

Useful? React with 👍 / 👎.

@tarushvkodes
tarushvkodes force-pushed the codex/lower-visualizer-threshold branch from e44aea0 to 59dc5cd Compare July 6, 2026 14:32
@tarushvkodes

Copy link
Copy Markdown
Author

Follow-up correction: tightened the Right Option / Edit Mode fix after live Google Docs testing.

The final version preserves automatic dictation behavior for Right Option alone. It only prioritizes the configured Edit Mode chord when physical Right Option is already down and the chord matches the Edit Mode shortcut, consuming the paired key-down/key-up before the web editor can receive printable Option text like ®. This keeps the user's push-to-talk/tap-to-continue dictation flow unchanged while preventing Option+R from replacing selected Google Docs text with a literal character.

Validated again locally with swiftc parse, git diff --check, xcodebuild macOS arm64 build, and installed/ad-hoc signed /Applications/FluidVoice.app.

@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: 59dc5cd199

ℹ️ 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 +738 to +745
if self.shouldPrioritizeRightOptionRewriteShortcut(keyCode: keyCode, modifiers: eventModifiers) {
self.consumeRightOptionTextInputKey(keyCode)
DebugLogger.shared.info(
"Right Option edit shortcut consumed before text input",
source: "GlobalHotkeyManager"
)
self.handleRewriteModeKeyDown()
return nil

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 Mark consumed Right Option chords as interruptions

When the primary shortcut is the Right Option modifier-only key and hotkey mode is Toggle, this early return fires before markOtherInputDuringModifierOnly(), so the modifier press remains clean. Pressing Right Option plus the rewrite shortcut triggers Edit mode here, but releasing Right Option then also runs the modifier-only toggle path and starts/stops dictation; the generic suppression branch below has the same clean-press problem for Option text characters. Mark the modifier-only press interrupted before consuming the key.

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: b84bd4ec8e

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


private static let codexBundleID = "com.openai.codex"
private static let pasteboardSessionSemaphore = DispatchSemaphore(value: 1)
private static let bundledCodexCLIPath = "/Applications/Codex.app/Contents/Resources/codex"

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 Resolve Codex CLI from the installed app

When Codex.app is installed outside /Applications (for example in ~/Applications or after the user moves/renames the bundle), App handoff can still locate it by bundle ID, but Notch handoff will fail this fixed path check and report “Codex CLI was not found.” Resolve the app URL via Launch Services/NSWorkspace and append Contents/Resources/codex instead of pinning /Applications.

Useful? React with 👍 / 👎.


private static func keyCode(for character: Character) -> CGKeyCode? {
switch character.lowercased() {
case "v": return 9

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 Resolve paste shortcut from active layout

When App handoff runs on non-QWERTY keyboard layouts, this maps paste to the fixed ANSI V key even though the rest of the typing pipeline resolves the paste key from the current layout. On those layouts Cmd+V can be delivered as a different key equivalent, so pasteAndSubmit can return success after pressing Return while Codex receives no pasted prompt; use the active-layout lookup or the existing reliable paste helper before posting the shortcut.

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: 35ce89241a

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

}

if let data = prompt.data(using: .utf8) {
inputPipe.fileHandleForWriting.write(data)

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 Guard Codex stdin writes after early exits

When the bundled Codex CLI exits immediately during Notch handoff (for example from bad flags or missing auth/config), the pipe's write end can already be closed here; FileHandle.write(_:) on a closed pipe terminates the app instead of returning a HandoffResult. Check process.isRunning/use a throwing write path or feed stdin from a file so early Codex failures are reported rather than crashing FluidVoice.

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: 6470ab371b

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


self.currentStatus = "Creating timestamped subtitles..."
self.progress = 0.5
let segments = try await whisperProvider.transcribeTimed(samples)

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 Keep video subtitle transcription chunked

When a Whisper-backed video is longer than the model's safe window, this new video/SRT branch resamples the entire file and sends all samples to transcribeTimed in one request, bypassing the 20-minute chunk loop below that was added to avoid model/window and memory limits. Long MP4/MOV meetings can therefore fail, truncate, or OOM even when the existing audio path would process bounded chunks; generate timed segments per chunk and offset the cue timestamps instead.

Useful? React with 👍 / 👎.

if !result.subtitleCues.isEmpty {
HStack(spacing: 10) {
Button {
self.exportFormat = .srt

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 Reset the export format after SRT exports

After a user clicks this SRT-only button, exportFormat remains .srt because the exporter completion only clears exportResult, and the generic export buttons do not set the format back to text. The next time the user clicks the normal export icon on a transcript without subtitleCues, TranscriptionDocument still calls exportToSRT and the export fails; reset the format after SRT export or set .text before opening the generic exporter.

Useful? React with 👍 / 👎.

Comment on lines +572 to +579
for segment in timedSegments {
guard let speakerID = self.bestSpeakerID(
forStart: segment.startSeconds,
end: segment.endSeconds,
diarization: speakerSegments
) else {
continue
}

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 Silent transcript data loss in Whisper + diarization path

When bestSpeakerID returns nil — which happens whenever a Whisper timed segment falls in a gap between labeled diarizer windows (common at the very start/end of a file, during overlapping speakers, or in brief pauses between turns) — the entire Whisper segment is silently skipped. The function still returns .success with no counter, warning, or partial-result note in the output text. The only safeguard is guard !paragraphs.isEmpty, which only throws when all segments were dropped; any partial drop reaches the caller as a complete transcript. A user whose recording has even a short unmatched region at the edges will get a silently truncated result.

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/Fluid/Services/MeetingTranscriptionService.swift
Line: 572-579

Comment:
**Silent transcript data loss in Whisper + diarization path**

When `bestSpeakerID` returns `nil` — which happens whenever a Whisper timed segment falls in a gap between labeled diarizer windows (common at the very start/end of a file, during overlapping speakers, or in brief pauses between turns) — the entire Whisper segment is silently skipped. The function still returns `.success` with no counter, warning, or partial-result note in the output text. The only safeguard is `guard !paragraphs.isEmpty`, which only throws when *all* segments were dropped; any partial drop reaches the caller as a complete transcript. A user whose recording has even a short unmatched region at the edges will get a silently truncated result.

How can I resolve this? If you propose a fix, please make it concise.

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: e6260e768d

ℹ️ 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 +76 to +80
process.arguments = [
"-a", "never",
"exec",
"--skip-git-repo-check",
"--color", "never",

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 Give Codex exec a writable sandbox

When Notch handoff is used for commands that need to modify files or run outside read-only constraints, these arguments never set --sandbox; I checked the Codex non-interactive docs, which state codex exec defaults to a read-only sandbox and recommend --sandbox workspace-write for edits. Combined with -a never, default installs cannot escalate for ordinary Command Mode requests like creating or updating files, so the new Notch route reports a Codex error instead of performing the action.

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: f9b4a8cf14

ℹ️ 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 +217 to +219
if self.overriddenModelDirectory == nil {
try WhisperModelStorage.migrateLegacyCacheIfNeeded()
}

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 Clear legacy models when clearing cache

When an existing installation is updated and the user clears the model cache before the next prepare, this migration has not run yet, while clearCache() only deletes from the new Application Support directory. The old ~/Library/Caches/.../WhisperModels files therefore remain (potentially retaining a large Whisper download) even though the UI reports the cache cleared; have the clear path also remove the legacy cache directory.

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: fbda4441c2

ℹ️ 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 +493 to 495
let needsRegularNotchCleanup = self.notch != nil
if needsRegularNotchCleanup {
self.hide()

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 Codex processing while replacing the regular notch

When a Codex notch handoff starts while the regular recording notch is visible, processCodexHandoff sets command processing to true and then opens this expanded output. Calling hide() here runs performHideAndWait, which unconditionally calls NotchContentState.shared.setProcessing(false) before the expanded notch appears. The Codex run is still active, but its spinner/disabled input state is cleared, so a user can submit another command and start a concurrent handoff; preserve command processing across this regular-notch cleanup (or restore it immediately afterward).

Useful? React with 👍 / 👎.

…r-threshold

# Conflicts:
#	Sources/Fluid/Services/TypingService.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: a23919648e

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

return utType.preferredFilenameExtension
}
return Set(extensions)
return Set(extensions).union(["mkv"])

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 Route MKV audio through FFmpeg for every model

When speaker identification is off and the selected model is not Whisper (for example the default Parakeet model), the MKV-specific decoder is bypassed and execution falls through to AVAudioFile(forReading:). MKV was manually added because it is not among AVFoundation's decodable types, so these accepted files fail even when FFmpeg is installed; use the extracted WAV for the normal chunked provider path as well.

Useful? React with 👍 / 👎.

Comment on lines +828 to +829
try process.run()
process.waitUntilExit()

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 Run FFmpeg extraction off the main actor

For an MKV that reaches decodeAudioSamples, this synchronous wait executes directly from the @MainActor transcription flow; declaring the helper nonisolated does not move the call to a background executor. Extracting a long recording can therefore freeze the entire app, including progress updates and cancellation, until FFmpeg exits.

Useful? React with 👍 / 👎.

Comment on lines +384 to +386
if SettingsStore.shared.commandModeRouteToCodex {
await self.processCodexHandoff(text)
return

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 Codex context for notch follow-ups

When Codex routing uses the Notch style, a follow-up is sent through processCodexHandoff exactly like a new request. runCodexInNotch starts a fresh codex exec process containing only that follow-up text and passes neither the displayed conversation history nor a prior session identifier, so contextual inputs such as “now shorten that” cannot refer to the preceding result even though they are submitted through the follow-up UI.

Useful? React with 👍 / 👎.

…r-threshold

# Conflicts:
#	Sources/Fluid/Persistence/FileTranscriptionHistoryStore.swift
#	Sources/Fluid/Services/CommandModeService.swift
#	Sources/Fluid/Services/MeetingTranscriptionService.swift
#	Sources/Fluid/Services/RewriteModeService.swift
#	Sources/Fluid/Services/TypingService.swift
#	Sources/Fluid/Services/WhisperProvider.swift
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean.

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

ℹ️ 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 +180 to +181
usleep(15_000)
} while Date() < deadline

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 Move clipboard polling off the main actor

When Edit Mode cannot read a selection through Accessibility—especially when no text is selected in a browser editor—RewriteModeService.captureSelectedText() invokes this synchronous polling loop from the main actor. The repeated usleep calls can block FluidVoice's UI and hotkey handling for the full one-second timeout before Write Mode opens; perform the fallback asynchronously or poll without blocking the main actor.

Useful? React with 👍 / 👎.

guard provider.isReady else {
throw TranscriptionError.modelLoadFailed("Transcription provider not ready")
}
let provider = try await self.provider(for: model)

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 Attribute file usage to the selected model

When the new file-transcription picker selects a model different from the global dictation model, this correctly prepares model, but the recordUsage call below still records SettingsStore.shared.selectedSpeechModel.analyticsDescriptor. Every such transcription is therefore attributed to the wrong model, corrupting per-model meeting usage and experiment results; pass the file-selected model's descriptor instead.

Useful? React with 👍 / 👎.

Comment on lines +191 to +197
Toggle(isOn: self.$speakerDiarizationEnabled) {
Label("Identify speakers", systemImage: "person.2.wave.2")
.font(.subheadline.weight(.medium))
}
.toggleStyle(.switch)
.disabled(self.transcriptionService.isTranscribing)

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 Use a single speaker-identification setting

Once a file is selected on supported hardware, this new “Identify speakers” toggle is displayed alongside the existing “Label speakers” toggle, but they control independent flags and separate branches in transcribeFile. Turning either control off does not disable diarization when the other remains on (the UI initially even shows the new toggle on and the existing toggle off), so users cannot reliably opt out of the model download and expensive speaker-processing path; consolidate both controls onto one setting and pipeline.

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: fc82dac737

ℹ️ 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 +315 to +317
if SettingsStore.shared.commandModeRouteToCodex {
await self.processCodexHandoff(text)
return

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 Record usage before routing commands to Codex

When Codex routing is enabled, this early return bypasses the recordUsage(mode: .command, ...) call below; the equivalent branch in processFollowUpCommand does the same. Consequently, every initial and follow-up Codex command disappears from Command Mode daily totals and core-action activity, undercounting the opted-in cohort. Record the usage before branching, using a Codex descriptor or no AI descriptor.

Useful? React with 👍 / 👎.

Comment on lines +1074 to +1076
process.standardError = errorPipe
try process.run()
process.waitUntilExit()

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 Drain FFmpeg stderr while the process runs

When a malformed MKV causes FFmpeg to emit enough error output to fill the pipe buffer, waitUntilExit() waits for the child while the child waits for this process to drain errorPipe, so transcription hangs indefinitely and never reaches the diagnostic read below. Drain stderr concurrently (or use a file/termination handler) before waiting for process completion.

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.

3 participants