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.
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:
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.
Press and hold the mic to record; release to send. Slide left to cancel. The classic one-handed gesture — fastest for a quick clip.
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.
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".)
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.
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.
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.)
Record, stop, then a playback bar lets you listen back — scrub, then send or delete. The "get it right before sending" pattern.
Fit: lowest regret — nothing sends unheard. Costs: an extra step every time, which slows the quick case down.
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.
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.
| Pattern | Gesture | Hands to panel | Seen in | Main friction |
|---|---|---|---|---|
| 2 · Hold-to-talk | press & hold, slide to cancel | audio clip | WhatsApp, WeChat | finger held; accidental sends |
| 3 · Tap toggle | tap start / tap send | audio clip | Telegram, Messenger | recordings left running |
| 4 · Live dictation | tap, speak, edit | text | iOS, ChatGPT | transcription errors |
| 5 · Record & review | record → play → send | audio clip | Telegram, iMessage | extra step every time |
| 6 · Voice mode | enter, talk freely | audio ↔ spoken | ChatGPT voice | build cost; who-speaks turn-taking |
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)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.
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:
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.
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.
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.
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:
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.
| Option | Field width while dictating | State clarity | Build | Main friction |
|---|---|---|---|---|
| A · Pin the fold | unchanged | — | 1 line (shipped v376) | none — ships regardless |
| B · One control | + one button (~44px) | good — ■ replaces ↑ in place | ~20 lines | mid-speech send = stop, then send |
| C · Listening strip | full pill, taller (~7 lines) | best — a visible mode | ~a session | composer jumps on enter/exit |
| D · Gemini two-deck | full pill past line one, 7-line cap | good — buttons re-rack visibly | ~50 lines (shipped v377) | layout shifts at the line boundary |
| mic-only-when-empty | + ~44px always | ok | small | rejected — kills resume-after-silence |
.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)