Skip to content

Commit 5b95d53

Browse files
bpmctclaude
andcommitted
feat: Add cmd+enter to submit tasks immediately
Implements cmd+enter (Mac) / ctrl+enter (Windows/Linux) keyboard shortcut for submitting new tasks on the tasks page. Regular enter still creates a new line in the textarea. Fixes #21179 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3a0e8af commit 5b95d53

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { fireEvent } from "@testing-library/react";
2+
import type { TextareaAutosizeProps } from "react-textarea-autosize";
3+
4+
// Test the keyboard handler logic directly
5+
describe("TaskPrompt - Cmd+Enter behavior", () => {
6+
test("calls form.requestSubmit when cmd+enter is pressed (Mac)", () => {
7+
const mockForm = {
8+
requestSubmit: vi.fn(),
9+
};
10+
11+
const mockTextarea = {
12+
closest: vi.fn().mockReturnValue(mockForm),
13+
};
14+
15+
const mockEvent = {
16+
key: "Enter",
17+
metaKey: true,
18+
ctrlKey: false,
19+
preventDefault: vi.fn(),
20+
currentTarget: mockTextarea,
21+
} as unknown as React.KeyboardEvent<HTMLTextAreaElement>;
22+
23+
// Simulate the handler logic from PromptTextarea
24+
if (mockEvent.key === "Enter" && (mockEvent.metaKey || mockEvent.ctrlKey)) {
25+
mockEvent.preventDefault();
26+
const form = mockEvent.currentTarget.closest("form");
27+
if (form) {
28+
form.requestSubmit();
29+
}
30+
}
31+
32+
expect(mockEvent.preventDefault).toHaveBeenCalled();
33+
expect(mockTextarea.closest).toHaveBeenCalledWith("form");
34+
expect(mockForm.requestSubmit).toHaveBeenCalled();
35+
});
36+
37+
test("calls form.requestSubmit when ctrl+enter is pressed (Windows/Linux)", () => {
38+
const mockForm = {
39+
requestSubmit: vi.fn(),
40+
};
41+
42+
const mockTextarea = {
43+
closest: vi.fn().mockReturnValue(mockForm),
44+
};
45+
46+
const mockEvent = {
47+
key: "Enter",
48+
metaKey: false,
49+
ctrlKey: true,
50+
preventDefault: vi.fn(),
51+
currentTarget: mockTextarea,
52+
} as unknown as React.KeyboardEvent<HTMLTextAreaElement>;
53+
54+
// Simulate the handler logic from PromptTextarea
55+
if (mockEvent.key === "Enter" && (mockEvent.metaKey || mockEvent.ctrlKey)) {
56+
mockEvent.preventDefault();
57+
const form = mockEvent.currentTarget.closest("form");
58+
if (form) {
59+
form.requestSubmit();
60+
}
61+
}
62+
63+
expect(mockEvent.preventDefault).toHaveBeenCalled();
64+
expect(mockTextarea.closest).toHaveBeenCalledWith("form");
65+
expect(mockForm.requestSubmit).toHaveBeenCalled();
66+
});
67+
68+
test("does not submit when only enter is pressed", () => {
69+
const mockForm = {
70+
requestSubmit: vi.fn(),
71+
};
72+
73+
const mockTextarea = {
74+
closest: vi.fn().mockReturnValue(mockForm),
75+
};
76+
77+
const mockEvent = {
78+
key: "Enter",
79+
metaKey: false,
80+
ctrlKey: false,
81+
preventDefault: vi.fn(),
82+
currentTarget: mockTextarea,
83+
} as unknown as React.KeyboardEvent<HTMLTextAreaElement>;
84+
85+
// Simulate the handler logic from PromptTextarea
86+
if (mockEvent.key === "Enter" && (mockEvent.metaKey || mockEvent.ctrlKey)) {
87+
mockEvent.preventDefault();
88+
const form = mockEvent.currentTarget.closest("form");
89+
if (form) {
90+
form.requestSubmit();
91+
}
92+
}
93+
94+
expect(mockEvent.preventDefault).not.toHaveBeenCalled();
95+
expect(mockForm.requestSubmit).not.toHaveBeenCalled();
96+
});
97+
98+
test("calls original onKeyDown if provided", () => {
99+
const mockOnKeyDown = vi.fn();
100+
const props: Partial<TextareaAutosizeProps> = {
101+
onKeyDown: mockOnKeyDown,
102+
};
103+
104+
const mockEvent = {
105+
key: "Enter",
106+
metaKey: true,
107+
ctrlKey: false,
108+
preventDefault: vi.fn(),
109+
currentTarget: {
110+
closest: vi.fn().mockReturnValue({ requestSubmit: vi.fn() }),
111+
},
112+
} as unknown as React.KeyboardEvent<HTMLTextAreaElement>;
113+
114+
// Simulate the handler logic from PromptTextarea with onKeyDown prop
115+
if (mockEvent.key === "Enter" && (mockEvent.metaKey || mockEvent.ctrlKey)) {
116+
mockEvent.preventDefault();
117+
const form = mockEvent.currentTarget.closest("form");
118+
if (form) {
119+
form.requestSubmit();
120+
}
121+
}
122+
if (props.onKeyDown) {
123+
props.onKeyDown(mockEvent);
124+
}
125+
126+
expect(mockOnKeyDown).toHaveBeenCalledWith(mockEvent);
127+
});
128+
});

site/src/modules/tasks/TaskPrompt/TaskPrompt.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,10 +468,27 @@ const PromptTextarea: FC<PromptTextareaProps> = ({
468468
isSubmitting,
469469
...props
470470
}) => {
471+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
472+
// Submit form on Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux)
473+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
474+
e.preventDefault();
475+
// Trigger form submission by finding the closest form and submitting it
476+
const form = e.currentTarget.closest("form");
477+
if (form) {
478+
form.requestSubmit();
479+
}
480+
}
481+
// Call the original onKeyDown if it exists
482+
if (props.onKeyDown) {
483+
props.onKeyDown(e);
484+
}
485+
};
486+
471487
return (
472488
<div className="relative">
473489
<TextareaAutosize
474490
{...props}
491+
onKeyDown={handleKeyDown}
475492
required
476493
id="prompt"
477494
name="prompt"

0 commit comments

Comments
 (0)