Dialogue · Design notes · Push-to-talk
← Design notes

Push-to-talk for the composer

Option 1 is the composer we shipped — Gemini's layout in our palette, with the mic running live dictation. Below it are the five alternatives we weighed. One thing shaped the call: the panel reads text, so a voice clip must be transcribed before anyone can answer, while live dictation drops straight into the prompt. Patterns are tagged by what they hand the panel — → audio clip or → text.

These are interactive mock-ups — no microphone is used. The recording state, waveforms, timers and playback are faked with CSS + timers so you can feel each gesture. Try them on desktop and on a phone.
1

Gemini-style composer

layout: Gemini→ textshipped

The layout we shipped — Gemini's clean single pill, in our palette: a leading +, the text field, then a quiet mic and a filled ↑ send circle. The mic runs the live dictation (#4 below) — tap to speak, words land as editable text; the round arrow sends. Try it:

Message the panel… listening…

Why this layout: one pill, thumb-reachable mic + send on the right, room to grow vertically as you dictate or type. The + is the natural home for attach / cast actions later. Mic = dictation (editable before send), not a voice clip.

2

Hold-to-talk

WhatsApp · WeChat→ audio clipalternative considered

Press and hold the mic to record; release to send. Slide left to cancel. The classic one-handed gesture — fastest for a quick clip.

Message the panel… 0:00
‹ slide to cancel

Fit: instant for short messages, no second tap. Costs: you must keep a finger down the whole time, accidental fires are common, and a long message is awkward to hold through.

3

Tap to toggle — hands-free

Telegram · Messenger→ audio clipalternative considered

Tap the mic to start; it stays recording with no finger held. A bar shows a live waveform + timer with cancel and send. Tap send to finish. (WhatsApp folds this in as "slide up to lock".)

Message the panel…

Fit: comfortable for longer messages and pausing to think. Costs: it's easy to leave a recording running, and you commit before hearing it back.

4

Live dictation → editable text

iOS · ChatGPT→ textshipped — the mic in option 1

Tap the mic and speak; words land in the composer as text you can edit before sending. No clip — it's the same as typing, just by voice.

Message the panel… listening…

Fit — the natural one for this app: the panel needs text, and you get to fix a fumbled sentence before it goes. Costs: transcription mistakes, and it needs a clear stop. (A fumble left half-fixed is arguably more human, too.)

5

Record, then review

Telegram · iMessage→ audio clipalternative considered

Record, stop, then a playback bar lets you listen back — scrub, then send or delete. The "get it right before sending" pattern.

Message the panel…

Fit: lowest regret — nothing sends unheard. Costs: an extra step every time, which slows the quick case down.

6

Voice mode — immersive

ChatGPT voiceaudio ↔ spokenambitious follow-up

A dedicated, hands-free conversation: tap once to enter, then just talk back and forth. The panel listens, confers, and speaks its replies aloud. Tap to leave.

Message the panel…
listening…

Fit: best hands-free, eyes-free feel. Costs: heaviest to build, and turn-taking is trickier with several voices — the UI has to say who is speaking.

At a glance

PatternGestureHands to panelSeen inMain friction
2 · Hold-to-talkpress & hold, slide to cancelaudio clipWhatsApp, WeChatfinger held; accidental sends
3 · Tap toggletap start / tap sendaudio clipTelegram, Messengerrecordings left running
4 · Live dictationtap, speak, edittextiOS, ChatGPTtranscription errors
5 · Record & reviewrecord → play → sendaudio clipTelegram, iMessageextra step every time
6 · Voice modeenter, talk freelyaudio ↔ spokenChatGPT voicebuild cost; who-speaks turn-taking
Shipped. We shipped option 1 — the Gemini-style composer, with the mic running #4 live dictation (speech → editable text, no clip). #6 voice mode stays as the ambitious follow-up once multi-voice turn-taking earns the build. — composer shipped 2026-06-21 (dictation mocked)
Dictation went real. The mock recogniser is replaced by iFlytek 中英识别大模型 — zh + en mixed in one session, streaming partials (wpgs) into the composer as a muted editable tail, browser-direct to the CN endpoint (users are in CN, the box in SG — audio never crosses the border; the box only HMAC-signs the WebSocket URL at /api/stt/ws-url). Sessions cap at ~60 s and auto-roll while speech continues; a silent session hangs the mic up. The mic shows only when the server holds XFYUN_* keys. — shipped 2026-07-11 (v375)

Round 2 — when the words outgrow the pill

Real dictation surfaced two problems the mock never met. ① The fold: the composer caps at ~4 lines (max-height:108px) and hides its scrollbar; typing keeps the caret in view, but streamed words move no caret — so a long hypothesis grows below the fold, invisibly. ② The squeeze: mic (40px) + send (42px) + gaps take ~96px of the pill — on a ~360px phone that's a quarter of the field, and it's gone precisely when the text is longest.

A

Pin the fold

→ textshipped — v376

While listening, every partial pins the field to its bottom (ta.scrollTop = ta.scrollHeight) — the newest words are always the visible ones, which is exactly what caret-follow gives typing for free. One line; ships regardless of which layout below wins. Toggle the fix mid-stream and watch the counter:

tap the mic to stream a long mixed zh + en line

Demo note: this mock caps at 3 lines so the fold arrives fast (the real composer caps at 4). Tap the mic again to replay.

B

One control while listening

proposal→ text

While dictating there is nothing sensible to send — the hypothesis is still moving — so the two right-side buttons merge: the mic folds away and the send circle becomes the red ■ stop, in place. The field gains a button's width exactly when the words are longest; tap ■ and it turns back into ↑ with the mic beside it. The idle layout doesn't change at all.

Fit: the smallest change that widens the field when it matters, and the ■ lands where your thumb already hovers. Cost: sending mid-speech becomes stop-then-send, two taps (today ↑ commits the moving hypothesis in one) — a path that's rare by design, since you usually want to see the sentence before it goes.

C

The listening strip

proposal→ textthe fuller redesign

Dictation becomes a visible mode: the pill transforms into a red-bordered strip — a ● LISTENING kicker on top, the transcript full-width beneath with a taller cap (~7 lines), one big ■ beside it. Stop, and the strip collapses back into the normal pill holding your text, both buttons restored.

Fit: the most room for words and an unmistakable "the room is listening" state — plus the natural home for future touches (a level dot, a language hint). Cost: the composer visibly jumps on enter/exit, and it's a real build (~a session) against B's ~20 lines.

D

Adaptive two-deck — the Gemini morph

user sketch · Gemini→ textshipped — v377

Straight from Gemini's composer (the + ignored for now). Empty: the field and a lone mic. Text: ↑ send joins the mic — it never replaces it, so voice can always resume. Past one line: the buttons drop to their own bottom row and the text takes the full pill width. The field grows to exactly 7 lines, then scrolls (pinned while dictating — fix A). This demo is live — type in it (Enter makes a new line), or tap the mic to stream:

state: empty — mic only

Fit: at multi-line the words get the whole pill — more than B returns — while one-line and idle stay as tight as today; the two-deck moment doubles as a visible "this is getting long" cue. Cost: the inline↔two-deck flip needs a width probe (text measured against the inline width, so the layout can't oscillate at the boundary), and the real composer's contenteditable + mention chips make that measurement the fiddly part — call it ~80 lines against B's ~20.

Considered and rejected — mic only when the field is empty (the Telegram/WhatsApp swap). One slot showing the mic when empty and ↑ once text exists would widen the field permanently — but our dictation commits text into the field and hangs up after silence. The moment it auto-stops, the mic would vanish behind your own words and voice input couldn't resume without deleting them. It optimizes the idle state by breaking the core loop.

Round 2 at a glance

OptionField width while dictatingState clarityBuildMain friction
A · Pin the foldunchanged1 line (shipped v376)none — ships regardless
B · One control+ one button (~44px)good — ■ replaces ↑ in place~20 linesmid-speech send = stop, then send
C · Listening stripfull pill, taller (~7 lines)best — a visible mode~a sessioncomposer jumps on enter/exit
D · Gemini two-deckfull pill past line one, 7-line capgood — buttons re-rack visibly~50 lines (shipped v377)layout shifts at the line boundary
mic-only-when-empty+ ~44px alwaysoksmallrejected — kills resume-after-silence
Recommendation. A is shipped (v376). D — the Gemini morph — is now the favoured direction (sketched by the user from Gemini's composer): past one line the words get the full pill, beating B's +44px; the mic never vanishes (send joins it, so the Telegram trap stays dodged); and the 7-line cap + pin makes long dictation fully visible. It also subsumes C's best property (full-width transcript) without the mode jump. B stays the 20-line fallback if the live composer's contenteditable + mention chips make D's width probe fiddly; C retires unless dictation someday wants a true mode. — mockups 2026-07-11 · updated same day after the Gemini sketch
D shipped. Live in the real composer as v377, same day. The deck drop is a flex wrap, not a DOM change (.inputrow.multi .ta-wrap{flex-basis:100%}); the field caps at calc(9px + 7 × --lh) (line-height as one var; bottom pad only — counting the top pad too left a 9px window where an 8th-line sliver peeked while pinned, v379) so "exactly 7 lines" holds at every reading size; the send-hide-when-empty rule applies only under body.stt-on (no dictation keys → today's layout) and exempts the ■ Stop state, so the interrupt never disappears while the panel replies. The revert probe (#deckProbe, a metrics twin of #ta) runs only in the boundary zone — multi-mode text that fits one full-width line — so the common keystroke path adds reads on an already-flushed layout and no writes, honouring the no-per-key-reflow rule that killed the old textarea. v378 trap, caught on a real phone: the probe shares .composer-input for metric parity, so its own rule must out-specify .inputrow .composer-input — at lower specificity it inherits position:relative + flex and sits in flow beside the field: invisible (left:-9999px on a relative box moves paint, not layout) yet space-taking once a probe pass stamps an inline width, squeezing the field to half. — shipped 2026-07-11 (v377, width fix v378)