diff --git a/webview-ui/src/components/chat/ModeSelector.tsx b/webview-ui/src/components/chat/ModeSelector.tsx
index 3c212f11cd..f3a565e93a 100644
--- a/webview-ui/src/components/chat/ModeSelector.tsx
+++ b/webview-ui/src/components/chat/ModeSelector.tsx
@@ -2,8 +2,7 @@ import React from "react"
import { ChevronUp, Check, X } from "lucide-react"
import { cn } from "@/lib/utils"
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
-import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, Button } from "@/components/ui"
-import { IconButton } from "./IconButton"
+import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
import { vscode } from "@/utils/vscode"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAppTranslation } from "@/i18n/TranslationContext"
@@ -12,6 +11,9 @@ import { ModeConfig, CustomModePrompts } from "@roo-code/types"
import { telemetryClient } from "@/utils/TelemetryClient"
import { TelemetryEventName } from "@roo-code/types"
import { Fzf } from "fzf"
+import { ImportModeDialog } from "@/components/common/ImportModeDialog"
+import { ModeSelectorFooter } from "./ModeSelectorFooter"
+import { useModeSelectorExportImport } from "./useModeSelectorExportImport"
// Minimum number of modes required to show search functionality
const SEARCH_THRESHOLD = 6
@@ -45,8 +47,17 @@ export const ModeSelector = ({
const portalContainer = useRooPortal("roo-portal")
const { hasOpenedModeSelector, setHasOpenedModeSelector } = useExtensionState()
const { t } = useAppTranslation()
- const [showImportDialog, setShowImportDialog] = React.useState(false)
- const [isImporting, setIsImporting] = React.useState(false)
+ const {
+ showImportDialog,
+ isImporting,
+ exportError,
+ importError,
+ handleExport,
+ handleImport,
+ openImportDialog,
+ closeImportDialog,
+ clearErrors,
+ } = useModeSelectorExportImport()
const trackModeSelectorOpened = React.useCallback(() => {
// Track telemetry every time the mode selector is opened
@@ -155,23 +166,6 @@ export const ModeSelector = ({
}
}, [open])
- // Handle import/export result messages
- React.useEffect(() => {
- const handler = (event: MessageEvent) => {
- const message = event.data
- if (message.type === "importModeResult") {
- setIsImporting(false)
- setShowImportDialog(false)
- if (!message.success && message.error !== "cancelled") {
- console.error("Failed to import mode:", message.error)
- }
- }
- }
-
- window.addEventListener("message", handler)
- return () => window.removeEventListener("message", handler)
- }, [])
-
// Determine if search should be shown
const showSearch = !disableSearch && modes.length > SEARCH_THRESHOLD
@@ -273,131 +267,44 @@ export const ModeSelector = ({
{/* Bottom bar with buttons on left and title on right */}
-
-
- {
- window.postMessage(
- {
- type: "action",
- action: "marketplaceButtonClicked",
- values: { marketplaceTab: "mode" },
- },
- "*",
- )
- setOpen(false)
- }}
- />
- {
- if (value) {
- vscode.postMessage({
- type: "exportMode",
- slug: value,
- })
- }
- setOpen(false)
- }}
- />
- {
- setShowImportDialog(true)
- setOpen(false)
- }}
- />
- {
- vscode.postMessage({
- type: "switchTab",
- tab: "modes",
- })
- setOpen(false)
- }}
- />
-
-
- {/* Info icon and title on the right - only show info icon when search bar is visible */}
-
- {showSearch && (
-
-
-
- )}
-
- {t("chat:modeSelector.title")}
-
-
-
+ handleExport(value)}
+ onImport={openImportDialog}
+ onClose={() => setOpen(false)}
+ />
{/* Import Mode Dialog */}
- {showImportDialog && (
-
-
-
{t("prompts:modes.importMode")}
-
- {t("prompts:importMode.selectLevel")}
-
-
-
-
-
-
-
-
+
+
+ {/* Error notifications */}
+ {(exportError || importError) && (
+
+
+
+
+
+ {exportError ? t("prompts:exportMode.errorTitle") : t("prompts:importMode.errorTitle")}
+
+
+ {exportError || importError}
+
+
)}
diff --git a/webview-ui/src/components/chat/ModeSelectorFooter.tsx b/webview-ui/src/components/chat/ModeSelectorFooter.tsx
new file mode 100644
index 0000000000..381217e10f
--- /dev/null
+++ b/webview-ui/src/components/chat/ModeSelectorFooter.tsx
@@ -0,0 +1,96 @@
+import React from "react"
+import { IconButton } from "./IconButton"
+import { StandardTooltip } from "@/components/ui"
+import { vscode } from "@/utils/vscode"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+
+interface ModeSelectorFooterProps {
+ selectedMode: string | null
+ showSearch: boolean
+ instructionText: string
+ onExport: () => void
+ onImport: () => void
+ onClose: () => void
+}
+
+export const ModeSelectorFooter: React.FC
= ({
+ selectedMode,
+ showSearch,
+ instructionText,
+ onExport,
+ onImport,
+ onClose,
+}) => {
+ const { t } = useAppTranslation()
+
+ const handleMarketplaceClick = () => {
+ window.postMessage(
+ {
+ type: "action",
+ action: "marketplaceButtonClicked",
+ values: { marketplaceTab: "mode" },
+ },
+ "*",
+ )
+ onClose()
+ }
+
+ const handleExportClick = () => {
+ if (selectedMode) {
+ onExport()
+ }
+ onClose()
+ }
+
+ const handleImportClick = () => {
+ onImport()
+ onClose()
+ }
+
+ const handleSettingsClick = () => {
+ vscode.postMessage({
+ type: "switchTab",
+ tab: "modes",
+ })
+ onClose()
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {/* Info icon and title on the right - only show info icon when search bar is visible */}
+
+ {showSearch && (
+
+
+
+ )}
+
+ {t("chat:modeSelector.title")}
+
+
+
+ )
+}
diff --git a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
index a829168893..945dd4e80b 100644
--- a/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/ModeSelector.spec.tsx
@@ -1,9 +1,10 @@
import React from "react"
-import { render, screen, fireEvent } from "@/utils/test-utils"
-import { describe, test, expect, vi } from "vitest"
+import { render, screen, fireEvent, waitFor } from "@/utils/test-utils"
+import { describe, test, expect, vi, beforeEach } from "vitest"
import ModeSelector from "../ModeSelector"
import { Mode } from "@roo/modes"
import { ModeConfig } from "@roo-code/types"
+import { vscode } from "@/utils/vscode"
// Mock the dependencies
vi.mock("@/utils/vscode", () => ({
@@ -12,6 +13,9 @@ vi.mock("@/utils/vscode", () => ({
},
}))
+// Spy on window.postMessage
+const windowPostMessageSpy = vi.spyOn(window, "postMessage")
+
vi.mock("@/context/ExtensionStateContext", () => ({
useExtensionState: () => ({
hasOpenedModeSelector: false,
@@ -47,6 +51,12 @@ vi.mock("@roo/modes", async () => {
})
describe("ModeSelector", () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ // Reset mock modes
+ mockModes = []
+ })
+
test("shows custom description from customModePrompts", () => {
const customModePrompts = {
code: {
@@ -199,4 +209,259 @@ describe("ModeSelector", () => {
const infoIcon = document.querySelector(".codicon-info")
expect(infoIcon).toBeInTheDocument()
})
+
+ describe("Export functionality", () => {
+ test("export button triggers export message", () => {
+ // Set up mock to return a few modes
+ mockModes = Array.from({ length: 3 }, (_, i) => ({
+ slug: `mode-${i}`,
+ name: `Mode ${i}`,
+ description: `Description for mode ${i}`,
+ roleDefinition: "Role definition",
+ groups: ["read", "edit"],
+ }))
+
+ render()
+
+ // Click to open the popover
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+
+ // Find and click the export button
+ const exportButton = screen.getByLabelText("prompts:exportMode.title")
+ fireEvent.click(exportButton)
+
+ // Should have sent export message
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: "exportMode",
+ slug: "mode-0",
+ })
+ })
+
+ test("export error is displayed when export fails", async () => {
+ render()
+
+ // Simulate export error message
+ const errorEvent = new MessageEvent("message", {
+ data: {
+ type: "exportModeResult",
+ success: false,
+ error: "Failed to export mode",
+ },
+ })
+ window.dispatchEvent(errorEvent)
+
+ // Error notification should be displayed
+ await waitFor(() => {
+ expect(screen.getByText("prompts:exportMode.errorTitle")).toBeInTheDocument()
+ expect(screen.getByText("Failed to export mode")).toBeInTheDocument()
+ })
+ })
+ })
+
+ describe("Import functionality", () => {
+ test("import button opens import dialog", () => {
+ render()
+
+ // Click to open the popover
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+
+ // Find and click the import button
+ const importButton = screen.getByLabelText("prompts:modes.importMode")
+ fireEvent.click(importButton)
+
+ // Import dialog should be displayed
+ expect(screen.getByText("prompts:modes.importMode")).toBeInTheDocument()
+ expect(screen.getByText("prompts:importMode.selectLevel")).toBeInTheDocument()
+ })
+
+ test("import dialog allows selection between project and global", () => {
+ render()
+
+ // Open popover and click import
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+ const importButton = screen.getByLabelText("prompts:modes.importMode")
+ fireEvent.click(importButton)
+
+ // Check radio buttons are present
+ const projectRadio = screen.getByLabelText(/prompts:importMode.project.label/)
+ const globalRadio = screen.getByLabelText(/prompts:importMode.global.label/)
+
+ expect(projectRadio).toBeInTheDocument()
+ expect(globalRadio).toBeInTheDocument()
+ expect(projectRadio).toBeChecked()
+ expect(globalRadio).not.toBeChecked()
+ })
+
+ test("import dialog cancel button closes dialog", () => {
+ render()
+
+ // Open import dialog
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+ fireEvent.click(screen.getByLabelText("prompts:modes.importMode"))
+
+ // Click cancel
+ const cancelButton = screen.getByText("prompts:createModeDialog.buttons.cancel")
+ fireEvent.click(cancelButton)
+
+ // Dialog should be closed
+ expect(screen.queryByText("prompts:importMode.selectLevel")).not.toBeInTheDocument()
+ })
+
+ test("import dialog import button triggers import message", () => {
+ render()
+
+ // Open import dialog
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+ fireEvent.click(screen.getByLabelText("prompts:modes.importMode"))
+
+ // Select global option
+ const globalRadio = screen.getByLabelText(/prompts:importMode.global.label/)
+ fireEvent.click(globalRadio)
+
+ // Click import
+ const importButton = screen.getByText("prompts:importMode.import")
+ fireEvent.click(importButton)
+
+ // Should have sent import message
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: "importMode",
+ source: "global",
+ })
+ })
+
+ test("import error is displayed when import fails", async () => {
+ render()
+
+ // Simulate import error message
+ const errorEvent = new MessageEvent("message", {
+ data: {
+ type: "importModeResult",
+ success: false,
+ error: "Failed to import mode",
+ },
+ })
+ window.dispatchEvent(errorEvent)
+
+ // Error notification should be displayed
+ await waitFor(() => {
+ expect(screen.getByText("prompts:importMode.errorTitle")).toBeInTheDocument()
+ expect(screen.getByText("Failed to import mode")).toBeInTheDocument()
+ })
+ })
+
+ test("import dialog closes on successful import", async () => {
+ render()
+
+ // Open import dialog
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+ fireEvent.click(screen.getByLabelText("prompts:modes.importMode"))
+
+ // Dialog should be open
+ expect(screen.getByText("prompts:importMode.selectLevel")).toBeInTheDocument()
+
+ // Simulate successful import message
+ const successEvent = new MessageEvent("message", {
+ data: {
+ type: "importModeResult",
+ success: true,
+ },
+ })
+ window.dispatchEvent(successEvent)
+
+ // Dialog should be closed
+ await waitFor(() => {
+ expect(screen.queryByText("prompts:importMode.selectLevel")).not.toBeInTheDocument()
+ })
+ })
+
+ test("cancelled import does not show error", async () => {
+ render()
+
+ // Simulate cancelled import message
+ const cancelEvent = new MessageEvent("message", {
+ data: {
+ type: "importModeResult",
+ success: false,
+ error: "cancelled",
+ },
+ })
+ window.dispatchEvent(cancelEvent)
+
+ // No error notification should be displayed
+ await waitFor(() => {
+ expect(screen.queryByText("prompts:importMode.errorTitle")).not.toBeInTheDocument()
+ })
+ })
+ })
+
+ describe("Bottom bar buttons", () => {
+ test("marketplace button sends correct message", () => {
+ render()
+
+ // Click to open the popover
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+
+ // Find and click the marketplace button
+ const marketplaceButton = screen.getByLabelText("chat:modeSelector.marketplace")
+ fireEvent.click(marketplaceButton)
+
+ // Should have sent marketplace message
+ expect(windowPostMessageSpy).toHaveBeenCalledWith(
+ {
+ type: "action",
+ action: "marketplaceButtonClicked",
+ values: { marketplaceTab: "mode" },
+ },
+ "*",
+ )
+ })
+
+ test("settings button sends correct message", () => {
+ render()
+
+ // Click to open the popover
+ fireEvent.click(screen.getByTestId("mode-selector-trigger"))
+
+ // Find and click the settings button
+ const settingsButton = screen.getByLabelText("chat:modeSelector.settings")
+ fireEvent.click(settingsButton)
+
+ // Should have sent switch tab message
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: "switchTab",
+ tab: "modes",
+ })
+ })
+ })
+
+ describe("Error notification behavior", () => {
+ test("error notification can be closed manually", async () => {
+ render()
+
+ // Simulate export error
+ const errorEvent = new MessageEvent("message", {
+ data: {
+ type: "exportModeResult",
+ success: false,
+ error: "Test error",
+ },
+ })
+ window.dispatchEvent(errorEvent)
+
+ // Error should be displayed
+ await waitFor(() => {
+ expect(screen.getByText("Test error")).toBeInTheDocument()
+ })
+
+ // Click close button - find the button with X icon inside the error notification
+ const errorNotification = screen.getByText("Test error").closest("div")?.parentElement?.parentElement
+ const closeButton = errorNotification?.querySelector("button:last-child")
+ if (closeButton) {
+ fireEvent.click(closeButton)
+ }
+
+ // Error should be gone
+ expect(screen.queryByText("Test error")).not.toBeInTheDocument()
+ })
+ })
})
diff --git a/webview-ui/src/components/chat/useModeSelectorExportImport.ts b/webview-ui/src/components/chat/useModeSelectorExportImport.ts
new file mode 100644
index 0000000000..ae348f964c
--- /dev/null
+++ b/webview-ui/src/components/chat/useModeSelectorExportImport.ts
@@ -0,0 +1,78 @@
+import React from "react"
+import { vscode } from "@/utils/vscode"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+
+export const useModeSelectorExportImport = () => {
+ const { t } = useAppTranslation()
+ const [showImportDialog, setShowImportDialog] = React.useState(false)
+ const [isImporting, setIsImporting] = React.useState(false)
+ const [exportError, setExportError] = React.useState(null)
+ const [importError, setImportError] = React.useState(null)
+
+ // Handle import/export result messages
+ React.useEffect(() => {
+ const handler = (event: MessageEvent) => {
+ const message = event.data
+ if (message.type === "importModeResult") {
+ setIsImporting(false)
+ setShowImportDialog(false)
+ if (!message.success && message.error !== "cancelled") {
+ setImportError(message.error || t("prompts:importMode.error"))
+ // Clear error after 5 seconds
+ setTimeout(() => setImportError(null), 5000)
+ }
+ } else if (message.type === "exportModeResult") {
+ if (!message.success) {
+ setExportError(message.error || t("prompts:exportMode.error"))
+ // Clear error after 5 seconds
+ setTimeout(() => setExportError(null), 5000)
+ }
+ }
+ }
+
+ window.addEventListener("message", handler)
+ return () => window.removeEventListener("message", handler)
+ }, [t])
+
+ const handleExport = (modeSlug: string) => {
+ setExportError(null)
+ vscode.postMessage({
+ type: "exportMode",
+ slug: modeSlug,
+ })
+ }
+
+ const handleImport = (source: "global" | "project") => {
+ setIsImporting(true)
+ vscode.postMessage({
+ type: "importMode",
+ source,
+ })
+ }
+
+ const openImportDialog = () => {
+ setImportError(null)
+ setShowImportDialog(true)
+ }
+
+ const closeImportDialog = () => {
+ setShowImportDialog(false)
+ }
+
+ const clearErrors = () => {
+ setExportError(null)
+ setImportError(null)
+ }
+
+ return {
+ showImportDialog,
+ isImporting,
+ exportError,
+ importError,
+ handleExport,
+ handleImport,
+ openImportDialog,
+ closeImportDialog,
+ clearErrors,
+ }
+}
diff --git a/webview-ui/src/components/common/ImportModeDialog.tsx b/webview-ui/src/components/common/ImportModeDialog.tsx
new file mode 100644
index 0000000000..192a63b547
--- /dev/null
+++ b/webview-ui/src/components/common/ImportModeDialog.tsx
@@ -0,0 +1,64 @@
+import React from "react"
+import { Button } from "@/components/ui"
+import { useAppTranslation } from "@/i18n/TranslationContext"
+
+interface ImportModeDialogProps {
+ isOpen: boolean
+ onClose: () => void
+ onImport: (source: "global" | "project") => void
+ isImporting?: boolean
+}
+
+export const ImportModeDialog: React.FC = ({
+ isOpen,
+ onClose,
+ onImport,
+ isImporting = false,
+}) => {
+ const { t } = useAppTranslation()
+
+ if (!isOpen) return null
+
+ const handleImport = () => {
+ const selectedLevel = (document.querySelector('input[name="importLevel"]:checked') as HTMLInputElement)
+ ?.value as "global" | "project"
+ onImport(selectedLevel || "project")
+ }
+
+ return (
+
+
+
{t("prompts:modes.importMode")}
+
{t("prompts:importMode.selectLevel")}
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/webview-ui/src/components/common/__tests__/ImportModeDialog.spec.tsx b/webview-ui/src/components/common/__tests__/ImportModeDialog.spec.tsx
new file mode 100644
index 0000000000..267ea2ebd0
--- /dev/null
+++ b/webview-ui/src/components/common/__tests__/ImportModeDialog.spec.tsx
@@ -0,0 +1,111 @@
+import React from "react"
+import { render, screen, fireEvent } from "@/utils/test-utils"
+import { describe, test, expect, vi } from "vitest"
+import { ImportModeDialog } from "../ImportModeDialog"
+
+// Mock the translation hook
+vi.mock("@/i18n/TranslationContext", () => ({
+ useAppTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}))
+
+describe("ImportModeDialog", () => {
+ test("renders nothing when not open", () => {
+ const { container } = render()
+
+ expect(container.firstChild).toBeNull()
+ })
+
+ test("renders dialog when open", () => {
+ render()
+
+ expect(screen.getByText("prompts:modes.importMode")).toBeInTheDocument()
+ expect(screen.getByText("prompts:importMode.selectLevel")).toBeInTheDocument()
+ })
+
+ test("project option is selected by default", () => {
+ render()
+
+ const projectRadio = screen.getByLabelText(/prompts:importMode.project.label/)
+ const globalRadio = screen.getByLabelText(/prompts:importMode.global.label/)
+
+ expect(projectRadio).toBeChecked()
+ expect(globalRadio).not.toBeChecked()
+ })
+
+ test("can switch between project and global options", () => {
+ render()
+
+ const projectRadio = screen.getByLabelText(/prompts:importMode.project.label/)
+ const globalRadio = screen.getByLabelText(/prompts:importMode.global.label/)
+
+ // Initially project is selected
+ expect(projectRadio).toBeChecked()
+ expect(globalRadio).not.toBeChecked()
+
+ // Click global
+ fireEvent.click(globalRadio)
+
+ // Now global should be selected
+ expect(projectRadio).not.toBeChecked()
+ expect(globalRadio).toBeChecked()
+ })
+
+ test("cancel button calls onClose", () => {
+ const onClose = vi.fn()
+ render()
+
+ const cancelButton = screen.getByText("prompts:createModeDialog.buttons.cancel")
+ fireEvent.click(cancelButton)
+
+ expect(onClose).toHaveBeenCalledTimes(1)
+ })
+
+ test("import button calls onImport with selected source", () => {
+ const onImport = vi.fn()
+ render()
+
+ // Select global
+ const globalRadio = screen.getByLabelText(/prompts:importMode.global.label/)
+ fireEvent.click(globalRadio)
+
+ // Click import
+ const importButton = screen.getByText("prompts:importMode.import")
+ fireEvent.click(importButton)
+
+ expect(onImport).toHaveBeenCalledWith("global")
+ })
+
+ test("import button calls onImport with project by default", () => {
+ const onImport = vi.fn()
+ render()
+
+ // Click import without changing selection
+ const importButton = screen.getByText("prompts:importMode.import")
+ fireEvent.click(importButton)
+
+ expect(onImport).toHaveBeenCalledWith("project")
+ })
+
+ test("import button is disabled when isImporting is true", () => {
+ render()
+
+ const importButton = screen.getByText("prompts:importMode.importing")
+ expect(importButton).toBeDisabled()
+ })
+
+ test("shows importing text when isImporting is true", () => {
+ render()
+
+ expect(screen.getByText("prompts:importMode.importing")).toBeInTheDocument()
+ expect(screen.queryByText("prompts:importMode.import")).not.toBeInTheDocument()
+ })
+
+ test("displays project and global descriptions", () => {
+ render()
+
+ expect(screen.getByText("prompts:importMode.project.description")).toBeInTheDocument()
+ expect(screen.getByText("prompts:importMode.global.description")).toBeInTheDocument()
+ })
+})
diff --git a/webview-ui/src/components/modes/ModesView.tsx b/webview-ui/src/components/modes/ModesView.tsx
index 170d03b0e4..fc3e2959e7 100644
--- a/webview-ui/src/components/modes/ModesView.tsx
+++ b/webview-ui/src/components/modes/ModesView.tsx
@@ -48,6 +48,7 @@ import {
StandardTooltip,
} from "@src/components/ui"
import { DeleteModeDialog } from "@src/components/modes/DeleteModeDialog"
+import { ImportModeDialog } from "@src/components/common/ImportModeDialog"
// Get all available groups that should show in prompts view
const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable)
@@ -96,6 +97,8 @@ const ModesView = ({ onDone }: ModesViewProps) => {
const [isExporting, setIsExporting] = useState(false)
const [isImporting, setIsImporting] = useState(false)
const [showImportDialog, setShowImportDialog] = useState(false)
+ const [exportError, setExportError] = useState(null)
+ const [importError, setImportError] = useState(null)
const [hasRulesToExport, setHasRulesToExport] = useState>({})
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [modeToDelete, setModeToDelete] = useState<{
@@ -449,17 +452,20 @@ const ModesView = ({ onDone }: ModesViewProps) => {
setIsExporting(false)
if (!message.success) {
- // Show error message
- console.error("Failed to export mode:", message.error)
+ setExportError(message.error || t("prompts:exportMode.error"))
+ // Clear error after 5 seconds
+ setTimeout(() => setExportError(null), 5000)
}
} else if (message.type === "importModeResult") {
setIsImporting(false)
setShowImportDialog(false)
if (!message.success) {
- // Only log error if it's not a cancellation
+ // Only show error if it's not a cancellation
if (message.error !== "cancelled") {
- console.error("Failed to import mode:", message.error)
+ setImportError(message.error || t("prompts:importMode.error"))
+ // Clear error after 5 seconds
+ setTimeout(() => setImportError(null), 5000)
}
}
} else if (message.type === "checkRulesDirectoryResult") {
@@ -483,7 +489,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
window.addEventListener("message", handler)
return () => window.removeEventListener("message", handler)
- }, []) // Empty dependency array - only register once
+ }, [t]) // Add t to dependency array
const handleAgentReset = (
modeSlug: string,
@@ -1203,6 +1209,7 @@ const ModesView = ({ onDone }: ModesViewProps) => {
const currentMode = getCurrentMode()
if (currentMode?.slug && !isExporting) {
setIsExporting(true)
+ setExportError(null)
vscode.postMessage({
type: "exportMode",
slug: currentMode.slug,
@@ -1219,7 +1226,10 @@ const ModesView = ({ onDone }: ModesViewProps) => {
{/* Import button - always visible */}