Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
efaaed9
feat: add Issue Fixer Orchestrator mode
MuriloFP Jul 3, 2025
57d3fbe
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 3, 2025
ef61905
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 4, 2025
f5a51c4
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 4, 2025
bcbf329
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 5, 2025
80413c0
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 5, 2025
ab10140
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 7, 2025
39c5cf7
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 7, 2025
00a0b63
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
080b61b
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
7a5ad14
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 8, 2025
2c73ff2
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 9, 2025
05ccf57
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 10, 2025
fdb1f35
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 11, 2025
10ce509
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 14, 2025
ab1f9fc
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 15, 2025
74fd8b4
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 15, 2025
6745c8f
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 16, 2025
faf2ee5
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
b2dadf9
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
f648e4c
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 17, 2025
a6d1e60
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 21, 2025
be90907
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 21, 2025
ed3a077
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 22, 2025
856313f
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 24, 2025
4dd68ea
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 28, 2025
b10fa5e
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 29, 2025
f016d7b
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 30, 2025
23855f2
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 31, 2025
3803c29
Merge branch 'RooCodeInc:main' into main
MuriloFP Jul 31, 2025
7612005
feat: add mode display indicators on task cards (#6493)
MuriloFP Jul 31, 2025
cf44525
fix: address PR review feedback for mode display indicators
MuriloFP Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion webview-ui/src/components/chat/TaskHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -90,11 +91,12 @@ const TaskHeader = ({
<div className="flex items-center shrink-0">
<span className={`codicon codicon-chevron-${isTaskExpanded ? "down" : "right"}`}></span>
</div>
<div className="ml-1.5 whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0">
<div className="ml-1.5 whitespace-nowrap overflow-hidden text-ellipsis grow min-w-0 flex items-center gap-2">
<span className="font-bold">
{t("chat:task.title")}
{!isTaskExpanded && ":"}
</span>
{currentTaskItem?.mode && <ModeBadge modeSlug={currentTaskItem.mode} />}
{!isTaskExpanded && (
<span className="ml-1">
<Mention text={task.text} />
Expand Down
39 changes: 35 additions & 4 deletions webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <div data-testid="mode-badge">{modeSlug}</div>,
}))

describe("TaskHeader", () => {
const defaultProps: TaskHeaderProps = {
task: { type: "say", ts: Date.now(), text: "Test task", images: [] },
Expand Down Expand Up @@ -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
})
})
36 changes: 36 additions & 0 deletions webview-ui/src/components/common/ModeBadge.tsx
Original file line number Diff line number Diff line change
@@ -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<ModeBadgeProps> = ({ 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 (
<StandardTooltip content={displayName}>
<span
className={cn(
"inline-flex px-2 py-0.5 rounded text-xs font-medium",
"bg-vscode-badge-background text-vscode-badge-foreground",
"max-w-[120px] truncate",
className,
)}>
{displayName}
</span>
</StandardTooltip>
)
}
51 changes: 51 additions & 0 deletions webview-ui/src/components/common/__tests__/ModeBadge.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(<ModeBadge modeSlug="code" />)
expect(screen.getByText("💻 Code")).toBeInTheDocument()
})

it("renders slug as fallback when mode not found", () => {
render(<ModeBadge modeSlug="deleted-mode" />)
expect(screen.getByText("deleted-mode")).toBeInTheDocument()
})

it("returns null when no mode slug provided", () => {
const { container } = render(<ModeBadge />)
expect(container.firstChild).toBeNull()
})

it("truncates long mode names", () => {
render(<ModeBadge modeSlug="custom" />)
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(<ModeBadge modeSlug="custom" />)
// 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()
})
})
2 changes: 2 additions & 0 deletions webview-ui/src/components/history/TaskItemHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +23,7 @@ const TaskItemHeader: React.FC<TaskItemHeaderProps> = ({ item, isSelectionMode,
<span className="text-vscode-descriptionForeground font-medium text-sm uppercase">
{formatDate(item.ts)}
</span>
{item.mode && <ModeBadge modeSlug={item.mode} />}
</div>

{/* Action Buttons */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ vi.mock("@src/i18n/TranslationContext", () => ({
}),
}))

// Mock ModeBadge component
vi.mock("@/components/common/ModeBadge", () => ({
ModeBadge: ({ modeSlug }: { modeSlug: string }) => <div data-testid="mode-badge">{modeSlug}</div>,
}))

const mockItem = {
id: "1",
number: 1,
Expand All @@ -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(<TaskItemHeader item={itemWithMode} isSelectionMode={false} onDelete={vi.fn()} />)

// 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(<TaskItemHeader item={mockItem} isSelectionMode={false} onDelete={vi.fn()} />)

// Verify no mode badge is rendered
expect(screen.queryByTestId("mode-badge")).not.toBeInTheDocument()
})
})