Compare commits

...
Author SHA1 Message Date
l0ng-ai c48becbfff fix(ui): keep Select All in a reading pane out of the composer
⌘A in an agent conversation selected the pane from its own start to its own end; WebKit paints a selection ending on the pane's boundary up to the next selectable text after it, so the composer looked selected too. Select All now runs from the pane's first to its last selectable text, like a clamped drag selection already does.
2026-11-13 13:32:23 +08:00
2 changed files with 19 additions and 6 deletions

No files matched your search

+14 -2
View File
@@ -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");
});
+5 -4
View File
@@ -120,9 +120,10 @@ export function selectReadingPane(focused: Element | null): boolean {
const pane = focused.closest<HTMLElement>("[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;
}