Skip to content
Closed
Show file tree
Hide file tree
Changes from 31 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
51 changes: 47 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,33 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
}))

// Mock the ExtensionStateContext
const { mockGetCurrentTaskItem, mockSetCurrentTaskItem } = vi.hoisted(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock setup here seems quite complex for the test needs. Could we simplify this mock to improve maintainability? The hoisted pattern is good, but the getter/setter functions might be overkill for these tests.

let currentTaskItem: any = { id: "test-task-id", mode: "code" }
return {
mockGetCurrentTaskItem: () => currentTaskItem,
mockSetCurrentTaskItem: (newItem: any) => {
currentTaskItem = newItem
},
}
})

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: mockGetCurrentTaskItem(),
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 +138,31 @@ 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 = mockGetCurrentTaskItem()
mockSetCurrentTaskItem({
id: "test-task-id",
number: 1,
ts: Date.now(),
task: "Test task",
tokensIn: 100,
tokensOut: 50,
totalCost: 0.05,
// No mode property
})

renderTaskHeader()
expect(screen.queryByTestId("mode-badge")).not.toBeInTheDocument()

// Restore original mock
mockSetCurrentTaskItem(originalTaskItem)
})
})
37 changes: 37 additions & 0 deletions webview-ui/src/components/common/ModeBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react"
import { findModeBySlug, getAllModes } 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 all modes (built-in + custom)
const allModes = getAllModes(customModes)
const mode = findModeBySlug(modeSlug, allModes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be using getModeBySlug() instead of findModeBySlug()? Looking at the modes file, getModeBySlug() checks custom modes first then built-in modes, which seems more appropriate for this use case. findModeBySlug() only searches within the provided modes array.

const displayName = mode?.name || modeSlug // Fallback to slug if mode deleted

return (
<StandardTooltip content={displayName}>
<span
className={cn(
"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the items-center class necessary when using truncate? The truncate behavior might not need vertical centering. Could we simplify this to just the essential classes?

"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", () => ({
findModeBySlug: vi.fn((slug, _modes) => {
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
}),
getAllModes: vi.fn(() => []),
}))

// 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", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test has a placeholder comment but doesn't actually verify tooltip behavior. Could we either implement proper tooltip testing (with user interactions) or remove this incomplete test to avoid confusion?

render(<ModeBadge modeSlug="custom" />)
// Tooltip content would be tested with user interaction
// This is a simplified test
expect(screen.getByText(/Very Long Custom Mode Name/)).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()
})
})