Skip to content

Commit a68c612

Browse files
committed
feat: replace emoji icons with professional text-based names
1 parent a8f87d2 commit a68c612

File tree

6 files changed

+129
-11
lines changed

6 files changed

+129
-11
lines changed

packages/types/src/mode.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const modeConfigSchema = z.object({
6767
roleDefinition: z.string().min(1, "Role definition is required"),
6868
whenToUse: z.string().optional(),
6969
description: z.string().optional(),
70+
icon: z.string().optional(),
7071
customInstructions: z.string().optional(),
7172
groups: groupEntryArraySchema,
7273
source: z.enum(["global", "project"]).optional(),
@@ -136,7 +137,8 @@ export type CustomSupportPrompts = z.infer<typeof customSupportPromptsSchema>
136137
export const DEFAULT_MODES: readonly ModeConfig[] = [
137138
{
138139
slug: "architect",
139-
name: "🏗️ Architect",
140+
name: "Architect",
141+
icon: "checklist",
140142
roleDefinition:
141143
"You are Roo, an experienced technical leader who is inquisitive and an excellent planner. Your goal is to gather information and get context to create a detailed plan for accomplishing the user's task, which the user will review and approve before they switch into another mode to implement the solution.",
142144
whenToUse:
@@ -148,7 +150,8 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
148150
},
149151
{
150152
slug: "code",
151-
name: "💻 Code",
153+
name: "Code",
154+
icon: "code",
152155
roleDefinition:
153156
"You are Roo, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.",
154157
whenToUse:
@@ -158,7 +161,8 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
158161
},
159162
{
160163
slug: "ask",
161-
name: "❓ Ask",
164+
name: "Ask",
165+
icon: "question",
162166
roleDefinition:
163167
"You are Roo, a knowledgeable technical assistant focused on answering questions and providing information about software development, technology, and related topics.",
164168
whenToUse:
@@ -170,7 +174,8 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
170174
},
171175
{
172176
slug: "debug",
173-
name: "🪲 Debug",
177+
name: "Debug",
178+
icon: "debug",
174179
roleDefinition:
175180
"You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.",
176181
whenToUse:
@@ -182,7 +187,8 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [
182187
},
183188
{
184189
slug: "orchestrator",
185-
name: "🪃 Orchestrator",
190+
name: "Orchestrator",
191+
icon: "organization",
186192
roleDefinition:
187193
"You are Roo, a strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes. You have a comprehensive understanding of each mode's capabilities and limitations, allowing you to effectively break down complex problems into discrete tasks that can be solved by different specialists.",
188194
whenToUse:

src/core/environment/__tests__/getEnvironmentDetails.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ describe("getEnvironmentDetails", () => {
121121
// Mock other dependencies.
122122
;(getApiMetrics as Mock).mockReturnValue({ contextTokens: 50000, totalCost: 0.25 })
123123
;(getFullModeDetails as Mock).mockResolvedValue({
124-
name: "💻 Code",
124+
name: "Code",
125125
roleDefinition: "You are a code assistant",
126126
customInstructions: "Custom instructions",
127127
})

src/shared/__tests__/modes.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ describe("FileRestrictionError", () => {
389389
expect(debugMode).toBeDefined()
390390
expect(debugMode).toMatchObject({
391391
slug: "debug",
392-
name: "🪲 Debug",
392+
name: "Debug",
393393
roleDefinition:
394394
"You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.",
395395
groups: ["read", "edit", "browser", "command", "mcp"],
@@ -410,7 +410,7 @@ describe("FileRestrictionError", () => {
410410
const result = await getFullModeDetails("debug")
411411
expect(result).toMatchObject({
412412
slug: "debug",
413-
name: "🪲 Debug",
413+
name: "Debug",
414414
roleDefinition:
415415
"You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.",
416416
})

webview-ui/src/components/chat/ModeSelector.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
1515
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui"
1616

1717
import { IconButton } from "./IconButton"
18+
import InlineIcon from "../common/InlineIcon"
1819

1920
const SEARCH_THRESHOLD = 6
2021

@@ -209,7 +210,19 @@ export const ModeSelector = ({
209210
? "bg-primary opacity-90 hover:bg-primary-hover text-vscode-button-foreground"
210211
: null,
211212
)}>
212-
<span className="truncate">{selectedMode?.name || ""}</span>
213+
<div className="flex items-center gap-1 truncate">
214+
{selectedMode?.icon && (
215+
<InlineIcon
216+
token={
217+
selectedMode.icon.startsWith("codicon-") || selectedMode.icon.startsWith("lucide-")
218+
? selectedMode.icon
219+
: `codicon-${selectedMode.icon}`
220+
}
221+
className="text-[11px] leading-none"
222+
/>
223+
)}
224+
<span className="truncate">{selectedMode?.name || ""}</span>
225+
</div>
213226
</PopoverTrigger>
214227
</StandardTooltip>
215228
<PopoverContent
@@ -269,7 +282,20 @@ export const ModeSelector = ({
269282
)}
270283
data-testid="mode-selector-item">
271284
<div className="flex-1 min-w-0">
272-
<div className="font-bold truncate">{mode.name}</div>
285+
<div className="font-bold truncate flex items-center gap-1">
286+
{mode.icon && (
287+
<InlineIcon
288+
token={
289+
mode.icon.startsWith("codicon-") ||
290+
mode.icon.startsWith("lucide-")
291+
? mode.icon
292+
: `codicon-${mode.icon}`
293+
}
294+
className="text-[11px] leading-none"
295+
/>
296+
)}
297+
<span className="truncate">{mode.name}</span>
298+
</div>
273299
{mode.description && (
274300
<div className="text-xs text-vscode-descriptionForeground truncate">
275301
{mode.description}

webview-ui/src/components/common/IconButton.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { StandardTooltip } from "@/components/ui"
2+
import InlineIcon from "./InlineIcon"
23

34
interface IconButtonProps {
45
icon: string
@@ -26,13 +27,18 @@ export function IconButton({
2627
medium: "w-7 h-7",
2728
}
2829

30+
const iconSize = size === "small" ? 14 : 16
31+
2932
const variantClasses = {
3033
default: "bg-transparent hover:bg-vscode-toolbar-hoverBackground",
3134
transparent: "bg-transparent hover:bg-vscode-toolbar-hoverBackground",
3235
}
3336

3437
const handleClick = onClick || ((_event: React.MouseEvent) => {})
3538

39+
// Support both raw codicon names (e.g., "extensions") and fully-qualified tokens ("codicon-..." | "lucide-...")
40+
const token = icon.startsWith("codicon-") || icon.startsWith("lucide-") ? icon : `codicon-${icon}`
41+
3642
const button = (
3743
<button
3844
className={`${sizeClasses[size]} flex items-center justify-center border-none text-vscode-editor-foreground cursor-pointer rounded-[3px] ${variantClasses[variant]}`}
@@ -41,7 +47,7 @@ export function IconButton({
4147
onMouseDown={onMouseDown}
4248
onMouseUp={onMouseUp}
4349
onMouseLeave={onMouseLeave}>
44-
<span className={`codicon codicon-${icon}`}></span>
50+
<InlineIcon token={token} size={iconSize} />
4551
</button>
4652
)
4753

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, { Suspense } from "react"
2+
import { cn } from "@/lib/utils"
3+
import dynamicIconImports from "lucide-react/dynamicIconImports"
4+
5+
type InlineIconProps = {
6+
token?: string
7+
className?: string
8+
style?: React.CSSProperties
9+
size?: number
10+
spin?: boolean
11+
}
12+
13+
function toPascalCase(name: string): string {
14+
return name
15+
.split(/[-_\s]+/)
16+
.filter(Boolean)
17+
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
18+
.join("")
19+
}
20+
21+
export const InlineIcon: React.FC<InlineIconProps> = ({ token, className, style, size = 16, spin = false }) => {
22+
if (!token) return null
23+
24+
const raw = token.trim()
25+
26+
// Normalize shorthand: default to codicon if no prefix
27+
let type: "codicon" | "lucide" = "codicon"
28+
let name = raw
29+
30+
if (raw.startsWith("lucide-")) {
31+
type = "lucide"
32+
name = raw.slice("lucide-".length)
33+
} else if (raw.startsWith("codicon-")) {
34+
type = "codicon"
35+
name = raw.slice("codicon-".length)
36+
} else {
37+
// no prefix implies codicon
38+
type = "codicon"
39+
name = raw
40+
}
41+
42+
// Temporary mapping: show lucide-bug when codicon-debug requested
43+
if (type === "codicon" && name === "debug") {
44+
type = "lucide"
45+
name = "bug"
46+
}
47+
48+
if (type === "codicon") {
49+
return (
50+
<span
51+
className={cn("codicon", `codicon-${name}`, spin && "codicon-modifier-spin", className)}
52+
style={style}
53+
aria-hidden="true"
54+
/>
55+
)
56+
}
57+
58+
const pascal = toPascalCase(name)
59+
const importer = (dynamicIconImports as Record<string, () => Promise<{ default: React.ComponentType<any> }>>)[
60+
pascal
61+
]
62+
63+
if (!importer) {
64+
// Fallback to a generic question icon if not found
65+
return <span className={cn("codicon", "codicon-question", className)} style={style} aria-hidden="true" />
66+
}
67+
68+
const LucideIcon = React.lazy(importer)
69+
70+
return (
71+
<Suspense
72+
fallback={
73+
<span className={cn("codicon", "codicon-symbol-icon", className)} style={style} aria-hidden="true" />
74+
}>
75+
<LucideIcon className={className} width={size} height={size} strokeWidth={1.75} aria-hidden="true" />
76+
</Suspense>
77+
)
78+
}
79+
80+
export default InlineIcon

0 commit comments

Comments
 (0)