feat(chat): Enter sends on desktop, Shift+Enter newline; touch keeps return-as-newline #70
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Change the submit key in both web composers: bare Enter submits on devices with a physical-pointer setup; Shift+Enter inserts a newline. Today submit is Cmd/Ctrl+Enter only and bare Enter is always a newline.
This intentionally reverses the "bare Enter stays a newline" clause of ADR-0022/ADR-0029 (it was a considered-and-rejected option in ADR-0022). All decisions below were resolved with the maintainer in a grill session on 2026-07-09.
Decided behavior
window.matchMedia('(hover: hover) and (pointer: fine)').matches(evaluate per keydown or via a listener; do not snapshot once at mount, so docking/undocking a keyboard on hybrids behaves). On touch devices the virtual keyboard's return key keeps inserting a newline and the on-screen Send button remains the submit affordance — preserving the phone-first rationale of ADR-0005/0022. Do not setenterkeyhint="send"(the platform offers only one action key; relabeling it would cost phone users keyboard newlines).shiftKeyis held — the browser default does the rest. (Alt+Enter likewise falls through.)web/src/routes/RunChat.tsx, keydown handler ~lines 1670–1699) and the new-run composer (web/src/routes/NewRun.tsx, ~lines 269–274, whose comment says it mirrors the chat composer). Consider extracting the shared gate into one helper so the two cannot drift.RunChat.tsxlogic); the send branch runs only when the popover is closed.e.isComposingis true (and the legacye.keyCode === 229case) so committing CJK composition with Enter does not fire a send. Currently absent everywhere inweb/src— it was safe under Ctrl+Enter, it is not under bare Enter.preventDefaultit so it does not insert a leading newline into an empty composer (match/keep the existingsend()guard).NewRun.tsxtitle="Start run (Cmd/Ctrl+Enter)"to reflect Enter (e.g. "Start run (Enter)"). The chat Send button has no shortcut hint; nothing to change there.Tests
web/src/routes/RunChat.test.tsx:360–380("sends on Cmd/Ctrl+Enter but never on bare Enter") to the new contract: bare Enter sends on fine-pointer, Shift+Enter does not send (newline), Ctrl/Cmd+Enter still sends.matchMediacoarse/no-hover) — bare Enter does not send.isComposing: trueEnter does not send.NewRun.tsxcomposer (currently untested).ADR
Add a new ADR (next free number) recording this decision and superseding the "bare Enter stays a newline in every state" clauses of ADR-0022 (
docs/adr/0022-chat-composer-send-interrupt-morph.md, incl. the rejected-option entry) and ADR-0029 (docs/adr/0029-chat-composer-always-send-interrupt-relocation.md); annotate those two files' status/compat notes accordingly. Everything else in ADR-0029 (always-send shortcut, header interrupt) remains in force.Out of scope
enterkeyhintchanges.web/src+ tests + ADR).Landing audit — PR #71 (
afk/70) → VERDICT: PASSAFK contract ✓ — head
afk/70, body carriesCloses #70, title is Conventional Commits (feat(chat): …).Correctness review of the diff ✓ — implements all 9 decided behaviors:
isComposerSendgate (web/src/lib/composerKeys.ts) that both composers call, so they cannot drift. Bare Enter sends iffmatchMedia('(hover: hover) and (pointer: fine)')matches, evaluated per-keydown (never snapshotted); the optional?.makes an absentmatchMediaread as not-fine-pointer (touch-safe). Cmd/Ctrl+Enter always sends; Shift/Alt+Enter are never intercepted; a new IME guard (isComposing/ legacykeyCode === 229) blocks sends mid-composition.RunChat.tsx: popover precedence preserved — the Enter/Tab accept branch callspreventDefault()and returns before the send gate is reached; Cmd/Ctrl+Enter still sends over the popover.NewRun.tsx: bare Enter on an empty boxpreventDefaults (no leading newline) then bails without spawning, while Cmd/Ctrl+Enter keeps the explicit empty plain-spawn path; tooltip updated to "Start run (Enter)".Verification signal — the repo's required
nativeCI check (.forgejo/workflows/ci.yml: SPA eslint + prettier + vitest +vite build, then the Gouibuild/test) covers exactly this diff;ci-nixis path-gated to**/*.nix/flake.lock/go.mod/go.sum, none of which this pure-web/srcdiff touches, so it correctly does not run. The forge CI status is not readable from the lab sandbox (no forge token), so I re-ran the SPA gate locally on the PR head (eff2f33): eslint clean, prettier clean, 558/558 tests green (37 files), andtsc --noEmit && vite buildclean — matching the PR body.Mergeability ✓ — branches directly off current
main;git merge-treereports no conflicts.No blockers. Awaiting the maintainer's free-text go-ahead to merge.