Skip to content

Add a setting to send messages with Command+Enter - #881

Open
mfieldingsquare wants to merge 2 commits into
block:mainfrom
mfieldingsquare:send-with-mod-enter
Open

mfieldingsquare wants to merge 2 commits into
block:mainfrom
mfieldingsquare:send-with-mod-enter

Conversation

@mfieldingsquare

@mfieldingsquare mfieldingsquare commented Oct 12, 2026 •

Copy link
Copy Markdown

Why?

Pressing Enter sends a message straight away, so people who write multi-line messages can send a draft before it is finished.

Changes

  • Add a "Send with ⌘Enter" switch (Ctrl+Enter on Windows and Linux) to Settings > Shortcuts. It is off by default and saved on each device.
  • When it is on, Enter adds a new line, and ⌘Enter sends the message, including from inside a list.
  • Share the on/off storage code that "Remember mentioned agents" already used, rather than copying it.

Validation

  • Component tests cover both send modes, sending from a list, and the settings switch.
  • An agent ran the full flow in headless Chromium: turn the setting on, type two lines, then send with ⌘Enter. No browser test was added; the screenshot script was temporary.
  • Not yet done: a person testing it in the desktop app, and WebKit.
composer-multiline composer-sent settings-dark-narrow settings-dark-on settings-light-off settings-light-on

🤖 Generated with Claude Code

@mfieldingsquare
mfieldingsquare marked this pull request as ready for review October 12, 2026 04:28
@mfieldingsquare
mfieldingsquare requested a review from a team as a code owner October 12, 2026 04:28

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.

Requesting changes for one correctness issue, explained in the inline thread: with the setting on, ⌘/Ctrl+Enter sends a draft while a mention, emoji, or command completion is still showing.

Validation at 017ba7af: tsc --noEmit passes, and the MessageComposer, ShortcutSettings, AgentSettings and Settings test files pass locally (324 tests). Main CI is waiting for maintainer approval on this fork PR, so it hasn't run yet. The PR notes that desktop and WebKit testing are still to do.

To merge: ⌘/Ctrl+Enter must not send while a completion menu is showing, with a test that covers it, and CI must pass.

!event.ctrlKey &&
!event.metaKey
) {
const sendKeystroke = readSendKeystroke(event.nativeEvent);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 ⌘/Ctrl+Enter sends the draft while a completion menu is open.

ComposerCompletions (src/features/conversation/ComposerCompletions.tsx:289) returns false straight away for any key pressed with Alt, Ctrl or Meta. So its "a displayed choice owns acceptance, never turn it into an unintended send" guard only covers plain Enter. Once ⌘/Ctrl+Enter is the send key, a half-typed @Ali, :smi or !search gets sent as plain text, and the person you meant to mention isn't notified. That breaks the rule in docs/plugin-architecture.md: "A rejected displayed choice must not fall through to sending."

I reproduced it at 017ba7af with a throwaway test placed next to accepts a completion before handling list Enter:

sendWithModEnterPreference.setEnabled(true);
const h = mount();
await h.user.type(h.input(), "!search");
expect(h.publish(h.completionRequests.length - 1)).not.toBe(false);
await h.user.keyboard("{Control>}{Enter}{/Control}");
expect(h.messages.send).not.toHaveBeenCalled();
// fails: send called with ("channel", "!search", [], [])

Fix: when a completion is showing, the active send keystroke should either accept the choice or be swallowed, the same as plain Enter is today. That could mean the completion handler checks the send keystroke, or the composer refuses to send while a choice is showing. Please add this case as a test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed in e5f3b81f. While a completion is showing, the completion key handler now ignores the active send keystroke, so the draft is not sent. Only plain Enter and Tab accept a choice, as docs/plugin-architecture.md describes, and that doc now mentions this case. Default mode is unchanged. The new test, does not send on Mod+Enter while a completion is showing, fails before the fix and passes after: Mod+Enter with !search showing does not send, Enter accepts the choice, and Mod+Enter then sends. The branch is rebased on main, and the affected tests (1703), typecheck and biome pass.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Carl, an automated reviewer, commenting via Wes’s GitHub account.

No blocking findings at 27c0abe. Three optional suggestions are inline.

What was checked, by reading the code plus a few throwaway local tests:

  • With the setting off, Enter behaves exactly as before.
  • With it on, plain Enter goes down the same line-break path as Shift+Enter, so leaving an empty list item still works.
  • Holding ⌘Enter, IME composition, and the zero-results retry row are handled.
  • Every composer (thread, edit, DM, solo, media review, forum) goes through MessageComposer.
  • No global or native shortcut uses Mod+Enter.
  • The device-toggle extraction keeps "Remember mentioned agents" behaving the same.
  • The new Messages group follows the DESIGN.md settings layout.

Not checked here: the setting in the desktop app, WebKit, and on-screen keyboards on touch devices. The PR description already lists these as open, and the send button covers touch.

if (event.altKey || event.ctrlKey || event.metaKey) {
// When ⌘/Ctrl+Enter is the send key, a displayed choice must still not
// fall through to sending. Only plain Enter and Tab accept a choice.
if (count && readSendKeystroke(event.nativeEvent).sends) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (low). This branch swallows ⌘/Ctrl+Enter without calling invalidate(), even when !active(). The plain-Enter branch below (:303-308) does call it.

When this bites: a provider's evidence is revoked while its menu is still on screen. Plain Enter dismisses the menu, and a second Enter sends. ⌘Enter does nothing and the stale menu stays until the user types or presses Escape.

Suggested fix: call current.current.editor.invalidate() here when !active(), matching the plain branch.

Related product question, not a defect: with a valid menu open, ⌘Enter neither accepts nor sends, with no feedback. That matches the updated plugin-architecture doc. Someone who types @ or : mid-sentence may still read it as ⌘Enter "not working".

<InlineHeader title="Messages" />
<SettingsGroup>
<SwitchPreferenceRow
label={`Send with ${modEnter}`}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (a11y nit). formatBinding returns .label ("Command Enter" / "Control Enter") specifically for assistive tech. Keep .text as the visible label and pass .label through the row's existing aria-label prop, e.g. aria-label={Send with ${formatBinding(MOD_ENTER, apple).label}}. The getByRole("switch", { name }) queries in ShortcutSettings.test.tsx would need to follow.

);
});

it("sends from inside a list on Mod+Enter when that preference is on", async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (coverage). The likeliest regression here is plain Enter inside a list once the setting is on. It now falls through to EditableInput → insertLineBreak rather than this handler's list branch. A throwaway local test (- first{Enter}{Enter}outside with the setting on: one <li>, then a paragraph, and nothing sent) passed. Adding that case to this test would pin it, if you think it's worth a test.

mfieldingsquare and others added 2 commits October 27, 2026 08:58
Enter adds a new line when the setting is on. The on/off storage code
for device-local choices is now shared with "Remember mentioned agents".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Fielding <mfielding@squareup.com>
When Command/Ctrl+Enter is the send key, pressing it while a mention,
emoji or command completion is showing sent the unfinished text. It is
now ignored until the choice is accepted or dismissed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Fielding <mfielding@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants