diff --git a/.env.sample b/.env.sample
index 6cdaa1b3b1..4d6c24ac72 100644
--- a/.env.sample
+++ b/.env.sample
@@ -1,2 +1 @@
-# PostHog API Keys for telemetry
-POSTHOG_API_KEY=key-goes-here
\ No newline at end of file
+POSTHOG_API_KEY=key-goes-here
diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx
index b537b9298e..99dda495d0 100644
--- a/webview-ui/src/App.tsx
+++ b/webview-ui/src/App.tsx
@@ -17,6 +17,12 @@ import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
type Tab = "settings" | "history" | "mcp" | "prompts" | "chat"
+type HumanRelayDialogState = {
+ isOpen: boolean
+ requestId: string
+ promptText: string
+}
+
const tabsByMessageAction: Partial, Tab>> = {
chatButtonClicked: "chat",
settingsButtonClicked: "settings",
@@ -24,24 +30,21 @@ const tabsByMessageAction: Partial {
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
useExtensionState()
+
const [showAnnouncement, setShowAnnouncement] = useState(false)
const [tab, setTab] = useState("chat")
- const settingsRef = useRef(null)
-
- // Human Relay Dialog Status
- const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
- isOpen: boolean
- requestId: string
- promptText: string
- }>({
+ const [humanRelayDialogState, setHumanRelayDialogState] = useState({
isOpen: false,
requestId: "",
promptText: "",
})
+ const settingsRef = useRef(null)
+
const switchTab = useCallback((newTab: Tab) => {
if (settingsRef.current?.checkUnsaveChanges) {
settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
@@ -74,23 +77,6 @@ const App = () => {
[switchTab],
)
- // Processing Human Relay Dialog Submission
- const handleHumanRelaySubmit = (requestId: string, text: string) => {
- vscode.postMessage({
- type: "humanRelayResponse",
- requestId,
- text,
- })
- }
-
- // Handle Human Relay dialog box cancel
- const handleHumanRelayCancel = (requestId: string) => {
- vscode.postMessage({
- type: "humanRelayCancel",
- requestId,
- })
- }
-
useEvent("message", onMessage)
useEffect(() => {
@@ -106,7 +92,7 @@ const App = () => {
}
}, [telemetrySetting, telemetryKey, machineId, didHydrateState])
- // Tell Extension that we are ready to receive messages
+ // Tell the extension that we are ready to receive messages.
useEffect(() => {
vscode.postMessage({ type: "webviewDidLaunch" })
}, [])
@@ -121,24 +107,23 @@ const App = () => {
) : (
<>
- {tab === "settings" && setTab("chat")} />}
- {tab === "history" && switchTab("chat")} />}
- {tab === "mcp" && switchTab("chat")} />}
{tab === "prompts" && switchTab("chat")} />}
+ {tab === "mcp" && switchTab("chat")} />}
+ {tab === "history" && switchTab("chat")} />}
+ {tab === "settings" && setTab("chat")} />}
setShowAnnouncement(false)}
showHistoryView={() => switchTab("history")}
/>
- {/* Human Relay Dialog */}
setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))}
- onSubmit={handleHumanRelaySubmit}
- onCancel={handleHumanRelayCancel}
+ onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })}
+ onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })}
/>
>
)
@@ -147,6 +132,7 @@ const App = () => {
const AppWithProviders = () => (
+
)
diff --git a/webview-ui/src/components/chat/Announcement.tsx b/webview-ui/src/components/chat/Announcement.tsx
index 82fdb628eb..791ca26085 100644
--- a/webview-ui/src/components/chat/Announcement.tsx
+++ b/webview-ui/src/components/chat/Announcement.tsx
@@ -33,7 +33,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
What's New
-
+
- • Faster asynchronous checkpoints
- • Support for .rooignore files
@@ -44,7 +44,7 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
- • Updated DeepSeek provider
- • New "Human Relay" provider
-
+
Get more details and discuss in{" "}
diff --git a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
index 1910fc0bb6..63867c9858 100644
--- a/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
+++ b/webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback } from "react"
+import { useState, useCallback } from "react"
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
@@ -14,7 +14,6 @@ type CheckpointMenuProps = {
}
export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => {
- const [portalContainer, setPortalContainer] = useState()
const [isOpen, setIsOpen] = useState(false)
const [isConfirming, setIsConfirming] = useState(false)
@@ -42,15 +41,6 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
setIsOpen(false)
}, [ts, commitHash])
- useEffect(() => {
- // The dropdown menu uses a portal from @shadcn/ui which by default renders
- // at the document root. This causes the menu to remain visible even when
- // the parent ChatView component is hidden (during settings/history view).
- // By moving the portal inside ChatView, the menu will properly hide when
- // its parent is hidden.
- setPortalContainer(document.getElementById("chat-view-portal") || undefined)
- }, [])
-
return (
{isDiffAvailable && (
@@ -70,7 +60,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
-
+
{!isCurrent && (
diff --git a/webview-ui/src/components/common/Alert.tsx b/webview-ui/src/components/common/Alert.tsx
new file mode 100644
index 0000000000..b16e799b91
--- /dev/null
+++ b/webview-ui/src/components/common/Alert.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+import { HTMLAttributes } from "react"
+
+type AlertProps = HTMLAttributes
+
+export const Alert = ({ className, children, ...props }: AlertProps) => (
+
+ {children}
+
+)
diff --git a/webview-ui/src/components/common/Tab.tsx b/webview-ui/src/components/common/Tab.tsx
new file mode 100644
index 0000000000..982fb7e103
--- /dev/null
+++ b/webview-ui/src/components/common/Tab.tsx
@@ -0,0 +1,23 @@
+import { HTMLAttributes } from "react"
+
+import { cn } from "@/lib/utils"
+
+type TabProps = HTMLAttributes
+
+export const Tab = ({ className, children, ...props }: TabProps) => (
+
+ {children}
+
+)
+
+export const TabHeader = ({ className, children, ...props }: TabProps) => (
+
+ {children}
+
+)
+
+export const TabContent = ({ className, children, ...props }: TabProps) => (
+
+ {children}
+
+)
diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx
index e65a11a3ec..ec44f8eaca 100644
--- a/webview-ui/src/components/history/HistoryView.tsx
+++ b/webview-ui/src/components/history/HistoryView.tsx
@@ -9,6 +9,7 @@ import { formatLargeNumber, formatDate } from "@/utils/format"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui"
+import { Tab, TabContent, TabHeader } from "../common/Tab"
import { useTaskSearch } from "./useTaskSearch"
import { ExportButton } from "./ExportButton"
import { CopyButton } from "./CopyButton"
@@ -25,8 +26,8 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
const [deleteTaskId, setDeleteTaskId] = useState(null)
return (
-
-
+
+
History
Done
@@ -81,8 +82,9 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
-
-
+
+
+
{
)}
/>
-
+
+
{deleteTaskId && (
!open && setDeleteTaskId(null)} open />
)}
-
+
)
}
diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx
index 7a24922d88..ce37a4c09d 100644
--- a/webview-ui/src/components/mcp/McpView.tsx
+++ b/webview-ui/src/components/mcp/McpView.tsx
@@ -1,3 +1,4 @@
+import { useState } from "react"
import {
VSCodeButton,
VSCodeCheckbox,
@@ -6,14 +7,17 @@ import {
VSCodePanelTab,
VSCodePanelView,
} from "@vscode/webview-ui-toolkit/react"
-import { useState } from "react"
-import { vscode } from "../../utils/vscode"
-import { useExtensionState } from "../../context/ExtensionStateContext"
+
import { McpServer } from "../../../../src/shared/mcp"
+
+import { vscode } from "@/utils/vscode"
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui"
+
+import { useExtensionState } from "../../context/ExtensionStateContext"
+import { Tab, TabContent, TabHeader } from "../common/Tab"
import McpToolRow from "./McpToolRow"
import McpResourceRow from "./McpResourceRow"
import McpEnabledToggle from "./McpEnabledToggle"
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "../ui/dialog"
type McpViewProps = {
onDone: () => void
@@ -29,12 +33,13 @@ const McpView = ({ onDone }: McpViewProps) => {
} = useExtensionState()
return (
-
-
+
+
MCP Servers
Done
-
-
-
+
+
)
}
-// Server Row Component
const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => {
const [isExpanded, setIsExpanded] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx
index ccf1e6d700..d51d7d8909 100644
--- a/webview-ui/src/components/prompts/PromptsView.tsx
+++ b/webview-ui/src/components/prompts/PromptsView.tsx
@@ -42,6 +42,7 @@ import {
import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups"
import { vscode } from "../../utils/vscode"
+import { Tab, TabContent, TabHeader } from "../common/Tab"
// Get all available groups that should show in prompts view
const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable)
@@ -406,12 +407,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
}
return (
-
-
+
+
Prompts
Done
-
-
+
+
+
Preferred Language
@@ -934,6 +936,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
+
{
)}
-
+
+
{isCreateModeDialogOpen && (
{
)}
+
{isDialogOpen && (
{
)}
+
{isCustomLanguage && (
@@ -1497,7 +1503,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)}
-
+
)
}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index 7cafbd6663..df08a03971 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -22,6 +22,7 @@ import {
Button,
} from "@/components/ui"
+import { Tab, TabContent, TabHeader } from "../common/Tab"
import { SetCachedStateField, SetExperimentEnabled } from "./types"
import { SectionHeader } from "./SectionHeader"
import ApiConfigManager from "./ApiConfigManager"
@@ -263,53 +264,41 @@ const SettingsView = forwardRef(({ onDone },
const scrollToSection = (ref: React.RefObject) => ref.current?.scrollIntoView()
return (
-
-
-
-
-
-
Settings
-
- {sections.map(({ id, icon: Icon, ref }) => (
-
- ))}
-
-
-
-
- Save
-
- checkUnsaveChanges(onDone)}>
- Done
-
-
+
+
+
+
Settings
+
+ {sections.map(({ id, icon: Icon, ref }) => (
+
+ ))}
-
+
+
+ Save
+
+ checkUnsaveChanges(onDone)}>
+ Done
+
+
+
-
+
@@ -425,7 +414,7 @@ const SettingsView = forwardRef(({ onDone },
telemetrySetting={telemetrySetting}
setTelemetrySetting={setTelemetrySetting}
/>
-
+
@@ -442,7 +431,7 @@ const SettingsView = forwardRef(({ onDone },
-
+
)
})
diff --git a/webview-ui/src/components/ui/dropdown-menu.tsx b/webview-ui/src/components/ui/dropdown-menu.tsx
index fc5ad5b2b8..3193f497ca 100644
--- a/webview-ui/src/components/ui/dropdown-menu.tsx
+++ b/webview-ui/src/components/ui/dropdown-menu.tsx
@@ -53,23 +53,25 @@ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayNam
const DropdownMenuContent = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef & {
- container?: HTMLElement
- }
->(({ className, sideOffset = 4, container, ...props }, ref) => (
-
-
-
-))
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => {
+ const container = React.useMemo(() => document.getElementById("roo-portal"), [])
+
+ return (
+
+
+
+ )
+})
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
diff --git a/webview-ui/src/components/ui/popover.tsx b/webview-ui/src/components/ui/popover.tsx
index 3ab3344689..b6235853ca 100644
--- a/webview-ui/src/components/ui/popover.tsx
+++ b/webview-ui/src/components/ui/popover.tsx
@@ -11,23 +11,25 @@ const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef,
- React.ComponentPropsWithoutRef & {
- container?: HTMLElement
- }
->(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
-
-
-
-))
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => {
+ const container = React.useMemo(() => document.getElementById("roo-portal"), [])
+
+ return (
+
+
+
+ )
+})
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/webview-ui/src/components/ui/select-dropdown.tsx b/webview-ui/src/components/ui/select-dropdown.tsx
index 775066732d..eef474cd02 100644
--- a/webview-ui/src/components/ui/select-dropdown.tsx
+++ b/webview-ui/src/components/ui/select-dropdown.tsx
@@ -7,7 +7,6 @@ import {
DropdownMenuSeparator,
} from "./dropdown-menu"
import { cn } from "@/lib/utils"
-import { useEffect, useState } from "react"
// Constants for option types
export enum DropdownOptionType {
@@ -39,6 +38,8 @@ export interface SelectDropdownProps {
shortcutText?: string
}
+// TODO: Get rid of this and use the native @shadcn/ui `Select` component.
+
export const SelectDropdown = React.forwardRef, SelectDropdownProps>(
(
{
@@ -60,16 +61,6 @@ export const SelectDropdown = React.forwardRef {
// Track open state
const [open, setOpen] = React.useState(false)
- const [portalContainer, setPortalContainer] = useState()
-
- useEffect(() => {
- // The dropdown menu uses a portal from @shadcn/ui which by default renders
- // at the document root. This causes the menu to remain visible even when
- // the parent ChatView component is hidden (during settings/history view).
- // By moving the portal inside ChatView, the menu will properly hide when
- // its parent is hidden.
- setPortalContainer(document.getElementById("chat-view-portal") || undefined)
- }, [])
// Find the selected option label
const selectedOption = options.find((option) => option.value === value)
@@ -130,7 +121,6 @@ export const SelectDropdown = React.forwardRef setOpen(false)}
onInteractOutside={() => setOpen(false)}
- container={portalContainer}
className={cn(
"bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border z-50",
contentClassName,
diff --git a/webview-ui/src/components/ui/select.tsx b/webview-ui/src/components/ui/select.tsx
index 50f89b3760..5e673257cf 100644
--- a/webview-ui/src/components/ui/select.tsx
+++ b/webview-ui/src/components/ui/select.tsx
@@ -39,8 +39,10 @@ function SelectContent({
position = "popper",
...props
}: React.ComponentProps) {
+ const container = React.useMemo(() => document.getElementById("roo-portal"), [])
+
return (
-
+
{
const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState()
@@ -23,18 +26,16 @@ const WelcomeView = () => {
}, [apiConfiguration, currentApiConfigName])
return (
-
-
Hi, I'm Roo!
-
- I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and access
- to tools that let me create & edit files, explore complex projects, use the browser, and execute
- terminal commands (with your permission, of course). I can even use MCP to create new tools and extend
- my own capabilities.
-
-
-
To get started, this extension needs an API provider.
-
-
+
+
+ Hi, I'm Roo!
+
+ I can do all kinds of tasks thanks to the latest breakthroughs in agentic coding capabilities and
+ access to tools that let me create & edit files, explore complex projects, use the browser, and
+ execute terminal commands (with your permission, of course). I can even use MCP to create new tools
+ and extend my own capabilities.
+
+ To get started, this extension needs an API provider.
{
errorMessage={errorMessage}
setErrorMessage={setErrorMessage}
/>
-
-
-
-
+
+
+
Let's go!
- {errorMessage &&
{errorMessage}}
+ {errorMessage &&
{errorMessage}
}
-
+
)
}
diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css
index 6cd405b93f..2144557a98 100644
--- a/webview-ui/src/index.css
+++ b/webview-ui/src/index.css
@@ -111,6 +111,10 @@
--color-vscode-charts-green: var(--vscode-charts-green);
--color-vscode-charts-yellow: var(--vscode-charts-yellow);
+
+ --color-vscode-inputValidation-infoForeground: var(--vscode-inputValidation-infoForeground);
+ --color-vscode-inputValidation-infoBackground: var(--vscode-inputValidation-infoBackground);
+ --color-vscode-inputValidation-infoBorder: var(--vscode-inputValidation-infoBorder);
}
@layer base {