From dfc53e42f09bf597deff46d0f2d4fe9f17133733 Mon Sep 17 00:00:00 2001 From: Roomote Date: Thu, 20 Aug 2026 21:35:39 +0000 Subject: [PATCH 1/2] fix(webview): finish light theme token cleanup --- webview-ui/playwright/vscode-theme-light.css | 2 + .../src/components/chat/ChatTextArea.tsx | 27 ++--- .../components/chat/CodebaseSearchResult.tsx | 8 +- webview-ui/src/components/chat/IconButton.tsx | 8 +- .../components/chat/IndexingStatusBadge.tsx | 3 +- .../src/components/chat/LucideIconButton.tsx | 6 +- .../chat/UpdateTodoListToolBlock.tsx | 109 +++++------------- .../src/components/chat/WorktreeSelector.tsx | 3 +- .../__tests__/ChatControlButtons.spec.tsx | 42 +++++++ .../__tests__/CodebaseSearchResult.spec.tsx | 38 ++++++ .../__tests__/ThemeTokenCleanup.visual.tsx | 61 ++++++++++ .../UpdateTodoListToolBlock.spec.tsx | 42 +++++++ .../src/components/chat/chatControlStyles.ts | 5 + .../components/ui/__tests__/checkbox.spec.tsx | 12 ++ webview-ui/src/components/ui/checkbox.tsx | 7 +- 15 files changed, 260 insertions(+), 113 deletions(-) create mode 100644 webview-ui/src/components/chat/__tests__/ChatControlButtons.spec.tsx create mode 100644 webview-ui/src/components/chat/__tests__/CodebaseSearchResult.spec.tsx create mode 100644 webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx create mode 100644 webview-ui/src/components/chat/__tests__/UpdateTodoListToolBlock.spec.tsx create mode 100644 webview-ui/src/components/chat/chatControlStyles.ts create mode 100644 webview-ui/src/components/ui/__tests__/checkbox.spec.tsx diff --git a/webview-ui/playwright/vscode-theme-light.css b/webview-ui/playwright/vscode-theme-light.css index c877cea470..5816e59760 100644 --- a/webview-ui/playwright/vscode-theme-light.css +++ b/webview-ui/playwright/vscode-theme-light.css @@ -7,6 +7,7 @@ --vscode-focusBorder: #0090f1; --vscode-editor-foreground: #333333; --vscode-editor-background: #ffffff; + --vscode-editorGroup-border: #cecece; --vscode-button-foreground: #ffffff; --vscode-button-background: #007acc; --vscode-button-hoverBackground: #0062a3; @@ -24,6 +25,7 @@ --vscode-toolbar-hoverBackground: #e8e8e8; --vscode-widget-border: #d4d4d4; --vscode-widget-shadow: #00000029; + --vscode-textLink-foreground: #006ab1; --vscode-menu-foreground: #3b3b3b; --vscode-menu-background: #ffffff; --vscode-editorHoverWidget-foreground: #3b3b3b; diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 4cf57d1e3e..c761ab809b 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -33,6 +33,7 @@ import ContextMenu from "./ContextMenu" import { IndexingStatusBadge } from "./IndexingStatusBadge" import { ZooCodeAuthBadge } from "./ZooCodeAuthBadge" import { usePromptHistory } from "./hooks/usePromptHistory" +import { disabledChatControlClassName, enabledChatControlClassName } from "./chatControlStyles" interface ChatTextAreaProps { inputValue: string @@ -1138,12 +1139,9 @@ export const ChatTextArea = forwardRef( !shouldDisableImages ? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto" : "opacity-0 pointer-events-none duration-200 delay-0", - !shouldDisableImages && - "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + !shouldDisableImages && enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - !shouldDisableImages && "active:bg-[rgba(255,255,255,0.1)]", - shouldDisableImages && - "opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent", + shouldDisableImages && disabledChatControlClassName, )}> @@ -1160,9 +1158,8 @@ export const ChatTextArea = forwardRef( "rounded-md min-w-[28px] min-h-[28px]", "opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground", "transition-all duration-150", - "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - "active:bg-[rgba(255,255,255,0.1)]", "cursor-pointer", )}> @@ -1184,10 +1181,8 @@ export const ChatTextArea = forwardRef( hasInputContent ? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto" : "opacity-0 pointer-events-none duration-200 delay-0", - hasInputContent && - "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + hasInputContent && enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - hasInputContent && "active:bg-[rgba(255,255,255,0.1)]", )}> ( "text-vscode-descriptionForeground hover:text-vscode-foreground", "transition-all duration-200", "opacity-100 hover:opacity-100 pointer-events-auto", - "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - "active:bg-[rgba(255,255,255,0.1)]", "cursor-pointer", )}> @@ -1247,13 +1241,11 @@ export const ChatTextArea = forwardRef( ? "opacity-100 hover:opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none", (isEditMode || isStreaming || hasInputContent) && - "hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - (isEditMode || isStreaming || hasInputContent) && - "active:bg-[rgba(255,255,255,0.1)]", (isEditMode || isStreaming || hasInputContent) && "cursor-pointer", isStreaming && - "bg-vscode-button-background hover:bg-vscode-button-background", + "bg-vscode-button-background hover:bg-vscode-button-background active:bg-vscode-button-background", )}> {isStreaming ? ( @@ -1333,9 +1325,8 @@ export const ChatTextArea = forwardRef( "rounded-md min-w-[28px] min-h-[28px]", "text-vscode-foreground opacity-85", "transition-all duration-150", - "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", + enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - "active:bg-[rgba(255,255,255,0.1)]", "cursor-pointer", )}> diff --git a/webview-ui/src/components/chat/CodebaseSearchResult.tsx b/webview-ui/src/components/chat/CodebaseSearchResult.tsx index 8280ea3d47..74249c6f73 100644 --- a/webview-ui/src/components/chat/CodebaseSearchResult.tsx +++ b/webview-ui/src/components/chat/CodebaseSearchResult.tsx @@ -30,15 +30,15 @@ const CodebaseSearchResult: React.FC = ({ filePath, s
+ className="group p-2 border border-vscode-editorGroup-border cursor-pointer hover:bg-vscode-list-hoverBackground">
- + {filePath.split("/").at(-1)}:{startLine === endLine ? startLine : `${startLine}-${endLine}`} - + {filePath.split("/").slice(0, -1).join("/")} - + {score.toFixed(3)}
diff --git a/webview-ui/src/components/chat/IconButton.tsx b/webview-ui/src/components/chat/IconButton.tsx index 00210ac5b3..d2acd53b00 100644 --- a/webview-ui/src/components/chat/IconButton.tsx +++ b/webview-ui/src/components/chat/IconButton.tsx @@ -1,5 +1,6 @@ import { cn } from "@src/lib/utils" import { Button, StandardTooltip } from "@src/components/ui" +import { disabledChatControlClassName, enabledChatControlClassName } from "./chatControlStyles" interface IconButtonProps extends React.ButtonHTMLAttributes { iconClass: string @@ -30,12 +31,9 @@ export const IconButton: React.FC = ({ "rounded-md min-w-[28px] min-h-[28px]", "text-vscode-foreground opacity-85", "transition-all duration-150", - "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)]", "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", - "active:bg-[rgba(255,255,255,0.1)]", - !disabled && "cursor-pointer", - disabled && - "opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent hover:border-[rgba(255,255,255,0.08)] active:bg-transparent", + !disabled && cn("cursor-pointer", enabledChatControlClassName), + disabled && disabledChatControlClassName, className, )} disabled={disabled} diff --git a/webview-ui/src/components/chat/IndexingStatusBadge.tsx b/webview-ui/src/components/chat/IndexingStatusBadge.tsx index 227df3e645..22dbe8f9d5 100644 --- a/webview-ui/src/components/chat/IndexingStatusBadge.tsx +++ b/webview-ui/src/components/chat/IndexingStatusBadge.tsx @@ -11,6 +11,7 @@ import { useExtensionState } from "@src/context/ExtensionStateContext" import { PopoverTrigger, StandardTooltip, Button } from "@src/components/ui" import { CodeIndexPopover } from "./CodeIndexPopover" +import { enabledChatControlClassName } from "./chatControlStyles" interface IndexingStatusBadgeProps { className?: string @@ -96,7 +97,7 @@ export const IndexingStatusBadge: React.FC = ({ classN className={cn( "relative h-5 w-5 p-0", "text-vscode-foreground opacity-85", - "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]", + enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", className, )}> diff --git a/webview-ui/src/components/chat/LucideIconButton.tsx b/webview-ui/src/components/chat/LucideIconButton.tsx index a527df3f1d..4f53b1248b 100644 --- a/webview-ui/src/components/chat/LucideIconButton.tsx +++ b/webview-ui/src/components/chat/LucideIconButton.tsx @@ -2,6 +2,7 @@ import { forwardRef } from "react" import { cn } from "@src/lib/utils" import { Button, StandardTooltip } from "@src/components/ui" import { Loader2, LucideIcon } from "lucide-react" +import { disabledChatControlClassName, enabledChatControlClassName } from "./chatControlStyles" interface LucideIconButtonProps extends React.ButtonHTMLAttributes { icon: LucideIcon @@ -27,9 +28,8 @@ export const LucideIconButton = forwardRef Math.random().toString(36).slice(2, 10) @@ -336,7 +333,7 @@ const UpdateTodoListToolBlock: React.FC = ({ style={{ border: "none", background: "transparent", - color: "#f14c4c", + color: "var(--vscode-errorForeground)", cursor: "pointer", fontSize: 14, marginLeft: 2, @@ -371,7 +368,7 @@ const UpdateTodoListToolBlock: React.FC = ({ fontSize: 13, marginRight: 6, padding: "1px 3px", - borderBottom: "1px solid #eee", + borderBottom: "1px solid var(--vscode-input-border)", }} />
- {/* Delete confirmation dialog */} - {deleteId && ( -
-
e.stopPropagation()}> -
- Are you sure you want to delete this todo item? -
-
- - -
-
-
- )} + + + Delete todo item + Are you sure you want to delete this todo item? + + + Cancel + + + Delete + + + + ) } diff --git a/webview-ui/src/components/chat/WorktreeSelector.tsx b/webview-ui/src/components/chat/WorktreeSelector.tsx index 938fa2cec7..0908516d32 100644 --- a/webview-ui/src/components/chat/WorktreeSelector.tsx +++ b/webview-ui/src/components/chat/WorktreeSelector.tsx @@ -11,6 +11,7 @@ import { vscode } from "@/utils/vscode" import { CreateWorktreeModal } from "../worktrees/CreateWorktreeModal" import { IconButton } from "./IconButton" +import { enabledChatControlClassName } from "./chatControlStyles" interface WorktreeSelectorProps { disabled?: boolean @@ -95,7 +96,7 @@ export const WorktreeSelector = ({ disabled = false }: WorktreeSelectorProps) => "transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset", disabled ? "opacity-50 cursor-not-allowed" - : "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer", + : cn("opacity-90 cursor-pointer", enabledChatControlClassName), )}> {t("worktrees:selector.worktree")}: diff --git a/webview-ui/src/components/chat/__tests__/ChatControlButtons.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatControlButtons.spec.tsx new file mode 100644 index 0000000000..e22dca8984 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/ChatControlButtons.spec.tsx @@ -0,0 +1,42 @@ +import { fireEvent, render, screen } from "@/utils/test-utils" +import { CircleHelp } from "lucide-react" +import { describe, expect, test, vi } from "vitest" + +import { IconButton } from "../IconButton" +import { LucideIconButton } from "../LucideIconButton" + +describe("chat control buttons", () => { + test("invokes enabled codicon controls", () => { + const onClick = vi.fn() + render() + + fireEvent.click(screen.getByRole("button", { name: "Settings" })) + expect(onClick).toHaveBeenCalledOnce() + }) + + test("keeps disabled codicon controls inert", () => { + const onClick = vi.fn() + render( + , + ) + + const button = screen.getByRole("button", { name: "Settings" }) + expect(button).toBeDisabled() + fireEvent.click(button) + expect(onClick).not.toHaveBeenCalled() + }) + + test("renders enabled and disabled Lucide controls", () => { + const { rerender } = render() + expect(screen.getByRole("button", { name: "Help" })).toBeEnabled() + + rerender() + expect(screen.getByRole("button", { name: "Help" })).toBeDisabled() + }) +}) diff --git a/webview-ui/src/components/chat/__tests__/CodebaseSearchResult.spec.tsx b/webview-ui/src/components/chat/__tests__/CodebaseSearchResult.spec.tsx new file mode 100644 index 0000000000..3cebafa598 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/CodebaseSearchResult.spec.tsx @@ -0,0 +1,38 @@ +import { fireEvent, render, screen } from "@/utils/test-utils" +import { beforeEach, describe, expect, test, vi } from "vitest" + +import CodebaseSearchResult from "../CodebaseSearchResult" +import { vscode } from "@/utils/vscode" + +vi.mock("@/utils/vscode", () => ({ vscode: { postMessage: vi.fn() } })) + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})) + +describe("CodebaseSearchResult", () => { + beforeEach(() => vi.clearAllMocks()) + + test("opens the selected file at the result start line", () => { + render( + , + ) + + const fileName = screen.getByText("example.ts:12-18") + expect(fileName).toHaveClass("group-hover:text-vscode-list-hoverForeground") + expect(screen.getByText("src")).toHaveClass("group-hover:text-vscode-list-hoverForeground") + fireEvent.click(fileName) + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "openFile", + text: "./src/example.ts", + values: { line: 12 }, + }) + }) +}) diff --git a/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx b/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx new file mode 100644 index 0000000000..67f564c300 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx @@ -0,0 +1,61 @@ +import React from "react" + +import { expect, test } from "../../../../playwright/coverage-fixture" +import { Checkbox } from "@/components/ui/checkbox" +import { enabledChatControlClassName } from "../chatControlStyles" + +const themes = [ + { + name: "dark", + bodyClass: "vscode-dark", + themeId: "Default Dark Modern", + expected: { + hover: "rgb(42, 45, 46)", + active: "rgb(42, 45, 46)", + description: "rgb(157, 157, 157)", + background: "rgb(30, 30, 30)", + }, + }, + { + name: "light", + bodyClass: "vscode-light", + themeId: "Default Light Modern", + expected: { + hover: "rgb(232, 232, 232)", + active: "rgb(232, 232, 232)", + description: "rgb(113, 113, 113)", + background: "rgb(255, 255, 255)", + }, + }, +] as const + +for (const theme of themes) { + test(`renders remaining controls in the VS Code ${theme.name} theme`, async ({ mount, page }) => { + await page.evaluate(({ bodyClass, themeId }) => { + document.documentElement.className = bodyClass + document.body.className = bodyClass + document.body.dataset.vscodeThemeId = themeId + }, theme) + + const component = await mount( +
+ + +
, + ) + + const iconButton = component.getByRole("button", { name: "Settings" }) + await iconButton.hover() + await expect(iconButton).toHaveCSS("background-color", theme.expected.hover) + await iconButton.focus() + await page.mouse.down() + await expect(iconButton).toHaveCSS("background-color", theme.expected.active) + await page.mouse.up() + + const checkbox = component.getByRole("checkbox", { name: "Include optional context" }) + await expect(checkbox).toHaveCSS("background-color", theme.expected.description) + await expect(checkbox).toHaveCSS("color", theme.expected.background) + }) +} diff --git a/webview-ui/src/components/chat/__tests__/UpdateTodoListToolBlock.spec.tsx b/webview-ui/src/components/chat/__tests__/UpdateTodoListToolBlock.spec.tsx new file mode 100644 index 0000000000..a8ca2dcf09 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/UpdateTodoListToolBlock.spec.tsx @@ -0,0 +1,42 @@ +import { fireEvent, render, screen } from "@testing-library/react" +import { describe, expect, test, vi } from "vitest" + +import UpdateTodoListToolBlock from "../UpdateTodoListToolBlock" + +describe("UpdateTodoListToolBlock", () => { + test("renders theme-aware edit controls", () => { + render( + , + ) + + fireEvent.click(screen.getByRole("button", { name: "Edit" })) + expect(screen.getByTitle("Remove")).toBeInTheDocument() + expect(screen.getByDisplayValue("Ship the cleanup")).toBeInTheDocument() + + fireEvent.click(screen.getByRole("button", { name: "+ Add Todo" })) + expect(screen.getByPlaceholderText("Enter todo item, press Enter to add")).toBeInTheDocument() + }) + + test("cancels and confirms todo deletion", () => { + const onChange = vi.fn() + render( + , + ) + + fireEvent.click(screen.getByRole("button", { name: "Edit" })) + fireEvent.click(screen.getByTitle("Remove")) + expect(screen.getByRole("alertdialog")).toBeInTheDocument() + fireEvent.click(screen.getByRole("button", { name: "Cancel" })) + expect(onChange).not.toHaveBeenCalled() + + fireEvent.click(screen.getByTitle("Remove")) + fireEvent.click(screen.getByRole("button", { name: "Delete" })) + expect(onChange).toHaveBeenCalledWith([]) + }) +}) diff --git a/webview-ui/src/components/chat/chatControlStyles.ts b/webview-ui/src/components/chat/chatControlStyles.ts new file mode 100644 index 0000000000..a0beb91494 --- /dev/null +++ b/webview-ui/src/components/chat/chatControlStyles.ts @@ -0,0 +1,5 @@ +export const enabledChatControlClassName = + "hover:opacity-100 hover:bg-vscode-toolbar-hoverBackground active:bg-vscode-list-hoverBackground" + +export const disabledChatControlClassName = + "opacity-40 cursor-not-allowed grayscale-[30%] hover:bg-transparent active:bg-transparent" diff --git a/webview-ui/src/components/ui/__tests__/checkbox.spec.tsx b/webview-ui/src/components/ui/__tests__/checkbox.spec.tsx new file mode 100644 index 0000000000..ab02e4b8ae --- /dev/null +++ b/webview-ui/src/components/ui/__tests__/checkbox.spec.tsx @@ -0,0 +1,12 @@ +import { render, screen } from "@/utils/test-utils" +import { describe, expect, test } from "vitest" + +import { Checkbox } from "../checkbox" + +describe("Checkbox", () => { + test("renders the description variant as checked", () => { + render() + + expect(screen.getByRole("checkbox", { name: "Include optional context" })).toBeChecked() + }) +}) diff --git a/webview-ui/src/components/ui/checkbox.tsx b/webview-ui/src/components/ui/checkbox.tsx index a4b005d00a..bfc069f852 100644 --- a/webview-ui/src/components/ui/checkbox.tsx +++ b/webview-ui/src/components/ui/checkbox.tsx @@ -15,7 +15,7 @@ const checkboxVariants = cva( default: "border-vscode-foreground data-[state=checked]:bg-primary data-[state=checked]:text-foreground", description: - "border-vscode-descriptionForeground data-[state=checked]:bg-vscode-descriptionForeground data-[state=checked]:text-white", + "border-vscode-descriptionForeground data-[state=checked]:bg-vscode-descriptionForeground data-[state=checked]:text-vscode-editor-background", }, }, defaultVariants: { @@ -25,14 +25,13 @@ const checkboxVariants = cva( ) export interface CheckboxProps - extends React.ComponentPropsWithoutRef, - VariantProps {} + extends React.ComponentPropsWithoutRef, VariantProps {} const Checkbox = React.forwardRef, CheckboxProps>( ({ className, variant, ...props }, ref) => ( - + ), From 173e4dbe3a0ebf6bfc84b4bb8a9048a9a1eb2ce6 Mon Sep 17 00:00:00 2001 From: Elliott de Launay Date: Fri, 21 Aug 2026 08:57:18 -0400 Subject: [PATCH 2/2] test(webview): add visual regression screenshots for remaining theme controls --- webview-ui/src/components/chat/ChatTextArea.tsx | 10 +++++----- .../chat/__tests__/ThemeTokenCleanup.visual.tsx | 9 +++++++++ .../remaining-controls-active-dark.png | Bin 0 -> 1662 bytes .../remaining-controls-active-light.png | Bin 0 -> 1623 bytes .../remaining-controls-resting-dark.png | Bin 0 -> 1501 bytes .../remaining-controls-resting-light.png | Bin 0 -> 1506 bytes 6 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-dark.png create mode 100644 webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-light.png create mode 100644 webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-resting-dark.png create mode 100644 webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-resting-light.png diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c761ab809b..db2ca80609 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -1137,7 +1137,7 @@ export const ChatTextArea = forwardRef( "transition-all duration-1000", "cursor-pointer", !shouldDisableImages - ? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto" + ? "opacity-50 delay-750 pointer-events-auto" : "opacity-0 pointer-events-none duration-200 delay-0", !shouldDisableImages && enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", @@ -1156,7 +1156,7 @@ export const ChatTextArea = forwardRef( "relative inline-flex items-center justify-center", "bg-transparent border-none p-1.5", "rounded-md min-w-[28px] min-h-[28px]", - "opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground", + "opacity-60 text-vscode-descriptionForeground hover:text-vscode-foreground", "transition-all duration-150", enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", @@ -1179,7 +1179,7 @@ export const ChatTextArea = forwardRef( "transition-all duration-1000", "cursor-pointer", hasInputContent - ? "opacity-50 hover:opacity-100 delay-750 pointer-events-auto" + ? "opacity-50 delay-750 pointer-events-auto" : "opacity-0 pointer-events-none duration-200 delay-0", hasInputContent && enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", @@ -1203,7 +1203,7 @@ export const ChatTextArea = forwardRef( "rounded-md min-w-[28px] min-h-[28px]", "text-vscode-descriptionForeground hover:text-vscode-foreground", "transition-all duration-200", - "opacity-100 hover:opacity-100 pointer-events-auto", + "opacity-100 pointer-events-auto", enabledChatControlClassName, "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder", "cursor-pointer", @@ -1238,7 +1238,7 @@ export const ChatTextArea = forwardRef( "text-vscode-descriptionForeground hover:text-vscode-foreground", "transition-all duration-200", isEditMode || isStreaming || hasInputContent - ? "opacity-100 hover:opacity-100 pointer-events-auto" + ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none", (isEditMode || isStreaming || hasInputContent) && enabledChatControlClassName, diff --git a/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx b/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx index 67f564c300..7c8ec1463e 100644 --- a/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx +++ b/webview-ui/src/components/chat/__tests__/ThemeTokenCleanup.visual.tsx @@ -46,6 +46,13 @@ for (const theme of themes) { , ) + await component.evaluate(async () => { + await document.fonts.ready + await new Promise((resolve) => requestAnimationFrame(() => resolve())) + }) + + await expect(component).toHaveScreenshot(`remaining-controls-resting-${theme.name}.png`) + const iconButton = component.getByRole("button", { name: "Settings" }) await iconButton.hover() await expect(iconButton).toHaveCSS("background-color", theme.expected.hover) @@ -54,6 +61,8 @@ for (const theme of themes) { await expect(iconButton).toHaveCSS("background-color", theme.expected.active) await page.mouse.up() + await expect(component).toHaveScreenshot(`remaining-controls-active-${theme.name}.png`) + const checkbox = component.getByRole("checkbox", { name: "Include optional context" }) await expect(checkbox).toHaveCSS("background-color", theme.expected.description) await expect(checkbox).toHaveCSS("color", theme.expected.background) diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-dark.png b/webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e6f84c0a585330fbc7cdab9d5cfd239850216cef GIT binary patch literal 1662 zcmV-^27&pBP)SF>=8usP0sye^-j9whEh+gpeQf~kfU>ebLHYo| z+e9NAck!ckYv0eUZKj9fE6SPhgcOal*K8MwELNY$ zSWOm((onuX+j(JM-JnPS-oQ-k1QYO6OoTqK$RN92S6yS_T#PE-m|s%7lMxQozdTGB zl`bP&XR`F$MMiDNNRtG!#o_3x-rhw3{wD;G|B|begdezVw(%0(viCMu@9vhlcu)G! zW+%5WFC{`wh{X4{)-;>kad{;hbdijD@2>ZLpG;mItCF_vkvCl#(wDCDbU@sP<%e@g z|EN4hqhjT5?$_oEjb?`=i^(k5lBs2hB#idj*rXWC>B>ee`T67W;fnAtSSrj-`2GbAQxBa{pgdCnoyFk;kew-%?kuOHdDeaGQeL6=`*d{-Q3 zZ8UMhVDpiajZIgsS%uNt=a1Ex{+@b%JVvFF_0}ESv2EA4{j!xg#t4Q{8}oBx9W52x zckFMlYV;o{J#|W5POesHtk}Ez%e^PsMXez*f+66a5J3J*wEc9X;^IAdV)n+eudB{q zIe(}mBZ1}+6OoV-A@p1`a}tpp=C*D=Qa_)JpiRzi_vk)E9J4vxjFOefB4b#WwX2Vl zBlwYtYazz<;v=C_!4^f^Uk`3ALY zW>XbsOi>fg!in5K56`a7NK|?9LaELAyfAIHh?aR?ru&P+^x(= zjP&fo#AbbUye!poaLF+|$O$y1(Es==bU=Q~*;`lrJzb3|J6ROThPYjS+53BbIM!xz zNW{@`x{fW#J6M$`5k~H|wVa`AV$ z?Abp@M+ty;c^20;=B9Y2kR;yH-qe6|4uSmVVpIYE;0r-RLqkzfQ3{1((V|5J0AS$> zg3#C3S6*Ho5Ed5pz`O&me#Rz-JIZ0N^v8KO_qc4K)}H%a$!uC=@R(8Xq6OfB$}0SJ%YE z1OWi>k^rq%TUc1AR4V5zbvm83wYA6~0PvPT+WX8WeJEPCX3ZJ`0N^FT%a<>oe|2nZ zECB%Uk~g~=9+%DofEUfZ3N|t_QdwE~=+Pqr0N_<~+?I!?Pg7IViWMuCE?r6h0KDpT z0rcR(1DZZbNlEGH=>!14+h&JgT!nwpArcK}#8vjL<~C<+P+9zT9eGiWA#j7H0s!D8fwykm62*DluA7#1c6Jf~051tzvSi7?z`)|gi)ruks=G7OMS6?H zLg%M31Ooss3W$n|A^-rs@DBh00RR8}C12bC000I_L_t&o03MKT;n&1x^#A|>07*qo IM6N<$f`CyJ3;+NC literal 0 HcmV?d00001 diff --git a/webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-light.png b/webview-ui/src/components/chat/__tests__/__screenshots__/remaining-controls-active-light.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6fa9c6c54099a5ecb5298e06dd7578beb1ecb0 GIT binary patch literal 1623 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