From 432ce91c5dc38114917a26b475d12866c6891a9e Mon Sep 17 00:00:00 2001 From: Kim Khoi Lam Date: Sun, 1 Nov 2026 04:38:37 -0800 Subject: [PATCH] fix(web): open citation comments with the caret at the end Reopening a saved citation comment left the caret before the first character, so anything typed went in front of the comment. Citation comments now open with the caret after the last character, and a comment too long to fit scrolls to show it. Co-Authored-By: Claude Opus 5.5 --- .../AssistantCitationChip.browser.test.tsx | 28 +++++++++++++++++-- .../components/chat/AssistantCitationChip.tsx | 8 +++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx b/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx index deee8d7afa69d..d583b69d02569 100644 --- a/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx +++ b/apps/web/src/components/chat/AssistantCitationChip.browser.test.tsx @@ -1,5 +1,6 @@ import { ASSISTANT_CITATION_MAX_COMMENT_LENGTH, + type AssistantCitation, EnvironmentId, MessageId, ThreadId, @@ -50,12 +51,16 @@ function setup() { return { sourceAnchor, [Symbol.dispose]: () => source.remove() }; } -function mount(sourceAnchor: AssistantCitationSourceAnchor, onSave: () => boolean) { +function mount( + sourceAnchor: AssistantCitationSourceAnchor | undefined, + onSave: () => boolean, + chipCitation: AssistantCitation = citation, +) { function Composer() { const [open, setOpen] = useState(true); return ( @@ -128,6 +133,25 @@ describe("citation comment source disappearance", () => { }); }); +describe("citation comment caret", () => { + it("opens a saved comment with the caret after its end, scrolled into view", async () => { + using _source = setup(); + const comment = `${"line\n".repeat(40)}end`; + await using _screen = await mount( + undefined, + vi.fn(() => true), + { ...citation, comment }, + ); + await vi.waitFor(() => expect(document.activeElement).toBe(textarea())); + const input = textarea()!; + + expect(input.scrollHeight).toBeGreaterThan(input.clientHeight); + expect(input.scrollTop).toBe(input.scrollHeight - input.clientHeight); + await userEvent.keyboard("!"); + expect(input.value).toBe(`${comment}!`); + }); +}); + describe("citation comment popup position", () => { it("stays on screen when the cited text ends in a code block's hidden overflow", async () => { using _source = setup(); diff --git a/apps/web/src/components/chat/AssistantCitationChip.tsx b/apps/web/src/components/chat/AssistantCitationChip.tsx index 2b90b09f68e77..953724c77062e 100644 --- a/apps/web/src/components/chat/AssistantCitationChip.tsx +++ b/apps/web/src/components/chat/AssistantCitationChip.tsx @@ -190,7 +190,13 @@ export function AssistantCitationChip({ align="end" anchor={popupAnchor} initialFocus={() => { - commentInputRef.current?.focus({ preventScroll: true }); + const input = commentInputRef.current; + if (input) { + input.focus({ preventScroll: true }); + input.setSelectionRange(input.value.length, input.value.length); + // Moving the caret doesn't scroll a long comment to it. + input.scrollTop = input.scrollHeight; + } return false; }} finalFocus={