diff --git a/webview-ui/src/components/chat/TaskHeader.tsx b/webview-ui/src/components/chat/TaskHeader.tsx
index 1896df486b..6bc1dbe71f 100644
--- a/webview-ui/src/components/chat/TaskHeader.tsx
+++ b/webview-ui/src/components/chat/TaskHeader.tsx
@@ -21,6 +21,7 @@ import { ShareButton } from "./ShareButton"
import { ContextWindowProgress } from "./ContextWindowProgress"
import { Mention } from "./Mention"
import { TodoListDisplay } from "./TodoListDisplay"
+import { ModeBadge } from "@/components/common/ModeBadge"
export interface TaskHeaderProps {
task: ClineMessage
@@ -90,11 +91,12 @@ const TaskHeader = ({
+
{t("chat:task.title")}
{!isTaskExpanded && ":"}
+ {currentTaskItem?.mode &&
}
{!isTaskExpanded && (
diff --git a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
index c04f7e45e5..85b012b6f0 100644
--- a/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
+++ b/webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
@@ -33,17 +33,27 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
}))
// Mock the ExtensionStateContext
+const mockCurrentTaskItem = vi.hoisted(() => ({
+ value: { id: "test-task-id", mode: "code" } as any,
+}))
+
vi.mock("@src/context/ExtensionStateContext", () => ({
useExtensionState: () => ({
apiConfiguration: {
apiProvider: "anthropic",
- apiKey: "test-api-key", // Add relevant fields
- apiModelId: "claude-3-opus-20240229", // Add relevant fields
- } as ProviderSettings, // Optional: Add type assertion if ProviderSettings is imported
- currentTaskItem: { id: "test-task-id" },
+ apiKey: "test-api-key",
+ apiModelId: "claude-3-opus-20240229",
+ } as ProviderSettings,
+ currentTaskItem: mockCurrentTaskItem.value,
+ customModes: [],
}),
}))
+// Mock ModeBadge component
+vi.mock("@/components/common/ModeBadge", () => ({
+ ModeBadge: ({ modeSlug }: { modeSlug: string }) => {modeSlug}
,
+}))
+
describe("TaskHeader", () => {
const defaultProps: TaskHeaderProps = {
task: { type: "say", ts: Date.now(), text: "Test task", images: [] },
@@ -122,4 +132,25 @@ describe("TaskHeader", () => {
fireEvent.click(condenseButton!)
expect(handleCondenseContext).not.toHaveBeenCalled()
})
+
+ it("should display mode badge when currentTaskItem has mode", () => {
+ renderTaskHeader()
+ expect(screen.getByTestId("mode-badge")).toBeInTheDocument()
+ expect(screen.getByText("code")).toBeInTheDocument()
+ })
+
+ it("should not display mode badge when currentTaskItem has no mode", () => {
+ // Override the mock for this test
+ const originalTaskItem = mockCurrentTaskItem.value
+ mockCurrentTaskItem.value = {
+ id: "test-task-id",
+ // No mode property
+ }
+
+ renderTaskHeader()
+ expect(screen.queryByTestId("mode-badge")).not.toBeInTheDocument()
+
+ // Restore original mock
+ mockCurrentTaskItem.value = originalTaskItem
+ })
})
diff --git a/webview-ui/src/components/common/ModeBadge.tsx b/webview-ui/src/components/common/ModeBadge.tsx
new file mode 100644
index 0000000000..655ebd723c
--- /dev/null
+++ b/webview-ui/src/components/common/ModeBadge.tsx
@@ -0,0 +1,36 @@
+import React from "react"
+import { getModeBySlug } from "@roo/modes"
+import { useExtensionState } from "@/context/ExtensionStateContext"
+import { StandardTooltip } from "@/components/ui"
+import { cn } from "@/lib/utils"
+
+interface ModeBadgeProps {
+ modeSlug?: string
+ className?: string
+}
+
+export const ModeBadge: React.FC = ({ modeSlug, className }) => {
+ const { customModes } = useExtensionState()
+
+ if (!modeSlug) {
+ return null
+ }
+
+ // Get mode using getModeBySlug which checks custom modes first, then built-in
+ const mode = getModeBySlug(modeSlug, customModes)
+ const displayName = mode?.name || modeSlug // Fallback to slug if mode deleted
+
+ return (
+
+
+ {displayName}
+
+
+ )
+}
diff --git a/webview-ui/src/components/common/__tests__/ModeBadge.spec.tsx b/webview-ui/src/components/common/__tests__/ModeBadge.spec.tsx
new file mode 100644
index 0000000000..cf2b0d96a4
--- /dev/null
+++ b/webview-ui/src/components/common/__tests__/ModeBadge.spec.tsx
@@ -0,0 +1,51 @@
+import { render, screen } from "@/utils/test-utils"
+import { ModeBadge } from "../ModeBadge"
+
+// Mock the shared modes module
+vi.mock("@roo/modes", () => ({
+ getModeBySlug: vi.fn((slug, _customModes) => {
+ if (slug === "code") return { slug: "code", name: "💻 Code" }
+ if (slug === "architect") return { slug: "architect", name: "🏗️ Architect" }
+ if (slug === "custom") return { slug: "custom", name: "Very Long Custom Mode Name That Should Be Truncated" }
+ return undefined
+ }),
+}))
+
+// Mock ExtensionStateContext
+vi.mock("@/context/ExtensionStateContext", () => ({
+ useExtensionState: () => ({
+ customModes: [],
+ }),
+}))
+
+describe("ModeBadge", () => {
+ it("renders mode name when mode exists", () => {
+ render()
+ expect(screen.getByText("💻 Code")).toBeInTheDocument()
+ })
+
+ it("renders slug as fallback when mode not found", () => {
+ render()
+ expect(screen.getByText("deleted-mode")).toBeInTheDocument()
+ })
+
+ it("returns null when no mode slug provided", () => {
+ const { container } = render()
+ expect(container.firstChild).toBeNull()
+ })
+
+ it("truncates long mode names", () => {
+ render()
+ const badge = screen.getByText(/Very Long Custom Mode Name/)
+ expect(badge).toHaveClass("truncate")
+ expect(badge).toHaveClass("max-w-[120px]")
+ })
+
+ it("shows full name in tooltip", () => {
+ render()
+ // The tooltip content is set via the StandardTooltip's content prop
+ // We verify the text is rendered in the badge itself
+ const badge = screen.getByText(/Very Long Custom Mode Name/)
+ expect(badge).toBeInTheDocument()
+ })
+})
diff --git a/webview-ui/src/components/history/TaskItemHeader.tsx b/webview-ui/src/components/history/TaskItemHeader.tsx
index bdddb090c8..ac28d42d94 100644
--- a/webview-ui/src/components/history/TaskItemHeader.tsx
+++ b/webview-ui/src/components/history/TaskItemHeader.tsx
@@ -3,6 +3,7 @@ import type { HistoryItem } from "@roo-code/types"
import { formatDate } from "@/utils/format"
import { DeleteButton } from "./DeleteButton"
import { cn } from "@/lib/utils"
+import { ModeBadge } from "@/components/common/ModeBadge"
export interface TaskItemHeaderProps {
item: HistoryItem
@@ -22,6 +23,7 @@ const TaskItemHeader: React.FC = ({ item, isSelectionMode,
{formatDate(item.ts)}
+ {item.mode && }
{/* Action Buttons */}
diff --git a/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx
index 090bf2521f..94bb5800fe 100644
--- a/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx
+++ b/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx
@@ -8,6 +8,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
}),
}))
+// Mock ModeBadge component
+vi.mock("@/components/common/ModeBadge", () => ({
+ ModeBadge: ({ modeSlug }: { modeSlug: string }) =>
{modeSlug}
,
+}))
+
const mockItem = {
id: "1",
number: 1,
@@ -32,4 +37,20 @@ describe("TaskItemHeader", () => {
expect(screen.getByRole("button")).toBeInTheDocument()
})
+
+ it("shows mode badge when item has mode", () => {
+ const itemWithMode = { ...mockItem, mode: "code" }
+ render(
)
+
+ // ModeBadge would be mocked in the test
+ expect(screen.getByTestId("mode-badge")).toBeInTheDocument()
+ expect(screen.getByText("code")).toBeInTheDocument()
+ })
+
+ it("does not show mode badge when item has no mode", () => {
+ render(
)
+
+ // Verify no mode badge is rendered
+ expect(screen.queryByTestId("mode-badge")).not.toBeInTheDocument()
+ })
})