diff --git a/apps/web/src/components/settings/AddProviderInstanceDialog.environment.browser.test.tsx b/apps/web/src/components/settings/AddProviderInstanceDialog.environment.browser.test.tsx index 2f3414683e077..568c6a953ffc7 100644 --- a/apps/web/src/components/settings/AddProviderInstanceDialog.environment.browser.test.tsx +++ b/apps/web/src/components/settings/AddProviderInstanceDialog.environment.browser.test.tsx @@ -170,6 +170,26 @@ describe("AddProviderInstanceDialog environment routing", () => { }); }); + it("includes the explicitly added default accent color when creating a provider", async () => { + setup(); + await using _screen = await render(dialog()); + await click("Show more"); + await click("Use Kilo Code"); + await act(async () => + document + .querySelector('button[aria-label="Add accent color for Kilo Code"]')! + .click(), + ); + await click("Add color"); + await click("Continue to sign-in"); + + expect(settingsHooks.mutate).toHaveBeenCalledWith({ + operation: "create", + instanceId: "acpRegistry_kilo_code", + instance: expect.objectContaining({ accentColor: "#2563eb" }), + }); + }); + it("keeps fx and the ACP Registry under Show more", async () => { setup(); await using _screen = await render(dialog()); diff --git a/apps/web/src/components/settings/ProviderAccentColorPicker.tsx b/apps/web/src/components/settings/ProviderAccentColorPicker.tsx index 3914935a76c45..21388d5148375 100644 --- a/apps/web/src/components/settings/ProviderAccentColorPicker.tsx +++ b/apps/web/src/components/settings/ProviderAccentColorPicker.tsx @@ -14,11 +14,20 @@ function ProviderCustomColorPanel(props: { readonly value: string; readonly onPreview: (value: string) => void; readonly onCommit: (value: string) => void; + readonly showAddButton: boolean; }) { const { onCommit, onPreview } = props; const [hsv, setHsv] = useState(() => hexToHsv(props.value)); const currentColor = hsvToHex(hsv.h, hsv.s, hsv.v); const [hexDraft, setHexDraft] = useState(null); + const [syncedValue, setSyncedValue] = useState(props.value); + if (syncedValue !== props.value) { + setSyncedValue(props.value); + if (currentColor !== props.value.toLowerCase()) { + setHsv(hexToHsv(props.value)); + setHexDraft(null); + } + } // Pointer drags only preview and commit once on release, so a drag writes // settings once instead of on every pointer move. Keyboard steps commit // immediately because they have no release to wait for. Holds the latest @@ -80,6 +89,15 @@ function ProviderCustomColorPanel(props: { aria-label="Custom hex accent color" spellCheck={false} /> + {props.showAddButton ? ( + onCommit(currentColor)}> + Add color + + } + /> + ) : null} ); @@ -123,6 +141,7 @@ function ProviderCustomColorPicker(props: { value={normalized ?? FALLBACK_ACCENT_COLOR} onPreview={props.onPreview} onCommit={props.onCommit} + showAddButton={!normalized} /> {normalized ? (
diff --git a/apps/web/src/components/settings/colorPickers.browser.test.tsx b/apps/web/src/components/settings/colorPickers.browser.test.tsx index e468830ff3e64..7da9dd32a6586 100644 --- a/apps/web/src/components/settings/colorPickers.browser.test.tsx +++ b/apps/web/src/components/settings/colorPickers.browser.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from "vite-plus/test"; -import { userEvent } from "vite-plus/test/browser"; +import { page, userEvent } from "vite-plus/test/browser"; import type { ReactNode } from "react"; @@ -102,6 +102,89 @@ async function renderAccentPicker(value: string) { } describe("provider accent color picker", () => { + it("leaves the optional color unset when the preview is dismissed", async () => { + const onCommit = vi.fn(); + await using _screen = await renderPicker( + , + ); + + await open("Add accent color for Example provider"); + expect(byLabel("Custom hex accent color").value).toBe("#2563eb"); + expect(onCommit).not.toHaveBeenCalled(); + await userEvent.keyboard("{Escape}"); + expect(onCommit).not.toHaveBeenCalled(); + }); + + it("adds the displayed default without adjusting a color control", async () => { + const onCommit = vi.fn(); + await using screen = await renderPicker( + , + ); + + await open("Add accent color for Example provider"); + await userEvent.click(page.getByRole("button", { name: "Add color", exact: true })); + + expect(onCommit).toHaveBeenCalledExactlyOnceWith("#2563eb"); + await expect + .element(screen.getByRole("button", { name: "Change accent color for Example provider" })) + .toBeVisible(); + await vi.waitFor(() => + expect(document.querySelector('[aria-label="Custom hex accent color"]')).toBeNull(), + ); + }); + + it("lets keyboard users add the displayed default", async () => { + const onCommit = vi.fn(); + await using screen = await renderPicker( + , + ); + + await open("Add accent color for Example provider"); + await act(async () => byLabel("Accent color saturation").focus()); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{Enter}"); + + expect(onCommit).toHaveBeenCalledExactlyOnceWith("#2563eb"); + await expect + .element(screen.getByRole("button", { name: "Change accent color for Example provider" })) + .toBeVisible(); + }); + + it("keeps live color editing and allows clearing back to unset", async () => { + const onCommit = vi.fn(); + await using screen = await renderPicker( + , + ); + + await open("Change accent color for Example provider"); + await press(HUE, "ArrowRight", true); + expect(onCommit).toHaveBeenLastCalledWith("#ff2a00"); + await userEvent.click(page.getByRole("button", { name: "Clear color", exact: true })); + expect(onCommit).toHaveBeenLastCalledWith(""); + await expect + .element(screen.getByRole("button", { name: "Add accent color for Example provider" })) + .toBeVisible(); + }); + it("commits a drag that is still in progress when the popover closes", async () => { await using screen = await renderAccentPicker("#ff0000"); @@ -123,6 +206,9 @@ describe("provider accent color picker", () => { await userEvent.fill(byLabel("Custom hex accent color"), "#ff0000"); expect(screen.onCommit).toHaveBeenLastCalledWith("#ff0000"); + await userEvent.fill(byLabel("Custom hex accent color"), "#00FF00"); + await screen.echo("#00FF00"); + expect(byLabel("Custom hex accent color").value).toBe("#00FF00"); }); it("shows the saved color again when it is reverted outside the picker", async () => { @@ -133,6 +219,12 @@ describe("provider accent color picker", () => { await screen.echo("#00ff00"); await screen.echo("#ff0000"); - expect(byLabel("Change accent color for Codex").style.backgroundColor).toBe("rgb(255, 0, 0)"); + expect(byLabel("Custom hex accent color").value).toBe("#ff0000"); + await screen.echo(""); + expect(byLabel("Custom hex accent color").value).toBe("#2563eb"); + await userEvent.click(page.getByRole("button", { name: "Add color", exact: true })); + expect(screen.onCommit).toHaveBeenLastCalledWith("#2563eb"); + + expect(byLabel("Change accent color for Codex").style.backgroundColor).toBe("rgb(37, 99, 235)"); }); });