-
-
-
- {showTooltip && (
-
- {getTooltipText()}
-
- )}
-
+ "relative h-7 w-7 p-0",
+ "text-vscode-foreground opacity-85",
+ "hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)]",
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
+ className,
+ )}>
+
+
+
+
+
+
)
}
diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx
index 93dd2f1f4f..2ae9279fa8 100644
--- a/webview-ui/src/components/chat/ModeSelector.tsx
+++ b/webview-ui/src/components/chat/ModeSelector.tsx
@@ -1,26 +1,28 @@
import React from "react"
+import { Fzf } from "fzf"
import { ChevronUp, Check, X } from "lucide-react"
+
+import { type ModeConfig, type CustomModePrompts, TelemetryEventName } from "@roo-code/types"
+
+import { type Mode, getAllModes } from "@roo/modes"
+
+import { vscode } from "@/utils/vscode"
+import { telemetryClient } from "@/utils/TelemetryClient"
import { cn } from "@/lib/utils"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import { useAppTranslation } from "@/i18n/TranslationContext"
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
+
import { IconButton } from "./IconButton"
-import { vscode } from "@/utils/vscode"
-import { useExtensionState } from "@/context/ExtensionStateContext"
-import { useAppTranslation } from "@/i18n/TranslationContext"
-import { Mode, getAllModes } from "@roo/modes"
-import { ModeConfig, CustomModePrompts } from "@roo-code/types"
-import { telemetryClient } from "@/utils/TelemetryClient"
-import { TelemetryEventName } from "@roo-code/types"
-import { Fzf } from "fzf"
-// Minimum number of modes required to show search functionality
const SEARCH_THRESHOLD = 6
interface ModeSelectorProps {
value: Mode
onChange: (value: Mode) => void
disabled?: boolean
- title?: string
+ title: string
triggerClassName?: string
modeShortcutText: string
customModes?: ModeConfig[]
@@ -32,7 +34,7 @@ export const ModeSelector = ({
value,
onChange,
disabled = false,
- title = "",
+ title,
triggerClassName = "",
modeShortcutText,
customModes,
@@ -47,29 +49,31 @@ export const ModeSelector = ({
const { t } = useAppTranslation()
const trackModeSelectorOpened = React.useCallback(() => {
- // Track telemetry every time the mode selector is opened
+ // Track telemetry every time the mode selector is opened.
telemetryClient.capture(TelemetryEventName.MODE_SELECTOR_OPENED)
- // Track first-time usage for UI purposes
+ // Track first-time usage for UI purposes.
if (!hasOpenedModeSelector) {
setHasOpenedModeSelector(true)
vscode.postMessage({ type: "hasOpenedModeSelector", bool: true })
}
}, [hasOpenedModeSelector, setHasOpenedModeSelector])
- // Get all modes including custom modes and merge custom prompt descriptions
+ // Get all modes including custom modes and merge custom prompt descriptions.
const modes = React.useMemo(() => {
const allModes = getAllModes(customModes)
+
return allModes.map((mode) => ({
...mode,
description: customModePrompts?.[mode.slug]?.description ?? mode.description,
}))
}, [customModes, customModePrompts])
- // Find the selected mode
+ // Find the selected mode.
const selectedMode = React.useMemo(() => modes.find((mode) => mode.slug === value), [modes, value])
- // Memoize searchable items for fuzzy search with separate name and description search
+ // Memoize searchable items for fuzzy search with separate name and
+ // description search.
const nameSearchItems = React.useMemo(() => {
return modes.map((mode) => ({
original: mode,
@@ -84,31 +88,29 @@ export const ModeSelector = ({
}))
}, [modes])
- // Create memoized Fzf instances for name and description searches
- const nameFzfInstance = React.useMemo(() => {
- return new Fzf(nameSearchItems, {
- selector: (item) => item.searchStr,
- })
- }, [nameSearchItems])
+ // Create memoized Fzf instances for name and description searches.
+ const nameFzfInstance = React.useMemo(
+ () => new Fzf(nameSearchItems, { selector: (item) => item.searchStr }),
+ [nameSearchItems],
+ )
- const descriptionFzfInstance = React.useMemo(() => {
- return new Fzf(descriptionSearchItems, {
- selector: (item) => item.searchStr,
- })
- }, [descriptionSearchItems])
+ const descriptionFzfInstance = React.useMemo(
+ () => new Fzf(descriptionSearchItems, { selector: (item) => item.searchStr }),
+ [descriptionSearchItems],
+ )
- // Filter modes based on search value using fuzzy search with priority
+ // Filter modes based on search value using fuzzy search with priority.
const filteredModes = React.useMemo(() => {
if (!searchValue) return modes
- // First search in names/slugs
+ // First search in names/slugs.
const nameMatches = nameFzfInstance.find(searchValue)
const nameMatchedModes = new Set(nameMatches.map((result) => result.item.original.slug))
- // Then search in descriptions
+ // Then search in descriptions.
const descriptionMatches = descriptionFzfInstance.find(searchValue)
- // Combine results: name matches first, then description matches
+ // Combine results: name matches first, then description matches.
const combinedResults = [
...nameMatches.map((result) => result.item.original),
...descriptionMatches
@@ -128,7 +130,7 @@ export const ModeSelector = ({
(modeSlug: string) => {
onChange(modeSlug as Mode)
setOpen(false)
- // Clear search after selection
+ // Clear search after selection.
setSearchValue("")
},
[onChange],
@@ -138,7 +140,8 @@ export const ModeSelector = ({
(isOpen: boolean) => {
if (isOpen) trackModeSelectorOpened()
setOpen(isOpen)
- // Clear search when closing
+
+ // Clear search when closing.
if (!isOpen) {
setSearchValue("")
}
@@ -146,44 +149,46 @@ export const ModeSelector = ({
[trackModeSelectorOpened],
)
- // Auto-focus search input when popover opens
+ // Auto-focus search input when popover opens.
React.useEffect(() => {
if (open && searchInputRef.current) {
searchInputRef.current.focus()
}
}, [open])
- // Determine if search should be shown
+ // Determine if search should be shown.
const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD
- // Combine instruction text for tooltip
+ // Combine instruction text for tooltip.
const instructionText = `${t("chat:modeSelector.description")} ${modeShortcutText}`
- const trigger = (
-
-
- {selectedMode?.name || ""}
-
- )
-
return (
- {title ? {trigger} : trigger}
-
+
+
+
+ {selectedMode?.name || ""}
+
+
{
- vscode.postMessage({
- type: "switchTab",
- tab: "modes",
- })
+ vscode.postMessage({ type: "switchTab", tab: "modes" })
setOpen(false)
}}
/>
@@ -300,5 +302,3 @@ export const ModeSelector = ({
)
}
-
-export default ModeSelector
diff --git a/webview-ui/src/components/chat/SlashCommandsPopover.tsx b/webview-ui/src/components/chat/SlashCommandsPopover.tsx
index 3a8bb8ae43..451eefede2 100644
--- a/webview-ui/src/components/chat/SlashCommandsPopover.tsx
+++ b/webview-ui/src/components/chat/SlashCommandsPopover.tsx
@@ -41,26 +41,24 @@ export const SlashCommandsPopover: React.FC
= ({ clas
}
}
- const trigger = (
-
-
-
- )
-
return (
- {trigger}
+
+
+
+
+
{
const defaultProps = {
value: "config1",
displayName: "Config 1",
+ title: "API Config",
onChange: mockOnChange,
listApiConfigMeta: [
{ id: "config1", name: "Config 1" },
@@ -75,8 +76,8 @@ describe("ApiConfigSelector", () => {
render()
const trigger = screen.getByTestId("dropdown-trigger")
- // Check for the icon by looking for the codicon span element
- const icon = trigger.querySelector(".codicon-chevron-up")
+ // Check for the icon by looking for the svg element (ChevronUp from lucide-react renders as svg)
+ const icon = trigger.querySelector("svg")
expect(icon).toBeInTheDocument()
})
diff --git a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx
index c056640c2f..23c365ab59 100644
--- a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx
@@ -1,12 +1,11 @@
-import { render, fireEvent, screen } from "@/utils/test-utils"
-
import { defaultModeSlug } from "@roo/modes"
+import { render, fireEvent, screen } from "@src/utils/test-utils"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { vscode } from "@src/utils/vscode"
import * as pathMentions from "@src/utils/path-mentions"
-import ChatTextArea from "../ChatTextArea"
+import { ChatTextArea } from "../ChatTextArea"
vi.mock("@src/utils/vscode", () => ({
vscode: {
@@ -1058,54 +1057,4 @@ describe("ChatTextArea", () => {
expect(apiConfigDropdown).toHaveAttribute("disabled")
})
})
- describe("edit mode integration", () => {
- it("should render edit mode UI when isEditMode is true", () => {
- ;(useExtensionState as ReturnType).mockReturnValue({
- filePaths: [],
- openedTabs: [],
- taskHistory: [],
- cwd: "/test/workspace",
- customModes: [],
- customModePrompts: {},
- })
-
- render()
-
- // The edit mode UI should be rendered
- // We can verify this by checking for the presence of elements that are unique to edit mode
- const cancelButton = screen.getByRole("button", { name: /cancel/i })
- expect(cancelButton).toBeInTheDocument()
-
- // Should show save button instead of send button
- const saveButton = screen.getByRole("button", { name: /save/i })
- expect(saveButton).toBeInTheDocument()
-
- // Should not show send button in edit mode
- const sendButton = screen.queryByRole("button", { name: /send.*message/i })
- expect(sendButton).not.toBeInTheDocument()
- })
-
- it("should not render edit mode UI when isEditMode is false", () => {
- ;(useExtensionState as ReturnType).mockReturnValue({
- filePaths: [],
- openedTabs: [],
- taskHistory: [],
- cwd: "/test/workspace",
- })
-
- render()
-
- // The edit mode UI should not be rendered
- const cancelButton = screen.queryByRole("button", { name: /cancel/i })
- expect(cancelButton).not.toBeInTheDocument()
-
- // Should show send button when not in edit mode
- const sendButton = screen.getByRole("button", { name: /send.*message/i })
- expect(sendButton).toBeInTheDocument()
-
- // Should not show save button when not in edit mode
- const saveButton = screen.queryByRole("button", { name: /save/i })
- expect(saveButton).not.toBeInTheDocument()
- })
- })
})
diff --git a/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx
index 4b23166bce..96efb00673 100644
--- a/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx
@@ -73,16 +73,19 @@ vi.mock("react-i18next", () => ({
}))
vi.mock("../ChatTextArea", () => {
+ const ChatTextAreaComponent = React.forwardRef(function MockChatTextArea(
+ _props: any,
+ ref: React.ForwardedRef<{ focus: () => void }>,
+ ) {
+ React.useImperativeHandle(ref, () => ({
+ focus: vi.fn(),
+ }))
+ return
+ })
+
return {
- default: React.forwardRef(function MockChatTextArea(
- _props: any,
- ref: React.ForwardedRef<{ focus: () => void }>,
- ) {
- React.useImperativeHandle(ref, () => ({
- focus: vi.fn(),
- }))
- return
- }),
+ default: ChatTextAreaComponent,
+ ChatTextArea: ChatTextAreaComponent, // Export as named export too
}
})
diff --git a/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx
index 19538ef932..adea2390b9 100644
--- a/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx
@@ -181,30 +181,33 @@ vi.mock("../ChatTextArea", () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const mockReact = require("react")
+ const ChatTextAreaComponent = mockReact.forwardRef(function MockChatTextArea(
+ props: ChatTextAreaProps,
+ ref: React.ForwardedRef<{ focus: () => void }>,
+ ) {
+ // Use useImperativeHandle to expose the mock focus method
+ mockReact.useImperativeHandle(ref, () => ({
+ focus: mockFocus,
+ }))
+
+ return (
+
+ {
+ // With message queueing, onSend is always called (it handles queueing internally)
+ props.onSend(e.target.value)
+ }}
+ data-sending-disabled={props.sendingDisabled}
+ />
+
+ )
+ })
+
return {
- default: mockReact.forwardRef(function MockChatTextArea(
- props: ChatTextAreaProps,
- ref: React.ForwardedRef<{ focus: () => void }>,
- ) {
- // Use useImperativeHandle to expose the mock focus method
- React.useImperativeHandle(ref, () => ({
- focus: mockFocus,
- }))
-
- return (
-
- {
- // With message queueing, onSend is always called (it handles queueing internally)
- props.onSend(e.target.value)
- }}
- data-sending-disabled={props.sendingDisabled}
- />
-
- )
- }),
+ default: ChatTextAreaComponent,
+ ChatTextArea: ChatTextAreaComponent, // Export as named export too
}
})
diff --git a/webview-ui/src/components/chat/__tests__/EditModeControls.spec.tsx b/webview-ui/src/components/chat/__tests__/EditModeControls.spec.tsx
deleted file mode 100644
index 2b72202b32..0000000000
--- a/webview-ui/src/components/chat/__tests__/EditModeControls.spec.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import React from "react"
-import { render, screen, fireEvent } from "@testing-library/react"
-import { describe, it, expect, vi, beforeEach } from "vitest"
-import { EditModeControls } from "../EditModeControls"
-import { Mode } from "@roo/modes"
-
-// Mock the translation hook
-vi.mock("@/i18n/TranslationContext", () => ({
- useAppTranslation: () => ({
- t: (key: string) => key,
- }),
-}))
-
-// Mock the UI components
-vi.mock("@/components/ui", () => ({
- Button: ({ children, onClick, disabled, ...props }: any) => (
-
- ),
- StandardTooltip: ({ children, content }: any) => {children}
,
-}))
-
-// Mock ModeSelector
-vi.mock("../ModeSelector", () => ({
- default: ({ value, onChange, title }: any) => (
-
- ),
-}))
-
-describe("EditModeControls", () => {
- const defaultProps = {
- mode: "code" as Mode,
- onModeChange: vi.fn(),
- modeShortcutText: "Ctrl+M",
- customModes: [],
- customModePrompts: {},
- onCancel: vi.fn(),
- onSend: vi.fn(),
- onSelectImages: vi.fn(),
- sendingDisabled: false,
- shouldDisableImages: false,
- }
-
- beforeEach(() => {
- vi.clearAllMocks()
- })
-
- it("renders all controls correctly", () => {
- render()
-
- // Check for mode selector
- expect(screen.getByTitle("chat:selectMode")).toBeInTheDocument()
-
- // Check for Cancel button
- expect(screen.getByText("Cancel")).toBeInTheDocument()
-
- // Check for image button
- expect(screen.getByTitle("chat:addImages")).toBeInTheDocument()
-
- // Check for send button
- expect(screen.getByTitle("chat:save.tooltip")).toBeInTheDocument()
- })
-
- it("calls onCancel when Cancel button is clicked", () => {
- render()
-
- const cancelButton = screen.getByText("Cancel")
- fireEvent.click(cancelButton)
-
- expect(defaultProps.onCancel).toHaveBeenCalledTimes(1)
- })
-
- it("calls onSend when send button is clicked", () => {
- render()
-
- const sendButton = screen.getByLabelText("chat:save.tooltip")
- fireEvent.click(sendButton)
-
- expect(defaultProps.onSend).toHaveBeenCalledTimes(1)
- })
-
- it("calls onSelectImages when image button is clicked", () => {
- render()
-
- const imageButton = screen.getByLabelText("chat:addImages")
- fireEvent.click(imageButton)
-
- expect(defaultProps.onSelectImages).toHaveBeenCalledTimes(1)
- })
-
- it("disables buttons when sendingDisabled is true", () => {
- render()
-
- const cancelButton = screen.getByText("Cancel")
- const sendButton = screen.getByLabelText("chat:save.tooltip")
-
- expect(cancelButton).toBeDisabled()
- expect(sendButton).toBeDisabled()
- })
-
- it("disables image button when shouldDisableImages is true", () => {
- render()
-
- const imageButton = screen.getByLabelText("chat:addImages")
- expect(imageButton).toBeDisabled()
- })
-
- it("does not call onSelectImages when image button is disabled", () => {
- render()
-
- const imageButton = screen.getByLabelText("chat:addImages")
- fireEvent.click(imageButton)
-
- expect(defaultProps.onSelectImages).not.toHaveBeenCalled()
- })
-
- it("does not call onSend when send button is disabled", () => {
- render()
-
- const sendButton = screen.getByLabelText("chat:save.tooltip")
- fireEvent.click(sendButton)
-
- expect(defaultProps.onSend).not.toHaveBeenCalled()
- })
-
- it("calls onModeChange when mode is changed", () => {
- render()
-
- const modeSelector = screen.getByTitle("chat:selectMode")
- fireEvent.change(modeSelector, { target: { value: "architect" } })
-
- expect(defaultProps.onModeChange).toHaveBeenCalledWith("architect")
- })
-})
diff --git a/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx b/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx
index 37eb291530..25f92757f8 100644
--- a/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/IndexingStatusBadge.spec.tsx
@@ -55,16 +55,6 @@ vi.mock("@src/utils/vscode", () => ({
},
}))
-// Mock the useTooltip hook
-vi.mock("@/hooks/useTooltip", () => ({
- useTooltip: vi.fn(() => ({
- showTooltip: false,
- handleMouseEnter: vi.fn(),
- handleMouseLeave: vi.fn(),
- cleanup: vi.fn(),
- })),
-}))
-
// Mock the ExtensionStateContext
vi.mock("@/context/ExtensionStateContext", () => ({
useExtensionState: () => ({
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
index a829168893..e2fd310b21 100644
--- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
@@ -1,11 +1,11 @@
-import React from "react"
import { render, screen, fireEvent } from "@/utils/test-utils"
-import { describe, test, expect, vi } from "vitest"
-import ModeSelector from "../ModeSelector"
-import { Mode } from "@roo/modes"
-import { ModeConfig } from "@roo-code/types"
-// Mock the dependencies
+import type { ModeConfig } from "@roo-code/types"
+
+import type { Mode } from "@roo/modes"
+
+import { ModeSelector } from "../ModeSelector"
+
vi.mock("@/utils/vscode", () => ({
vscode: {
postMessage: vi.fn(),
@@ -35,7 +35,7 @@ vi.mock("@/utils/TelemetryClient", () => ({
},
}))
-// Create a variable to control what getAllModes returns
+// Create a variable to control what getAllModes returns.
let mockModes: ModeConfig[] = []
vi.mock("@roo/modes", async () => {
@@ -56,6 +56,7 @@ describe("ModeSelector", () => {
render(
{
/>,
)
- // The component should be rendered
expect(screen.getByTestId("mode-selector-trigger")).toBeInTheDocument()
})
test("falls back to default description when no custom prompt", () => {
- render()
+ render(
+ ,
+ )
- // The component should be rendered
expect(screen.getByTestId("mode-selector-trigger")).toBeInTheDocument()
})
test("shows search bar when there are more than 6 modes", () => {
- // Set up mock to return 7 modes
mockModes = Array.from({ length: 7 }, (_, i) => ({
slug: `mode-${i}`,
name: `Mode ${i}`,
@@ -84,22 +84,28 @@ describe("ModeSelector", () => {
groups: ["read", "edit"],
}))
- render()
+ render(
+ ,
+ )
- // Click to open the popover
+ // Click to open the popover.
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
- // Search input should be visible
+ // Search input should be visible.
expect(screen.getByTestId("mode-search-input")).toBeInTheDocument()
- // Info icon should be visible
+ // Info icon should be visible.
expect(screen.getByText("chat:modeSelector.title")).toBeInTheDocument()
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
test("shows info blurb instead of search bar when there are 6 or fewer modes", () => {
- // Set up mock to return 5 modes
mockModes = Array.from({ length: 5 }, (_, i) => ({
slug: `mode-${i}`,
name: `Mode ${i}`,
@@ -108,24 +114,30 @@ describe("ModeSelector", () => {
groups: ["read", "edit"],
}))
- render()
+ render(
+ ,
+ )
- // Click to open the popover
+ // Click to open the popover.
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
- // Search input should NOT be visible
+ // Search input should NOT be visible.
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
- // Info blurb should be visible
+ // Info blurb should be visible.
expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
- // Info icon should NOT be visible
+ // Info icon should NOT be visible.
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).not.toBeInTheDocument()
})
test("filters modes correctly when searching", () => {
- // Set up mock to return 7 modes to enable search
mockModes = Array.from({ length: 7 }, (_, i) => ({
slug: `mode-${i}`,
name: `Mode ${i}`,
@@ -134,22 +146,28 @@ describe("ModeSelector", () => {
groups: ["read", "edit"],
}))
- render()
+ render(
+ ,
+ )
- // Click to open the popover
+ // Click to open the popover.
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
- // Type in search
+ // Type in search.
const searchInput = screen.getByTestId("mode-search-input")
fireEvent.change(searchInput, { target: { value: "Mode 3" } })
- // Should show filtered results
+ // Should show filtered results.
const modeItems = screen.getAllByTestId("mode-selector-item")
- expect(modeItems.length).toBeLessThan(7) // Should have filtered some out
+ expect(modeItems.length).toBeLessThan(7) // Should have filtered some out.
})
test("respects disableSearch prop even when there are more than 6 modes", () => {
- // Set up mock to return 10 modes
mockModes = Array.from({ length: 10 }, (_, i) => ({
slug: `mode-${i}`,
name: `Mode ${i}`,
@@ -159,25 +177,30 @@ describe("ModeSelector", () => {
}))
render(
- ,
+ ,
)
- // Click to open the popover
+ // Click to open the popover.
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
- // Search input should NOT be visible even with 10 modes
+ // Search input should NOT be visible even with 10 modes.
expect(screen.queryByTestId("mode-search-input")).not.toBeInTheDocument()
- // Info blurb should be visible instead
+ // Info blurb should be visible instead.
expect(screen.getByText(/chat:modeSelector.description/)).toBeInTheDocument()
- // Info icon should NOT be visible
+ // Info icon should NOT be visible.
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).not.toBeInTheDocument()
})
test("shows search when disableSearch is false (default) and modes > 6", () => {
- // Set up mock to return 8 modes
mockModes = Array.from({ length: 8 }, (_, i) => ({
slug: `mode-${i}`,
name: `Mode ${i}`,
@@ -186,16 +209,20 @@ describe("ModeSelector", () => {
groups: ["read", "edit"],
}))
- // Don't pass disableSearch prop (should default to false)
- render()
+ // Don't pass disableSearch prop (should default to false).
+ render(
+ ,
+ )
- // Click to open the popover
fireEvent.click(screen.getByTestId("mode-selector-trigger"))
- // Search input should be visible
expect(screen.getByTestId("mode-search-input")).toBeInTheDocument()
- // Info icon should be visible
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
diff --git a/webview-ui/src/components/common/TelemetryBanner.tsx b/webview-ui/src/components/common/TelemetryBanner.tsx
index 63eb262b05..4fcd7fa170 100644
--- a/webview-ui/src/components/common/TelemetryBanner.tsx
+++ b/webview-ui/src/components/common/TelemetryBanner.tsx
@@ -3,7 +3,7 @@ import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import styled from "styled-components"
import { Trans } from "react-i18next"
-import { TelemetrySetting } from "@roo/TelemetrySetting"
+import type { TelemetrySetting } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx
index 5075643e6e..c25a1aebe3 100644
--- a/webview-ui/src/components/settings/About.tsx
+++ b/webview-ui/src/components/settings/About.tsx
@@ -2,11 +2,11 @@ import { HTMLAttributes } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { Trans } from "react-i18next"
import { Info, Download, Upload, TriangleAlert } from "lucide-react"
-
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
+import type { TelemetrySetting } from "@roo-code/types"
+
import { Package } from "@roo/package"
-import { TelemetrySetting } from "@roo/TelemetrySetting"
import { vscode } from "@/utils/vscode"
import { cn } from "@/lib/utils"
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 2738b82632..4d55bc69c1 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -25,11 +25,10 @@ import {
LucideIcon,
} from "lucide-react"
-import type { ProviderSettings, ExperimentId } from "@roo-code/types"
-
-import { TelemetrySetting } from "@roo/TelemetrySetting"
+import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
import { vscode } from "@src/utils/vscode"
+import { cn } from "@src/lib/utils"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { ExtensionStateContextType, useExtensionState } from "@src/context/ExtensionStateContext"
import {
@@ -65,7 +64,6 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
-import { cn } from "@/lib/utils"
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
export const settingsTabList =
diff --git a/webview-ui/src/components/ui/standard-tooltip.tsx b/webview-ui/src/components/ui/standard-tooltip.tsx
index 7dc4fffd67..a2b05386e6 100644
--- a/webview-ui/src/components/ui/standard-tooltip.tsx
+++ b/webview-ui/src/components/ui/standard-tooltip.tsx
@@ -1,13 +1,14 @@
-import * as React from "react"
+import { ReactNode } from "react"
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
export const STANDARD_TOOLTIP_DELAY = 300
interface StandardTooltipProps {
/** The element(s) that trigger the tooltip */
- children: React.ReactNode
+ children: ReactNode
/** The content to display in the tooltip */
- content: React.ReactNode
+ content: ReactNode
/** The preferred side of the trigger to render the tooltip */
side?: "top" | "right" | "bottom" | "left"
/** The preferred alignment against the trigger */
@@ -51,7 +52,7 @@ export function StandardTooltip({
asChild = true,
maxWidth,
}: StandardTooltipProps) {
- // Don't render tooltip if content is empty or only whitespace
+ // Don't render tooltip if content is empty or only whitespace.
if (!content || (typeof content === "string" && !content.trim())) {
return <>{children}>
}
diff --git a/webview-ui/src/components/ui/tooltip.tsx b/webview-ui/src/components/ui/tooltip.tsx
index d09d8cec9d..7e0573b293 100644
--- a/webview-ui/src/components/ui/tooltip.tsx
+++ b/webview-ui/src/components/ui/tooltip.tsx
@@ -1,33 +1,44 @@
+"use client"
+
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
-const TooltipProvider = TooltipPrimitive.Provider
+function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps) {
+ return
+}
-const Tooltip = TooltipPrimitive.Root
+function Tooltip({ ...props }: React.ComponentProps) {
+ return
+}
-const TooltipTrigger = TooltipPrimitive.Trigger
+function TooltipTrigger({ ...props }: React.ComponentProps) {
+ return
+}
-const TooltipContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
-
-
-))
-TooltipContent.displayName = TooltipPrimitive.Content.displayName
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index e632b27a31..e308ed5e64 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -7,10 +7,10 @@ import {
type ModeConfig,
type ExperimentId,
type TodoItem,
+ type TelemetrySetting,
} from "@roo-code/types"
import { type OrganizationAllowList, ORGANIZATION_ALLOW_ALL } from "@roo/cloud"
-
import { ExtensionMessage, ExtensionState, MarketplaceInstalledMetadata, Command } from "@roo/ExtensionMessage"
import { findLastIndex } from "@roo/array"
import { McpServer } from "@roo/mcp"
@@ -18,7 +18,6 @@ import { checkExistKey } from "@roo/checkExistApiConfig"
import { Mode, defaultModeSlug, defaultPrompts } from "@roo/modes"
import { CustomSupportPrompts } from "@roo/support-prompt"
import { experimentDefault } from "@roo/experiments"
-import { TelemetrySetting } from "@roo/TelemetrySetting"
import { RouterModels } from "@roo/api"
import { vscode } from "@src/utils/vscode"
diff --git a/webview-ui/src/hooks/useTooltip.ts b/webview-ui/src/hooks/useTooltip.ts
deleted file mode 100644
index f098017acf..0000000000
--- a/webview-ui/src/hooks/useTooltip.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { useState, useCallback, useRef } from "react"
-
-interface UseTooltipOptions {
- delay?: number
-}
-
-export const useTooltip = (options: UseTooltipOptions = {}) => {
- const { delay = 300 } = options
- const [showTooltip, setShowTooltip] = useState(false)
- const timeoutRef = useRef(null)
-
- const handleMouseEnter = useCallback(() => {
- if (timeoutRef.current) clearTimeout(timeoutRef.current)
- timeoutRef.current = setTimeout(() => setShowTooltip(true), delay)
- }, [delay])
-
- const handleMouseLeave = useCallback(() => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current)
- timeoutRef.current = null
- }
- setShowTooltip(false)
- }, [])
-
- // Cleanup on unmount
- const cleanup = useCallback(() => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current)
- timeoutRef.current = null
- }
- }, [])
-
- return {
- showTooltip,
- handleMouseEnter,
- handleMouseLeave,
- cleanup,
- }
-}
diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css
index fbb362ca8f..7448fc1f0f 100644
--- a/webview-ui/src/index.css
+++ b/webview-ui/src/index.css
@@ -76,6 +76,7 @@
--color-vscode-button-background: var(--vscode-button-background);
--color-vscode-button-secondaryForeground: var(--vscode-button-secondaryForeground);
--color-vscode-button-secondaryBackground: var(--vscode-button-secondaryBackground);
+ --color-vscode-button-hoverBackground: var(--vscode-button-hoverBackground);
--color-vscode-dropdown-foreground: var(--vscode-dropdown-foreground);
--color-vscode-dropdown-background: var(--vscode-dropdown-background);
@@ -107,6 +108,7 @@
--color-vscode-list-activeSelectionForeground: var(--vscode-list-activeSelectionForeground);
--color-vscode-toolbar-hoverBackground: var(--vscode-toolbar-hoverBackground);
+ --color-vscode-toolbar-hoverOutline: var(--vscode-toolbar-hoverOutline);
--color-vscode-panel-border: var(--vscode-panel-border);
@@ -129,9 +131,14 @@
--color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder);
--color-vscode-widget-border: var(--vscode-widget-border);
+
--color-vscode-textLink-foreground: var(--vscode-textLink-foreground);
+
--color-vscode-textCodeBlock-background: var(--vscode-textCodeBlock-background);
- --color-vscode-button-hoverBackground: var(--vscode-button-hoverBackground);
+
+ --color-vscode-editorHoverWidget-foreground: var(--vscode-editorHoverWidget-foreground);
+ --color-vscode-editorHoverWidget-background: var(--vscode-editorHoverWidget-background);
+ --color-vscode-editorHoverWidget-border: var(--vscode-editorHoverWidget-border);
}
@layer base {
diff --git a/webview-ui/src/utils/TelemetryClient.ts b/webview-ui/src/utils/TelemetryClient.ts
index cf2bfc54a5..dd587f51ed 100644
--- a/webview-ui/src/utils/TelemetryClient.ts
+++ b/webview-ui/src/utils/TelemetryClient.ts
@@ -1,6 +1,6 @@
import posthog from "posthog-js"
-import { TelemetrySetting } from "@roo/TelemetrySetting"
+import type { TelemetrySetting } from "@roo-code/types"
class TelemetryClient {
private static instance: TelemetryClient
diff --git a/webview-ui/src/utils/test-utils.tsx b/webview-ui/src/utils/test-utils.tsx
index 05560c3065..ad5659ec33 100644
--- a/webview-ui/src/utils/test-utils.tsx
+++ b/webview-ui/src/utils/test-utils.tsx
@@ -1,7 +1,8 @@
import React from "react"
import { render, RenderOptions } from "@testing-library/react"
-import { TooltipProvider } from "@/components/ui/tooltip"
-import { STANDARD_TOOLTIP_DELAY } from "@/components/ui/standard-tooltip"
+
+import { TooltipProvider } from "@src/components/ui/tooltip"
+import { STANDARD_TOOLTIP_DELAY } from "@src/components/ui/standard-tooltip"
interface AllTheProvidersProps {
children: React.ReactNode