diff --git a/src/features/agents/Composer.tsx b/src/features/agents/Composer.tsx
index fbae7ff59..84e7682ec 100644
--- a/src/features/agents/Composer.tsx
+++ b/src/features/agents/Composer.tsx
@@ -62,6 +62,7 @@ import { useCompletionAnchor } from "./useCompletionAnchor";
import { useAgentSettings } from "./agentSettings";
import styles from "./Composer.module.css";
import { isImeKey } from "@/platform/ime";
+import { isMacHost } from "@/platform/keymap";
const MAX_PROMPT_HEIGHT = 280;
const MAX_ITEMS = 12;
@@ -454,6 +455,12 @@ export function Composer(props: ComposerProps) {
recalled.current = { index: -1, stash: "" };
return;
}
+ // macOS text boxes scroll on Home / End and leave the caret where it is.
+ if ((e.key === "Home" || e.key === "End") && !e.altKey && !e.ctrlKey && !e.metaKey && isMacHost()) {
+ e.preventDefault();
+ getSelection()?.modify(e.shiftKey ? "extend" : "move", e.key === "Home" ? "backward" : "forward", "lineboundary");
+ return;
+ }
if (e.key === "Tab" && e.shiftKey) {
const mode = modeOption(props.config);
const value = mode && nextValue(mode);
diff --git a/src/features/agents/composerCommands.test.tsx b/src/features/agents/composerCommands.test.tsx
index 0aaf28e80..94726b8c2 100644
--- a/src/features/agents/composerCommands.test.tsx
+++ b/src/features/agents/composerCommands.test.tsx
@@ -1,4 +1,4 @@
-import { beforeEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { sendKeyAction } from "./Composer";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@@ -43,6 +43,7 @@ vi.mock("@/platform/tauriCore", async (importOriginal) => ({
import { isTauri } from "@/platform/ipc";
import { useProject } from "@/platform/project";
+import { usePlatform } from "@/platform/platformInfo";
import type { ProjectInfo } from "@/bindings";
import { defaultTaskProject } from "./project";
import { HomeEditor } from "./HomeEditor";
@@ -243,6 +244,35 @@ describe("New Task composer commands", () => {
});
});
+describe("New Task composer Home / End", () => {
+ const os = usePlatform.getState().os;
+ afterEach(() => {
+ usePlatform.setState({ os });
+ vi.restoreAllMocks();
+ });
+
+ it("moves the caret to the line's start or end on macOS, extending the selection with Shift", () => {
+ usePlatform.setState({ os: "macos" });
+ const modify = vi.fn();
+ vi.spyOn(window, "getSelection").mockReturnValue({ modify } as unknown as Selection);
+ render();
+ const box = screen.getByRole("textbox");
+ expect(fireEvent.keyDown(box, { key: "Home" })).toBe(false);
+ fireEvent.keyDown(box, { key: "End", shiftKey: true });
+ fireEvent.keyDown(box, { key: "End", metaKey: true });
+ expect(modify.mock.calls).toEqual([
+ ["move", "backward", "lineboundary"],
+ ["extend", "forward", "lineboundary"],
+ ]);
+ });
+
+ it("leaves Home / End to the text box elsewhere", () => {
+ usePlatform.setState({ os: "windows" });
+ render();
+ expect(fireEvent.keyDown(screen.getByRole("textbox"), { key: "Home" })).toBe(true);
+ });
+});
+
describe("New Task composer paste", () => {
it("attaches the clipboard's image when WebKitGTK pastes an empty clipboardData", async () => {
render();