fix(next): preserve gesture proof for rapid composer sends
Leo reported: “WHAT THE F*** ARE THESE” after his typed message was marked “Not sent: it needs your own key press or click” with transport.gesture_required during rapid sends.
The composer now binds each real Enter or Send click to its prompt before chat-start and persistence waits. The host retains prompt-specific tickets while other sends drain, with bounded retention for abandoned tickets. Forged sends still receive no ticket and remain refused by the host gesture guard.
Regression coverage exercises rapid queued and steer sends in the web client and prompt-ticket redemption on the native transport.
Changelog
- Rapid typed composer sends retain their own gesture proof and are no longer refused by
transport.gesture_required.
Agent session: agent session
🤖 Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes rapid typed composer sends being refused with "Not sent: it needs your own key press or click" when transport.gesture_required is on, because the first prompt reaching the relay invalidated the second send's gesture proof.
Each send now binds its own gesture ticket to its prompt before chat-start and persistence waits, and the frame carries that ticket so queued and steer sends stay redeemable. The host keeps prompt-specific tickets while other sends drain (8 live, 64 total per connection); a same-prompt replacement still drops the earlier ticket, and forged sends still receive no ticket and stay refused. Tickets stay redeemable when a Cloud hand-off labels their prompt handoff-<turn>-<id>. Regression tests cover rapid queued and steer sends in the web client, prompt-ticket redemption on the native transport, and a socket that drops while a send awaits its gesture (the prompt never goes out without its ticket).