Skip to content

fix: race conditions#672

Open
joshribakoff wants to merge 1 commit intocjpais:mainfrom
joshribakoff:fix/simple-state-machine
Open

fix: race conditions#672
joshribakoff wants to merge 1 commit intocjpais:mainfrom
joshribakoff:fix/simple-state-machine

Conversation

@joshribakoff
Copy link
Contributor

@joshribakoff joshribakoff commented Jan 26, 2026

Before Submitting This PR

Please confirm you have done the following:

Human Written Description

Implements state machine, single source of truth, declarative UI pattern, testing pattern, fixes race conditions at multiple layers. Trades off concurrency for simplicity, laying the foundation for future extensibility (and concurrency). Replaces jQuery style imperative UI management with React style declarative updates.

I am new to Rust and tried to do my best with AI, open to feedback / feel free to close and "steal" the idea but I would love feedback and/or credit!

Related Issues/Discussions

Fixes #641, #462

How to Reproduce

Three ways to trigger / different issue(s):

  1. During transcription: Record something, release hotkey, then while transcribing trigger the hotkey again., global lock solves it
  2. Rapid toggle: Quickly press and release the hotkey multiple times in succession capturing small empty audio packets rapidly, causes "deadlock" (not responding), global lock solves it.
  3. Hold and spam: Hold down the hotkey key rapidly (same as 2) but UI panel disappears but audio cues indicates its still recording (which it is). Global lock does not solve it, required declarative UI.

Testing

Tested locally by triggering all three reproduction scenarios above.

AI Assistance

  • AI was used (please describe below)

If AI was used:

  • Tools used: Claude Code
  • How extensively: Pair programming to implement the global lock and declarative UI sync

The app has multiple sources of state that can get out of sync - different mutex locks at different layers, imperative UI updates with delays that don't get cancelled. This PR demonstrates a pattern that fixes the UI overlay and tray icon: a single source of truth state machine where UI syncs declaratively when the phase changes (similar to React's model).

This also fixes the "Not Responding" freeze by adding a global lock that prevents concurrent operations.

Intentionally pragmatic: I've kept this PR minimal to avoid overwhelming reviewers with huge changes. It focuses on fixing the immediate issue and demonstrating a pattern that could be extended to other parts of the app in future PRs.

Other state could be consolidated into this pattern in follow-up PRs:

  • Audio feedback timing (stop playing one sound, play the latest sound instead, etc)
  • The functionality that mutes the sound on the users system also feels like declarative "UI" state, but is out of scope for this PR too.

In “Rust way” terms: I made state transitions explicit, serialized with a single lock, and made UI a pure projection of state.
That’s consistent with correctness-first design.

I’d pitch it as:

  • The previous decentralized approach lacked a single source of truth for phase, which caused races.
  • The controller is a minimal, scoped state machine with clear transitions.
  • If we want “Rustier” later, we can evolve it into a channel-driven state machine without changing call sites (hopefully with tests and mock services, or something).

@joshribakoff joshribakoff changed the title feat: add pure state machine for operation lifecycle WIP: add pure state machine for operation lifecycle Jan 26, 2026
@joshribakoff joshribakoff force-pushed the fix/simple-state-machine branch from 908bf62 to a10abde Compare January 26, 2026 01:24
@joshribakoff joshribakoff changed the title WIP: add pure state machine for operation lifecycle WIP: Tested pure blocking state machine for operation lifecycle Jan 26, 2026
@joshribakoff joshribakoff changed the title WIP: Tested pure blocking state machine for operation lifecycle feat: GlobalController - global lock (pragmatic fix) Jan 26, 2026
@joshribakoff joshribakoff changed the title feat: GlobalController - global lock (pragmatic fix) fix: race conditions - global lock (pragmatic fix) Jan 26, 2026
@joshribakoff joshribakoff changed the title fix: race conditions - global lock (pragmatic fix) fix: race conditions & testing situation - global lock (pragmatic fix) Jan 26, 2026
@joshribakoff joshribakoff changed the title fix: race conditions & testing situation - global lock (pragmatic fix) fix: race conditions w/ global lock (pragmatic fix) Jan 26, 2026
@joshribakoff joshribakoff force-pushed the fix/simple-state-machine branch 2 times, most recently from ef4881c to 75dee31 Compare January 26, 2026 05:01
joshribakoff added a commit to joshribakoff/Handy that referenced this pull request Jan 26, 2026
Shows how to inject mocks for testing.

Depends on cjpais#672

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@joshribakoff joshribakoff force-pushed the fix/simple-state-machine branch from 75dee31 to 5cb51af Compare January 26, 2026 05:06
joshribakoff added a commit to joshribakoff/Handy that referenced this pull request Jan 26, 2026
Shows how to inject mocks for testing.

Depends on cjpais#672

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@joshribakoff joshribakoff changed the title fix: race conditions w/ global lock (pragmatic fix) fix: race conditions (pragmatic fix) Jan 26, 2026
@joshribakoff joshribakoff changed the title fix: race conditions (pragmatic fix) fix: race conditions Jan 26, 2026
@joshribakoff joshribakoff force-pushed the fix/simple-state-machine branch 2 times, most recently from 6d7e467 to 2c37814 Compare January 26, 2026 05:50
@joshribakoff joshribakoff marked this pull request as ready for review January 26, 2026 06:12
@joshribakoff joshribakoff force-pushed the fix/simple-state-machine branch from 2c37814 to b5f8fba Compare January 26, 2026 06:12
@cjpais
Copy link
Owner

cjpais commented Jan 26, 2026

Thanks for this, this is one of the higher priority issues for me. Will try and review in about a week

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.

[BUG] It's crashing when you accidentally hit push-to-talk twice in a row.

2 participants