diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index d819312a4685..49cb0a7e93c6 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -8511,6 +8511,11 @@ export default function ChatView(props: ChatViewProps) { setComposerDraftThreadContexts(composerDraftTarget, [...records]), resetCursor: (options) => composerRef.current?.resetCursorState(options), }); + } else if ( + submissionIntent === "background" && + currentRouteThreadKeyRef.current === routeThreadKey + ) { + handleNewThreadInActiveProject(); } return; } @@ -8743,7 +8748,9 @@ export default function ChatView(props: ChatViewProps) { } beginLocalDispatch({ preparingWorktree: multipleModelSelections !== null || Boolean(baseBranchForWorktree), - submissionIntent, + // Only a draft has a background submission to hide behind its hero. + submissionIntent: + submissionIntent === "background" && !isLocalDraftThread ? "foreground" : submissionIntent, }); setWorktreeSetupRef( multipleModelSelections === null && baseBranchForWorktree @@ -9316,6 +9323,12 @@ export default function ChatView(props: ChatViewProps) { }), ); } + } else if ( + submissionIntent === "background" && + currentRouteThreadKeyRef.current === routeThreadKey + ) { + // An existing thread keeps running; open a fresh composer like a draft does. + handleNewThreadInActiveProject(); } } } diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.ts index dc31de55d1f8..4897007d8fda 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.ts @@ -314,6 +314,7 @@ export function buildKeybindingCommandOptions( export function commandLabel(command: KeybindingCommand): string { if (command === "composer.sendAlternate") return "Composer: Opposite Queue or Steer Action"; if (command === "composer.sendBackground") return "Composer: Start in Background"; + if (command === "composer.sendAndNewThread") return "Composer: Send and Start New Thread"; if (command === "thread.steerQueuedMessage") return "Queue: Send First Queued Message as Steer"; if (command === "thread.editQueuedMessage") return "Queue: Edit Last Queued Message"; if (command === "thread.copyReference") return "Pull Request: Copy Link or Thread ID"; diff --git a/apps/web/src/composer-logic.test.ts b/apps/web/src/composer-logic.test.ts index 2da0c9671c73..6d81f3be8507 100644 --- a/apps/web/src/composer-logic.test.ts +++ b/apps/web/src/composer-logic.test.ts @@ -124,6 +124,14 @@ describe("composerSubmissionIntentForKey", () => { event: { ...modEnter, altKey: true }, }), ).toBe("background"); + expect( + composerSubmissionIntentForKey({ + ...input, + platform, + sendShortcut, + event: { ...modEnter, altKey: true }, + }), + ).toBe("background"); expect( composerSubmissionIntentForKey({ ...input, diff --git a/apps/web/src/composer-logic.ts b/apps/web/src/composer-logic.ts index 99567b5a4a68..09c5e714aeb3 100644 --- a/apps/web/src/composer-logic.ts +++ b/apps/web/src/composer-logic.ts @@ -59,6 +59,7 @@ export function composerSubmissionIntentForKey(input: { }); if (command === "composer.sendAlternate" && input.isRunning) return "alternate"; if (command === "composer.sendBackground" && input.isDraftThread) return "background"; + if (command === "composer.sendAndNewThread" && !input.isDraftThread) return "background"; if (command !== null || event.key !== "Enter" || event.shiftKey || event.altKey) return null; if ( composerRequiresModifier(input.sendShortcut, input.prompt ?? "") && diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index 43bd23333f66..63c9577767af 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -11,11 +11,12 @@ inserts a new line. This applies to the web and desktop composer at desktop widt **Follow-up behavior** chooses Queue or Steer while the agent runs. Use `mod+Enter` to do the opposite for one message, even when the send shortcut -requires a modifier. In a new thread, `mod+Alt+Enter` starts the thread in the -background and opens a fresh composer. Change either shortcut in -**Settings → Keybindings** under **Composer: Opposite Queue or Steer Action** or -**Composer: Start in Background**. These bindings take priority over the send -shortcut. Click the send button to use the configured follow-up behavior. +requires a modifier. `mod+Alt+Enter` sends, keeps that thread running in the +background, and opens a fresh new-thread composer. Change these +shortcuts in **Settings → Keybindings** under **Composer: Opposite Queue or +Steer Action**, **Composer: Start in Background**, or **Composer: Send and Start +New Thread**. These bindings take priority over the send shortcut. Click the +send button to use the configured follow-up behavior. When an active turn has queued messages, `mod+Shift+Enter` sends the first as a steer. Change it under **Queue: Send First Queued Message as Steer** in Keybindings. diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 06ac4fb83820..e827ba8270fd 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -87,6 +87,7 @@ export const STATIC_KEYBINDING_COMMANDS = [ "composer.stash", "composer.sendAlternate", "composer.sendBackground", + "composer.sendAndNewThread", "composer.host", "composer.effort", "composer.mode", diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts index 447d42560365..8cb3fee23e02 100644 --- a/packages/shared/src/keybindings.ts +++ b/packages/shared/src/keybindings.ts @@ -53,6 +53,11 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [ command: "composer.sendBackground", when: "composerFocus && draftThreadRoute", }, + { + key: "mod+alt+enter", + command: "composer.sendAndNewThread", + when: "composerFocus && !draftThreadRoute", + }, { key: "mod+n", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" }, { key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" },