Skip to content

Let people choose their push-to-talk key - #188

Open
HamptonMakes wants to merge 2 commits into
mainfrom
hampton/audio-recording-hotkey-242ef4
Open

Let people choose their push-to-talk key#188
HamptonMakes wants to merge 2 commits into
mainfrom
hampton/audio-recording-hotkey-242ef4

Conversation

@HamptonMakes

Copy link
Copy Markdown
Collaborator

Push-to-talk was Shift, for everyone, forever. That's a key people are already pressing all day, which is why the gesture needed a 350ms hold delay and a selection check to tell talking from typing a capital — and why it couldn't work at all for anyone whose browser or OS already spends the key.

It's now a setting.

What changed

Settings → Hold to talk — a segmented control: Ctrl+Space (new default), Shift, Option / Alt, Off. Saves on click, no save button, same shape as the Theme row. Choosing Ctrl+Space reveals a note that macOS uses it to switch input sources: if the OS eats the key the browser never sees it, and a shortcut that silently does nothing is worse than one you were warned about.

Two kinds of gesture, on purpose. A chord is nobody's accident, so it records from the press — no hold delay to sit out, no selection check, nothing to guess. A bare modifier keeps every one of the old guards, because the ambiguity that motivated them is real. off is a supported answer, not a broken state: the mic button stays and the keyboard belongs to the page again.

Existing users keep Shift. A data migration writes it into their metadata, so nobody's hands relearn anything on deploy. From here, "no preference recorded" means "arrived after the setting existed" and reads as Ctrl+Space.

The mic names its own key — a hover tooltip and the aria-label. With the shortcut configurable, the button is the only thing on the page that can tell you which one yours is. The server renders a platform-neutral version; the controller rewrites it knowing whether the key in front of this person says ⌥ Option or Alt. It's suppressed mid-take, when the button is a stop button and the status text is already talking.

Also folds the theme switcher's CSS into a shared .segmented control rather than growing a second copy of it — hence the one-line touch in nav_stimulus_spec.rb.

Notes for review

  • db/schema.rb has only its version line bumped, by hand. The migration is data-only, and a real dump off this dev database picked up columns from other unmerged branches (wake_url, behavior) that don't belong here.
  • The engine migration is copied into the host as db/migrate/20260820195225_..._.co_plan.rb, per the usual co_plan:install:migrations flow. It's idempotent — only users with no voice_hotkey recorded are touched.
  • New coverage: the chord gesture end to end, each half of it alone doing nothing, the hotkey turned off, the tooltip naming each key, and the settings endpoint (including that an unknown key is ignored rather than stored). The existing Shift specs now set the preference explicitly, which is also a check that the non-default path still works.

Testing

Full suite green: 1577 examples, 0 failures. Also driven in a browser — the settings row, the live save, and the tooltip were checked by hand.

🤖 Generated with Claude Code

Push-to-talk was Shift, for everyone, forever. Shift is a key people are
already pressing all day, which is why the gesture needed a 350ms hold
delay and a selection check to tell talking from typing a capital — and
why it still couldn't work at all for anyone whose browser or OS already
spends the key.

It's now a setting: Ctrl+Space (the new default), Shift, Option/Alt, or
off. Presets rather than a free capture, because each one behaves
differently on purpose. A chord is nobody's accident, so it records from
the press — no delay to sit out and nothing to guess. A bare modifier
keeps all the old hedging, because the ambiguity is real.

Everyone who was already here is written down as a Shift user by a
backfill, so no hands have to relearn anything on deploy; "no preference
recorded" now means "arrived after the setting existed".

The mic names its own key on hover and to a screen reader — with the
shortcut configurable, the button is the only thing on the page that can
say which one yours is. The server renders it; the controller rewrites it
knowing whether that key says Option or Alt on this machine.

Also folds the theme switcher's CSS into a shared .segmented control
rather than growing a second copy of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

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

ℹ️ 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 engine/app/javascript/controllers/coplan/voice_hotkey_controller.js Outdated
Comment thread engine/app/assets/stylesheets/coplan/application.css
Two things from review.

The PATCH was fire-and-forget: a refused or dropped save left the page
lit up on a key the server never took, discovered only on the next visit
when push-to-talk silently wasn't what the settings said. The click stays
the feedback — no spinner narrating a round trip that takes milliseconds
— but a failure now puts the previous choice back and says so.

And the picker didn't fit a phone. Its no-shrink rule kept the strip at
full width, which crushed the explanation into one word per line and ran
the last option off the screen. Below 640px the row stacks and the strip
takes the width; segments size to content rather than splitting it evenly,
because "Ctrl+Space" has nowhere to break and an equal quarter clips it;
below about 350px the strip wraps to a second line rather than hiding
"Off" off the edge. Fixes the Theme row at those widths too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant