From 4f246d28be569eca55a07192d92c0c3bda755149 Mon Sep 17 00:00:00 2001 From: waclaude Date: Sun, 23 Aug 2026 16:11:08 +0000 Subject: [PATCH 1/2] fix(web): expose skill autocomplete as combobox --- .../prompt-skill-autocomplete.test.tsx | 16 ++++++++-------- .../src/components/prompt-skill-autocomplete.tsx | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/web/src/components/prompt-skill-autocomplete.test.tsx b/packages/web/src/components/prompt-skill-autocomplete.test.tsx index 7f835af9c..ccd5fc2a4 100644 --- a/packages/web/src/components/prompt-skill-autocomplete.test.tsx +++ b/packages/web/src/components/prompt-skill-autocomplete.test.tsx @@ -55,7 +55,7 @@ describe("PromptSkillTextarea", () => { it("filters and selects a skill with the keyboard", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "$re"); expect(screen.getByRole("listbox", { name: "Managed skills" })).toBeInTheDocument(); @@ -74,7 +74,7 @@ describe("PromptSkillTextarea", () => { it("dismisses without changing the draft", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "/rev"); await user.keyboard("{Escape}"); @@ -90,7 +90,7 @@ describe("PromptSkillTextarea", () => { const user = userEvent.setup(); const onFallbackKeyDown = vi.fn(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "$"); await user.keyboard("{Control>}{Enter}{/Control}"); @@ -102,7 +102,7 @@ describe("PromptSkillTextarea", () => { it("shows explicit loading and no-match states above the composer", async () => { const user = userEvent.setup(); const { rerender } = render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "/"); expect(screen.getByRole("listbox", { name: "Managed skills" })).toHaveAttribute( @@ -124,7 +124,7 @@ describe("PromptSkillTextarea", () => { it("does not insert a completion beyond the prompt limit", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "$r"); await user.keyboard("{Enter}"); @@ -136,7 +136,7 @@ describe("PromptSkillTextarea", () => { it("selects with a pointer without blurring the textarea", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "/review"); await user.pointer({ @@ -151,7 +151,7 @@ describe("PromptSkillTextarea", () => { it("selects the active completion with Tab", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); await user.type(input, "/rev"); await user.keyboard("{Tab}"); @@ -163,7 +163,7 @@ describe("PromptSkillTextarea", () => { it("suppresses suggestions and selection while composing", async () => { const onFallbackKeyDown = vi.fn(); render(); - const input = screen.getByRole("textbox", { name: "Prompt" }); + const input = screen.getByRole("combobox", { name: "Prompt" }); fireEvent.focus(input); fireEvent.compositionStart(input); diff --git a/packages/web/src/components/prompt-skill-autocomplete.tsx b/packages/web/src/components/prompt-skill-autocomplete.tsx index e92aff524..696f90921 100644 --- a/packages/web/src/components/prompt-skill-autocomplete.tsx +++ b/packages/web/src/components/prompt-skill-autocomplete.tsx @@ -173,6 +173,7 @@ export const PromptSkillTextarea = forwardRef Date: Fri, 28 Aug 2026 01:39:44 +0000 Subject: [PATCH 2/2] fix(web): preserve prompt textarea semantics --- .../prompt-skill-autocomplete.test.tsx | 22 +++++++++++-------- .../components/prompt-skill-autocomplete.tsx | 14 ++++++++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/web/src/components/prompt-skill-autocomplete.test.tsx b/packages/web/src/components/prompt-skill-autocomplete.test.tsx index ccd5fc2a4..734ccbf63 100644 --- a/packages/web/src/components/prompt-skill-autocomplete.test.tsx +++ b/packages/web/src/components/prompt-skill-autocomplete.test.tsx @@ -55,7 +55,7 @@ describe("PromptSkillTextarea", () => { it("filters and selects a skill with the keyboard", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "$re"); expect(screen.getByRole("listbox", { name: "Managed skills" })).toBeInTheDocument(); @@ -63,7 +63,8 @@ describe("PromptSkillTextarea", () => { expect(screen.getAllByRole("option")[0]).toHaveAttribute("tabindex", "-1"); expect(screen.getByTestId("prompt-skill-suggestions")).not.toHaveAttribute("role"); expect(input).toHaveAttribute("aria-autocomplete", "list"); - expect(input).toHaveAttribute("aria-expanded", "true"); + expect(input).not.toHaveAttribute("aria-expanded"); + expect(screen.getByRole("status")).toHaveTextContent("2 managed skill suggestions available."); await user.keyboard("{ArrowDown}{Enter}"); expect(input).toHaveValue("$release-notes "); @@ -74,7 +75,7 @@ describe("PromptSkillTextarea", () => { it("dismisses without changing the draft", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "/rev"); await user.keyboard("{Escape}"); @@ -90,7 +91,7 @@ describe("PromptSkillTextarea", () => { const user = userEvent.setup(); const onFallbackKeyDown = vi.fn(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "$"); await user.keyboard("{Control>}{Enter}{/Control}"); @@ -102,7 +103,7 @@ describe("PromptSkillTextarea", () => { it("shows explicit loading and no-match states above the composer", async () => { const user = userEvent.setup(); const { rerender } = render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "/"); expect(screen.getByRole("listbox", { name: "Managed skills" })).toHaveAttribute( @@ -110,21 +111,24 @@ describe("PromptSkillTextarea", () => { "true" ); expect(screen.getByText("Loading managed skills...")).toBeInTheDocument(); + expect(screen.getByRole("status")).toHaveTextContent("Loading managed skill suggestions."); rerender(); expect(screen.getByText("No managed skills match this session.")).toBeInTheDocument(); + expect(screen.getByRole("status")).toHaveTextContent("No managed skill suggestions available."); expect(screen.getByTestId("prompt-skill-suggestions")).toHaveClass("bottom-full"); rerender(); expect( screen.getByText("Managed skills could not be loaded. Try again shortly.") ).toBeInTheDocument(); + expect(screen.getByRole("status")).toHaveTextContent("Managed skill suggestions unavailable."); }); it("does not insert a completion beyond the prompt limit", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "$r"); await user.keyboard("{Enter}"); @@ -136,7 +140,7 @@ describe("PromptSkillTextarea", () => { it("selects with a pointer without blurring the textarea", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "/review"); await user.pointer({ @@ -151,7 +155,7 @@ describe("PromptSkillTextarea", () => { it("selects the active completion with Tab", async () => { const user = userEvent.setup(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); await user.type(input, "/rev"); await user.keyboard("{Tab}"); @@ -163,7 +167,7 @@ describe("PromptSkillTextarea", () => { it("suppresses suggestions and selection while composing", async () => { const onFallbackKeyDown = vi.fn(); render(); - const input = screen.getByRole("combobox", { name: "Prompt" }); + const input = screen.getByRole("textbox", { name: "Prompt" }); fireEvent.focus(input); fireEvent.compositionStart(input); diff --git a/packages/web/src/components/prompt-skill-autocomplete.tsx b/packages/web/src/components/prompt-skill-autocomplete.tsx index 696f90921..8322b4a61 100644 --- a/packages/web/src/components/prompt-skill-autocomplete.tsx +++ b/packages/web/src/components/prompt-skill-autocomplete.tsx @@ -78,6 +78,15 @@ export const PromptSkillTextarea = forwardRef { @@ -173,12 +182,10 @@ export const PromptSkillTextarea = forwardRef { @@ -219,6 +226,9 @@ export const PromptSkillTextarea = forwardRef + + {suggestionStatus} + {open && completion && (