diff --git a/src/ui/useSelectionBoundary.test.tsx b/src/ui/useSelectionBoundary.test.tsx index 442d16038..643cf294c 100644 --- a/src/ui/useSelectionBoundary.test.tsx +++ b/src/ui/useSelectionBoundary.test.tsx @@ -121,8 +121,10 @@ it("selects only the pane's text on primary+A", () => { const event = fireEvent.keyDown(pane, { key: "a", ctrlKey: true, metaKey: true }); expect(event).toBe(false); const range = window.getSelection()!.getRangeAt(0); - expect(range.startContainer).toBe(pane); - expect(range.endContainer).toBe(pane); + expect(range.startContainer).toBe(view.getByText("GET /data.json").firstChild); + expect(range.startOffset).toBe(0); + expect(range.endContainer).toBe(view.getByText("GET /data.json").firstChild); + expect(range.endOffset).toBe("GET /data.json".length); expect(window.getSelection()!.toString()).not.toContain("Request history"); }); @@ -133,3 +135,13 @@ it("leaves Select All in an editable field to the field", () => { expect(selectReadingPane(input)).toBe(false); expect(selectReadingPane(document.body)).toBe(false); }); + +it("ends Select All in the pane's last selectable text, not on its edge before the composer", () => { + const view = fixture(); + expect(selectReadingPane(view.getByText("First message"))).toBe(true); + const range = window.getSelection()!.getRangeAt(0); + expect(range.startContainer).toBe(view.text("First message")); + expect(range.endContainer).toBe(view.text("Last message")); + expect(range.endOffset).toBe("Last message".length); + expect(window.getSelection()!.toString()).not.toContain("Reply to the agent"); +}); diff --git a/src/ui/useSelectionBoundary.ts b/src/ui/useSelectionBoundary.ts index 1234f92e5..8c880db51 100644 --- a/src/ui/useSelectionBoundary.ts +++ b/src/ui/useSelectionBoundary.ts @@ -120,9 +120,10 @@ export function selectReadingPane(focused: Element | null): boolean { const pane = focused.closest("[data-selection-scope]"); const selection = pane?.ownerDocument.getSelection(); if (!pane || !selection) return false; - const range = pane.ownerDocument.createRange(); - range.selectNodeContents(pane); - selection.removeAllRanges(); - selection.addRange(range); + // From the first to the last text, not the pane's own boundaries, which WebKit paints into + // the field after the pane (the composer). + const start = textEdge(pane, "start"); + const end = textEdge(pane, "end"); + selection.setBaseAndExtent(start.node, start.offset, end.node, end.offset); return true; }