From 62d763f4e13d43b8b855f1fab7931a2fc0383ad2 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 12 Aug 2026 00:49:41 +0000 Subject: [PATCH] fix(ui): make task assignee picker searchable Co-Authored-By: Paperclip --- .../task-chat/TaskChatComposer.test.tsx | 39 ++++++++++++++++--- .../components/task-chat/TaskChatComposer.tsx | 3 ++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/ui/src/components/task-chat/TaskChatComposer.test.tsx b/ui/src/components/task-chat/TaskChatComposer.test.tsx index e82fa34f0be..b232298de6d 100644 --- a/ui/src/components/task-chat/TaskChatComposer.test.tsx +++ b/ui/src/components/task-chat/TaskChatComposer.test.tsx @@ -580,24 +580,53 @@ describe("TaskChatComposer", () => { expect(editable().textContent).toBe(`[/deploy](${SLASH_HREF}) `); }); - it("shows the assignee combobox only when reassign is enabled, with the current label", () => { + it("shows a searchable assignee combobox and selects a filtered assignee", async () => { + const onAdd = vi.fn().mockResolvedValue(undefined); render(); expect(container.querySelector('[data-testid="task-chat-composer-assignee"]')).toBeNull(); render( , ); - const trigger = container.querySelector('[data-testid="task-chat-composer-assignee"]'); + const trigger = container.querySelector('[data-testid="task-chat-composer-assignee"]'); expect(trigger?.textContent).toContain("Sam"); + + flushSync(() => trigger?.click()); + await flushAsync(); + + const searchInput = document.body.querySelector('input[placeholder="Search assignees…"]'); + expect(searchInput).not.toBeNull(); + flushSync(() => { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(searchInput, "engineering"); + searchInput!.dispatchEvent(new Event("input", { bubbles: true })); + }); + + const optionButtons = Array.from(document.body.querySelectorAll('button[type="button"]')); + const clippyOption = optionButtons.find((button) => button.textContent?.includes("Clippy")); + const samOption = optionButtons.find((button) => button.textContent?.includes("Sam") && button !== trigger); + expect(clippyOption).not.toBeUndefined(); + expect(samOption).toBeUndefined(); + + flushSync(() => clippyOption?.click()); + expect(trigger?.textContent).toContain("Clippy"); + + typeText("Please take this."); + pressKey("Enter", { metaKey: true }); + await flushAsync(); + expect(onAdd).toHaveBeenCalledWith( + "Please take this.", + undefined, + { assigneeAgentId: "a1", assigneeUserId: null }, + ); }); describe("draft persistence", () => { diff --git a/ui/src/components/task-chat/TaskChatComposer.tsx b/ui/src/components/task-chat/TaskChatComposer.tsx index 72ffd24261d..2617c926f76 100644 --- a/ui/src/components/task-chat/TaskChatComposer.tsx +++ b/ui/src/components/task-chat/TaskChatComposer.tsx @@ -107,6 +107,9 @@ function shouldImplicitlyReopenComment(issueStatus: string | undefined, assignee } function parseAssigneeValue(value: string): CommentReassignment | undefined { + if (!value) { + return { assigneeAgentId: null, assigneeUserId: null }; + } if (value.startsWith("agent:")) { const id = value.slice("agent:".length); return id ? { assigneeAgentId: id, assigneeUserId: null } : undefined;