← All posts
5 min read

Hush: a failed experiment to mute Discord while I dictate (and my first Electron app)

I dictate a lot. Wispr Flow has quietly become the way I write half of everything — messages, commit bodies, notes, the first draft of posts like this one. Hold a key, talk, and clean text appears wherever my cursor is. It's great.

Until you do it on a Discord call.

This is the story of a small tool I built to fix that — and why it didn't work. Spoiler: it was still one of the more fun things I've built, and it taught me exactly where the wall is.

The problem: dictating out loud, on an open mic

Here's the scene. I'm pair-programming or in a casual Discord voice channel, and I want to fire off a quick message with Wispr. So I start dictating — out loud, because that's how dictation works — and the entire call hears me narrate my own sentence, punctuation and all. Everyone gets a live feed of "hey can you check the PR comma when you get a sec question mark."

The fix is obvious and annoying: mute Discord, dictate, then unmute. Four actions, every single time, in the right order. Miss one and you either broadcast your dictation or come back from a message muted and wonder why nobody can hear you. I did both, repeatedly.

I wanted one key. Press and hold: Discord goes muted and Wispr starts listening. Release: Wispr stops and Discord comes back. Push-to-talk, but for not talking to the room.

Enter Hush

Hush is a tiny macOS menu-bar app meant to do exactly that. One configurable trigger key wires two other apps together:

  • On press → tap Discord's mute shortcut, then start Wispr dictation.
  • On release → stop Wispr dictation, then tap Discord's unmute shortcut.

No dock icon, a tray glyph that flips between a safe (muted) and a live (mic open) state, and everything configurable from a little settings window. That was the plan.

My first Electron project

This was my first time actually building with Electron, and honestly? It was a great time. I write React Native all day, so a desktop runtime that's "just" Chromium plus Node felt refreshingly direct: a main process for the tray, IPC and native bits, a plain HTML/CSS/JS window for settings, and a preload bridge in between. I had a menu-bar app running in an afternoon.

The fun part was everything around the browser window — the native OS stuff you don't touch in a mobile framework:

  • Listen to the keyboard globally, even when no window is focused, with uiohook-napi.
  • Synthesize keystrokes into other apps — Discord's and Wispr's hotkeys — with @nut-tree-fork/nut-js.
  • Ask macOS for Accessibility and Input Monitoring permissions, and send the user straight to the right System Settings pane.

The hard bugs — and then the wall

A tool this small has no right to be as tricky as it was. Several problems came from the same root: the app both listens to the keyboard and types on the keyboard. Two of them I actually solved:

  • It hears itself. uiohook is a global listener, so it observes the very keystrokes nut-js injects. When Hush synthesized Discord's combo, its own listener saw those keys and flapped mute on and off forever. Fixed with a small SynthGuard that marks the injection window so self-generated events get dropped.
  • Held modifiers leak. If your trigger has modifiers, you're physically holding them while Hush injects, so they bleed into every synthetic chord and the target combo never matches. Fixed by synth-releasing the trigger's modifiers first.

And then I hit the wall that ended the project:

Discord accepts a real, physical keypress for its mute hotkey — but it ignores a synthesized one.

Wispr Flow happily reacted to injected keys. Discord did not. Its global-shortcut capture treats an OS-injected key event differently from a real one, so no matter how clean my combo was, the mute never actually fired. The whole premise of Hush — "let a background app press Discord's hotkey for you" — simply isn't something Discord allows. That was the "Discord ne se mute jamais" bug, and it turned out not to be a bug in my code at all. It was the design.

So it's a fail — but a useful one

I could have brute-forced around it (drive Discord's actual mute state through another channel), but at that point I'm fighting the app instead of using a shortcut, and the whole appeal — small, dumb, one key — is gone. So Hush goes in the graveyard. Honest label: failed experiment.

What I don't regret:

  • It was my first Electron app, start to finish, and I'd reach for Electron again without hesitating for a small native-ish utility.
  • The interesting parts — the global listener, the self-observation guard, the combo math — are all unit-tested against fake engines, and I learned a surprising amount about how macOS routes and distinguishes input events.
  • I now know exactly where synthesized input stops working: it's per-app, and some apps deliberately refuse it.

And here's the part that keeps the idea alive: the pattern is sound for any app that does accept intercepted shortcuts. Wispr did. Plenty of tools do. A "press one key, orchestrate two other apps' hotkeys" bridge is genuinely useful — a mute-while-you-dictate, a start-recording-and-hide-notifications, a launch-this-and-focus-that. Discord was just the one target that says no. Point Hush at a cooperative app and the exact same 1,000 lines work.

Hush didn't ship, but I'd still build this shape again. If you know a couple of apps whose global shortcuts do honor synthesized keys, one trigger key can choreograph them beautifully. Grab the RSS feed — I write up the wins and the fails.

Sometimes the best thing a weekend project gives you isn't the tool — it's a very precise map of where the wall is.