diff --git a/src/components/insights/coach-panel/__tests__/coach-input.test.tsx b/src/components/insights/coach-panel/__tests__/coach-input.test.tsx index 891d6264c..1ee7ef2c9 100644 --- a/src/components/insights/coach-panel/__tests__/coach-input.test.tsx +++ b/src/components/insights/coach-panel/__tests__/coach-input.test.tsx @@ -228,7 +228,7 @@ describe("", () => { expect(html).toContain('data-slot="coach-input-send"'); }); - it("puts the textarea above a split control row on phones", () => { + it("keeps the textarea and its controls on one row at every width", () => { const html = render( ", () => { const controls = html.match( /]*data-slot="coach-input-controls"[^>]*>/, ); - expect(controls?.[0]).toMatch(/\bw-full\b/); - expect(controls?.[0]).toMatch(/\bjustify-between\b/); - expect(controls?.[0]).toMatch(/\bsm:w-auto\b/); - expect(controls?.[0]).not.toMatch(/\bsm:contents\b/); + // The controls sit BESIDE the textarea, not under it. Stacking them made an + // empty composer two rows tall — a text line plus a 44 px tap-target row — + // which is roughly a tenth of a phone screen before anyone has typed. The + // controls must therefore never claim the full width or push themselves + // onto their own line. + expect(controls?.[0]).not.toMatch(/\bw-full\b/); + expect(controls?.[0]).not.toMatch(/\bjustify-between\b/); + expect(controls?.[0]).toMatch(/\bshrink-0\b/); + + // And the row itself is a row at every width, bottom-aligned so the send + // button stays beside the last line as the textarea grows. + const shell = html.match(/]*class="[^"]*\bflex-row\b[^"]*"[^>]*>/); + expect(shell?.[0]).toMatch(/\bitems-end\b/); + expect(shell?.[0]).not.toMatch(/\bflex-col\b/); }); it("sizes the hub actions trigger to the 44px tap-target floor on phones", () => { diff --git a/src/components/insights/coach-panel/coach-input.tsx b/src/components/insights/coach-panel/coach-input.tsx index 123e4f97c..a17d49b7e 100644 --- a/src/components/insights/coach-panel/coach-input.tsx +++ b/src/components/insights/coach-panel/coach-input.tsx @@ -419,9 +419,17 @@ export function CoachInput({ "border-border/60 bg-muted/40 group rounded-2xl border", "shadow-sm transition-colors", "focus-within:border-primary/50 focus-within:ring-primary/50 focus-within:bg-background focus-within:ring-2", - // Mobile uses two rows. Desktop keeps the same DOM and visual order - // (textarea, actions, Send) so keyboard focus never jumps backwards. - "flex flex-col gap-1.5 p-1.5 sm:flex-row sm:items-end", + // One row at every width. Mobile used to stack the controls under the + // textarea, which made an EMPTY composer two rows tall: a 44 px line + // plus a 44 px touch-target row plus padding, around a tenth of a + // phone screen before anyone had typed anything. The stacking was + // there to protect keyboard focus order, but that argument does not + // hold: focus follows DOM order, and the DOM order (textarea, + // actions, Send) is identical in both layouts. Nothing jumps. + // + // `items-end` keeps the controls beside the LAST line as the textarea + // grows, so the send button stays where the thumb left it. + "flex flex-row items-end gap-1.5 p-1.5", )} >