You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
T3 Code has no voice input today — there is no way to dictate a prompt or attach a voice note on any surface (web, mobile, desktop). This issue proposes adding push-to-talk voice prompting, transcribed to text and dropped into the composer.
Motivation: the primary phone workflow here is the web app over the Tailscale HTTPS origin (see #19). Typing a long prompt on a phone keyboard is the main friction point in that flow; speaking it is not.
Current state (verified against main)
There is no microphone capture, speech-to-text, or audio-attachment path anywhere in the tree. What exists is audio-adjacent but unrelated:
Thing
What it actually is
apps/web/src/browser/browserRecording.ts
MediaRecorder over canvas.captureStream(12) — video capture of the browser-preview tool. No audio track, no getUserMedia.
thread/realtime/* in CodexAdapter.ts:1163-1241, CodexSessionRuntime.ts:530-537
Pass-through plumbing for the Codex app-server protocol's realtime events (outputAudio/delta, transcript/delta, sdp). Translated to canonical thread.realtime.* events in packages/contracts/src/providerRuntime.ts:157-161 — but no client subscribes to them and there is no RPC to start a realtime session. Dead-ends at the contract boundary.
NSMicrophoneUsageDescription in the built .app Info.plist
Electron packaging boilerplate, not a T3 feature.
"VoiceOver" mentions
Accessibility labels.
Composer affordances today are attachments / line-break / model / config / stop — no mic button (apps/mobile/src/features/threads/ThreadComposer.tsx:881-916).
Upstream context — this has to be fork-owned
Upstream pingdotgg/t3code#653 "[Discussion] Voice support" was closed as NOT PLANNED (2026-03-15), despite a working demo PR (local NVIDIA Parakeet 0.6B v2 STT + Rust sidecar). Maintainer response: "out of scope, there are apps that do this already." Commenters asked for it to be opt-in if it ever landed.
So there is no upstream implementation to wait for or rebase onto — if we want it, the fork builds and maintains it.
Related upstream issues, still open, all output-side rather than input-side:
#728 — macOS TCC mic/camera permissions for apps launched from the integrated terminal (relevant if capture ever runs desktop-side)
Proposed scope (v1)
Surface first: web, because that is the phone-over-Tailscale path.
Mic button in the composer footer, push-to-talk (hold to record) plus a tap-to-toggle mode.
Capture via getUserMedia + MediaRecorder (opus/webm). Secure-context requirement is already satisfied by the Tailscale HTTPS origin.
Transcribe, then insert the text into the composer for review — do not auto-send. Dictation errors on a coding prompt are expensive.
Opt-in: off by default, enabled from a settings row (mirror the notifyOnNeedsInput pattern in SettingsPanels.tsx).
Visible recording state + level meter, and a hard cap on clip length.
Deliberately out of scope for v1: TTS/read-aloud, realtime bidirectional voice sessions, storing the audio as a durable "voice note" artifact, and mobile-native capture. Mobile app parity is a follow-up once the web shape is settled.
Open decisions
Where transcription runs. Options: (a) server-side call to a cloud STT model — simplest, best accuracy, needs a key and sends audio off-box; (b) on-device SpeechRecognition Web Speech API — free, but effectively unusable on iOS Safari, which is the target device; (c) local model + sidecar, as the upstream demo did — no network, but ships a binary and burns CPU/RAM. Leaning (a) for v1, behind an opt-in setting, with the provider abstracted so (c) can land later.
Whether audio is retained at all after transcription, or discarded immediately. Retaining turns this into a true "voice note" feature and pulls in persistence + a contracts change; discarding keeps it a dictation feature. Leaning discard for v1.
The composer is one of the hottest fork seams we have — docs/t3x/SEAMS.md already carries rows for ComposerPrimaryActions.tsx (risk 276), ChatComposer.tsx (612), ChatView.tsx (9540), and ThreadComposer.tsx (480), and the outbox work already drifted against an upstream gate once.
Per the ledger's tripwire rule: put the capture/transcription logic in fork-owned files registered through apps/server/src/t3x/index.ts and the per-surface aggregator. Budget for exactly one new upstream edit — the mic button mount point in the composer footer — and update the SEAMS.md row and header totals in the same commit that adds it.
Summary
T3 Code has no voice input today — there is no way to dictate a prompt or attach a voice note on any surface (web, mobile, desktop). This issue proposes adding push-to-talk voice prompting, transcribed to text and dropped into the composer.
Motivation: the primary phone workflow here is the web app over the Tailscale HTTPS origin (see #19). Typing a long prompt on a phone keyboard is the main friction point in that flow; speaking it is not.
Current state (verified against
main)There is no microphone capture, speech-to-text, or audio-attachment path anywhere in the tree. What exists is audio-adjacent but unrelated:
apps/web/src/browser/browserRecording.tsMediaRecorderovercanvas.captureStream(12)— video capture of the browser-preview tool. No audio track, nogetUserMedia.thread/realtime/*inCodexAdapter.ts:1163-1241,CodexSessionRuntime.ts:530-537outputAudio/delta,transcript/delta,sdp). Translated to canonicalthread.realtime.*events inpackages/contracts/src/providerRuntime.ts:157-161— but no client subscribes to them and there is no RPC to start a realtime session. Dead-ends at the contract boundary.NSMicrophoneUsageDescriptionin the built.appInfo.plistComposer affordances today are attachments / line-break / model / config / stop — no mic button (
apps/mobile/src/features/threads/ThreadComposer.tsx:881-916).Upstream context — this has to be fork-owned
Upstream pingdotgg/t3code#653 "[Discussion] Voice support" was closed as NOT PLANNED (2026-03-15), despite a working demo PR (local NVIDIA Parakeet 0.6B v2 STT + Rust sidecar). Maintainer response: "out of scope, there are apps that do this already." Commenters asked for it to be opt-in if it ever landed.
So there is no upstream implementation to wait for or rebase onto — if we want it, the fork builds and maintains it.
Related upstream issues, still open, all output-side rather than input-side:
Proposed scope (v1)
Surface first: web, because that is the phone-over-Tailscale path.
getUserMedia+MediaRecorder(opus/webm). Secure-context requirement is already satisfied by the Tailscale HTTPS origin.notifyOnNeedsInputpattern inSettingsPanels.tsx).Deliberately out of scope for v1: TTS/read-aloud, realtime bidirectional voice sessions, storing the audio as a durable "voice note" artifact, and mobile-native capture. Mobile app parity is a follow-up once the web shape is settled.
Open decisions
SpeechRecognitionWeb Speech API — free, but effectively unusable on iOS Safari, which is the target device; (c) local model + sidecar, as the upstream demo did — no network, but ships a binary and burns CPU/RAM. Leaning (a) for v1, behind an opt-in setting, with the provider abstracted so (c) can land later.Seam discipline
The composer is one of the hottest fork seams we have —
docs/t3x/SEAMS.mdalready carries rows forComposerPrimaryActions.tsx(risk 276),ChatComposer.tsx(612),ChatView.tsx(9540), andThreadComposer.tsx(480), and the outbox work already drifted against an upstream gate once.Per the ledger's tripwire rule: put the capture/transcription logic in fork-owned files registered through
apps/server/src/t3x/index.tsand the per-surface aggregator. Budget for exactly one new upstream edit — the mic button mount point in the composer footer — and update theSEAMS.mdrow and header totals in the same commit that adds it.