Skip to content

Fix login launch (#520), volume spike (#522), add spoken Enter keystr…#524

Open
TheJagStudio wants to merge 1 commit into
altic-dev:mainfrom
TheJagStudio:fluidvoice-issues-520-523-m85yrj
Open

Fix login launch (#520), volume spike (#522), add spoken Enter keystr…#524
TheJagStudio wants to merge 1 commit into
altic-dev:mainfrom
TheJagStudio:fluidvoice-issues-520-523-m85yrj

Conversation

@TheJagStudio

@TheJagStudio TheJagStudio commented Jul 5, 2026

Copy link
Copy Markdown

Description

Addresses three issues in one focused change set (6 files):

#523 – Native Enter keypress emulation via spoken phrase. New opt-in setting "Press Enter by Voice" with a configurable trigger phrase (default press enter; falls back to the default if left empty, so the empty-replacement problem from the dictionary workaround in #380 can't occur). When the transcript contains the phrase, TypingService splits the output into segments and posts a real kVK_Return CGEvent keystroke (targeted at the focused app's PID, HID tap fallback) where the phrase was spoken, instead of pasting a \n. Because it is a genuine keystroke, zsh/bash with bracketed paste execute the command — this works in terminals as well as chat apps. Matching is case-insensitive, swallows punctuation the transcriber attaches around the phrase, handles a transcript consisting only of the phrase, and waits ~300 ms after paste-based insertion so the Return never fires before the text lands. The new settings round-trip through backup export/import.

#522 – Independent volume selection causes system volume spike. TranscriptionSoundPlayer previously always set the system volume to the cue's level and played at full player volume, spiking any other audio. Now, when the desired cue level fits under the current system volume (the common case), the system volume is never touched — loudness is compensated inside the player (playerVolume = desired / systemVolume). Only when the cue is louder than the system volume does it boost, and the boost now ramps in 8 small steps over ~120 ms up and back down instead of jumping. Overlapping sounds no longer clobber the saved pre-boost volume, so restores can't get stuck at a boosted level. Settings footnote updated accordingly.

#520 – Persistent & invisible background launch on system startup. Login-launch detection relied on the legacy keyAELaunchedAsLogInItem Apple Event, which SMAppService login launches on modern macOS often don't carry — so every reboot looked like a manual launch and the main window appeared. Detection now falls back to a heuristic: the app is registered as a login item and the process started within 5 minutes of boot. The default for "Show window when launched at login" is flipped to OFF so login launches start silently in the menu bar (manual launches always show the window), and the LaunchServices reopen fallback now boots the window hidden immediately instead of flashing it until the retry loop catches it.

Type of Change

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

Related Issue or Discussion

Closes #520
Closes #522
Closes #523

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:

Screenshots / Video

This PR does change Settings UI: two new rows ("Press Enter by Voice" toggle + "Trigger phrase" field) and an updated footnote on "Independent Volume".

image

Note: SettingsStore.swift is flagged by the policy check but contains only persistence changes (new UserDefaults keys, backup fields, a default-value flip) — no visual code. The visual changes are the SettingsView.swift rows shown above.

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

Notes

  • The showMainWindowAtLoginLaunch default flip means existing installs that never touched the toggle will now start silently at login (per [BUG] Persistent & invisible Background Launch on System Startup #520's expected behavior); users can re-enable the window in App Settings.
  • The login-launch heuristic (SMAppService enabled + uptime < 300 s) can classify a manual launch made within 5 minutes of boot as a login launch; the trade-off favors never flashing the window on reboot.
  • "Press Enter by Voice" is off by default, so no behavior changes unless a user opts in.

…poken Enter keystroke (altic-dev#523)

- Silent background launch at login: robust SMAppService login-launch
  detection, silent default, no window flash on reopen fallback (altic-dev#520)
- Independent volume: compensate in-player when possible; ramp system
  volume smoothly only when a boost is needed (altic-dev#522)
- Press Enter by Voice: opt-in spoken trigger phrase posts a real
  kVK_Return keystroke that also submits in terminals (altic-dev#523)
Copilot AI review requested due to automatic review settings July 5, 2026 00:10
@github-actions github-actions Bot added the needs screenshots Pull request needs screenshot or video evidence. label Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 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.

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 bundles three user-facing improvements: silent/background behavior for login-item launches, a fix to avoid sudden system volume spikes during transcription cue playback, and an opt-in “Press Enter by Voice” feature that emits a native Return keystroke when a trigger phrase is spoken.

Changes:

  • Adds spoken-phrase detection in TypingService that splits dictated text into segments and injects native Return key events at the phrase location.
  • Reworks transcription cue volume handling to avoid touching system volume in the common case and to ramp volume smoothly when a boost is required.
  • Improves login-launch detection for SMAppService-based login items and changes the default to not show the main window on login launches.

Reviewed changes

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

Show a summary per file
File Description
Sources/Fluid/UI/SettingsView.swift Adds Settings UI for “Press Enter by Voice” and updates the Independent Volume footnote.
Sources/Fluid/Services/TypingService.swift Implements transcript splitting + native Return injection for the spoken trigger phrase.
Sources/Fluid/Services/TranscriptionSoundPlayer.swift Adjusts cue playback to avoid system-volume spikes and adds smooth ramping for boosts.
Sources/Fluid/Persistence/SettingsStore.swift Adds new settings (toggle + trigger phrase), includes them in backup round-trip, and flips the login-window default.
Sources/Fluid/Persistence/BackupService.swift Extends backup payload schema with the new “Press Enter by Voice” fields.
Sources/Fluid/AppDelegate.swift Adds SMAppService + uptime heuristic for login-launch detection and suppresses window flash on reopen fallback.

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

Comment on lines +279 to +292
for match in matches {
let before = nsText
.substring(with: NSRange(location: cursor, length: match.range.location - cursor))
.trimmingCharacters(in: .whitespacesAndNewlines)
if !before.isEmpty {
segments.append(.text(before))
}
segments.append(.pressEnter)
cursor = match.range.location + match.range.length
}
let tail = nsText.substring(from: cursor).trimmingCharacters(in: .whitespacesAndNewlines)
if !tail.isEmpty {
segments.append(.text(tail))
}
Comment on lines 99 to 103
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.05) { [weak self] in
Self.setSystemVolume(saved)
self?.savedSystemVolume = nil
guard let self, let saved = self.savedSystemVolume else { return }
Self.rampSystemVolume(to: saved)
self.savedSystemVolume = nil
}
Comment on lines +128 to +132
let start = self.getSystemVolume()
guard abs(start - target) > 0.001 else { return }

let stepDelayMicros = useconds_t((duration / Double(steps)) * 1_000_000)
DispatchQueue.global(qos: .userInteractive).async {

@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: 0a8ba5865d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +281 to +282
.substring(with: NSRange(location: cursor, length: match.range.location - cursor))
.trimmingCharacters(in: .whitespacesAndNewlines)

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 spacing added for continuous dictation

When the spoken Enter feature is used together with "Space Between Dictations", ASRService.applyContinuousDictationFormatting can intentionally prepend a space before this method sees the text. Trimming every text segment here removes that space whenever the trigger phrase is present, so dictating bar press enter after existing text foo inserts bar as foobar before sending Return instead of preserving the separator.

Useful? React with 👍 / 👎.

Comment on lines +101 to +102
Self.rampSystemVolume(to: saved)
self.savedSystemVolume = 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 Keep the saved volume until the restore ramp finishes

When Independent Volume needs a boost, rampSystemVolume restores asynchronously over several steps, but savedSystemVolume is cleared immediately. If another boosted cue starts during that restore ramp (for example repeated Settings previews), it records the intermediate system volume as the new baseline and later restores to that raised value instead of the user's original volume, leaving output volume stuck above where it started.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot removed the needs screenshots Pull request needs screenshot or video evidence. label Jul 5, 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

2 participants