perf(ui): migrate icons to SVG sprite system

Replace @remixicon/react with a shared Icon component that renders
via <use href> references to a single hidden SVG sprite. This reduces
DOM node count by replacing inline SVGs with lightweight references.

- Create Icon component with sprite injection (packages/ui/src/components/icon/)
- Migrate all 164 files from @remixicon/react to Icon component
- Auto-generate sprite data from remixicon bundle (scripts/generate-icon-sprite.mjs)
- Add bun run icons:generate to package.json
- Move @remixicon/react to devDependencies
- Add icon usage instructions to theme-system skill
This commit is contained in:
Bohdan Triapitsyn
2026-05-13 13:26:35 +03:00
parent b4cbd7f0d6
commit 14357257ae
173 changed files with 2342 additions and 2227 deletions
@@ -1,5 +1,4 @@
import React from 'react';
import { RiArrowLeftRightLine, RiChat4Line, RiCloseLine, RiDonutChartFill, RiFileTextLine, RiFullscreenExitLine, RiFullscreenLine, RiGlobalLine, RiRefreshLine, RiExternalLinkLine, RiTerminalBoxLine, RiCursorLine } from '@remixicon/react';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
import { Button } from '@/components/ui/button';
@@ -20,6 +19,7 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
import { useInputStore } from '@/sync/input-store';
import { ContextPanelContent } from './ContextSidebarTab';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
const CONTEXT_PANEL_MIN_WIDTH = 360;
const CONTEXT_PANEL_MAX_WIDTH = 1400;
@@ -296,23 +296,23 @@ const getTabIcon = (tab: { mode: 'diff' | 'file' | 'context' | 'plan' | 'chat' |
}
if (tab.mode === 'diff') {
return <RiArrowLeftRightLine className="h-3.5 w-3.5" />;
return <Icon name="arrow-left-right" className="h-3.5 w-3.5" />;
}
if (tab.mode === 'plan') {
return <RiFileTextLine className="h-3.5 w-3.5" />;
return <Icon name="file-text" className="h-3.5 w-3.5" />;
}
if (tab.mode === 'context') {
return <RiDonutChartFill className="h-3.5 w-3.5" />;
return <Icon name="donut-chart-fill" className="h-3.5 w-3.5" />;
}
if (tab.mode === 'chat') {
return <RiChat4Line className="h-3.5 w-3.5" />;
return <Icon name="chat-4" className="h-3.5 w-3.5" />;
}
if (tab.mode === 'preview') {
return <RiGlobalLine className="h-3.5 w-3.5 text-[var(--status-info)]" />;
return <Icon name="global" className="h-3.5 w-3.5 text-[var(--status-info)]" />;
}
return undefined;
@@ -919,7 +919,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.actions.reload')}
disabled={!effectiveSrc}
>
<RiRefreshLine className="h-3.5 w-3.5" />
<Icon name="refresh" className="h-3.5 w-3.5" />
</Button>
<Button
type="button"
@@ -934,7 +934,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.actions.openExternal')}
disabled={!directSrc}
>
<RiExternalLinkLine className="h-3.5 w-3.5" />
<Icon name="external-link" className="h-3.5 w-3.5" />
</Button>
{isLoopback ? (
<Button
@@ -947,7 +947,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.inspect.toggle')}
disabled={!bridgeReady}
>
<RiCursorLine className="h-3.5 w-3.5" />
<Icon name="cursor" className="h-3.5 w-3.5" />
</Button>
) : null}
{isLoopback ? (
@@ -961,7 +961,7 @@ const PreviewPane: React.FC<PreviewPaneProps> = ({ rawUrl, onNavigate }) => {
aria-label={bridgeReady ? t('contextPanel.preview.console.open') : t('contextPanel.preview.console.waiting')}
disabled={!bridgeReady && consoleEvents.length === 0}
>
<RiTerminalBoxLine className="h-3.5 w-3.5" />
<Icon name="terminal-box" className="h-3.5 w-3.5" />
{consoleErrorCount > 0 ? (
<span className="typography-micro text-status-error">{consoleErrorCount}</span>
) : null}
@@ -1380,7 +1380,7 @@ export const ContextPanel: React.FC = () => {
? <PreviewPane rawUrl={activeTab.targetPath ?? ''} onNavigate={(url) => openContextPreview(effectiveDirectory, url)} />
: (
<div className="flex h-full flex-col items-center justify-center gap-3 p-6 text-center">
<RiGlobalLine className="h-12 w-12 text-muted-foreground/50" />
<Icon name="global" className="h-12 w-12 text-muted-foreground/50" />
<div className="typography-ui-header text-foreground">{t('contextPanel.preview.title')}</div>
<div className="max-w-sm typography-micro text-muted-foreground">{t('contextPanel.preview.description')}</div>
</div>
@@ -1433,7 +1433,7 @@ export const ContextPanel: React.FC = () => {
title={isExpanded ? t('contextPanel.actions.collapsePanel') : t('contextPanel.actions.expandPanel')}
aria-label={isExpanded ? t('contextPanel.actions.collapsePanel') : t('contextPanel.actions.expandPanel')}
>
{isExpanded ? <RiFullscreenExitLine className="h-3.5 w-3.5" /> : <RiFullscreenLine className="h-3.5 w-3.5" />}
{isExpanded ? <Icon name="fullscreen-exit" className="h-3.5 w-3.5" /> : <Icon name="fullscreen" className="h-3.5 w-3.5" />}
</Button>
<Button
type="button"
@@ -1444,7 +1444,7 @@ export const ContextPanel: React.FC = () => {
title={t('contextPanel.actions.closePanel')}
aria-label={t('contextPanel.actions.closePanel')}
>
<RiCloseLine className="h-3.5 w-3.5" />
<Icon name="close" className="h-3.5 w-3.5" />
</Button>
</div>
</header>
@@ -1,9 +1,9 @@
import React from 'react';
import type { Message, Part } from '@opencode-ai/sdk/v2';
import { RiCheckLine, RiFileCopyLine } from '@remixicon/react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { deriveMessageRole } from '@/components/chat/message/messageRole';
import { Icon } from "@/components/icon/Icon";
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator';
import { useConfigStore } from '@/stores/useConfigStore';
@@ -565,7 +565,7 @@ export const ContextPanelContent: React.FC = () => {
aria-label={isCopied ? t('contextSidebar.actions.copied') : t('contextSidebar.actions.copyJson')}
title={isCopied ? t('contextSidebar.actions.copied') : t('contextSidebar.actions.copy')}
>
{isCopied ? <RiCheckLine className="size-3.5" /> : <RiFileCopyLine className="size-3.5" />}
{isCopied ? <Icon name="check" className="size-3.5" /> : <Icon name="file-copy" className="size-3.5" />}
</button>
</div>
<SyntaxHighlighter
+46 -48
View File
@@ -16,7 +16,6 @@ import {
} from '@/components/ui/dropdown-menu';
import { SortableTabsStrip, type SortableTabsStripItem } from '@/components/ui/sortable-tabs-strip';
import { RiArrowLeftSLine, RiChat4Line, RiChatNewLine, RiCheckLine, RiCloseLine, RiCommandLine, RiFileTextLine, RiFolder6Line, RiGitBranchLine, RiGithubFill, RiLayoutLeftLine, RiLayoutRightLine, RiPictureInPicture2Line, RiPlayListAddLine, RiRefreshLine, RiServerLine, RiStackLine, RiTerminalBoxLine, RiTimerLine, RiAlertLine, type RemixiconComponentType } from '@remixicon/react';
import { DiffIcon } from '@/components/icons/DiffIcon';
import { useUIStore, type MainTab } from '@/stores/useUIStore';
import { useConfigStore } from '@/stores/useConfigStore';
@@ -56,7 +55,6 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react';
import type { UsageWindow } from '@/types';
import type { GitHubAuthStatus } from '@/lib/api/types';
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
@@ -68,8 +66,10 @@ import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton';
import { canUseElectronDesktopIPC, invokeDesktop, isDesktopShell, isVSCodeRuntime, startDesktopWindowDrag } from '@/lib/desktop';
import { desktopHostsGet, locationMatchesHost, redactSensitiveUrl } from '@/lib/desktopHosts';
import { resolveSessionDiffStats } from '@/components/session/sidebar/utils';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import type { Session } from '@opencode-ai/sdk/v2/client';
import type { IconName } from "@/components/icon/icons";
const DESKTOP_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-8 w-8 items-center justify-center gap-2 rounded-md typography-ui-label font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:bg-interactive-hover transition-colors';
const MOBILE_HEADER_ICON_BUTTON_CLASS = 'app-region-no-drag inline-flex h-9 w-9 items-center justify-center gap-2 p-2 rounded-md typography-ui-label font-medium text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50 hover:text-foreground hover:bg-interactive-hover transition-colors';
@@ -80,7 +80,7 @@ type HeaderIconActionButtonProps = {
ariaLabel: string;
onClick: () => void;
className?: string;
Icon: RemixiconComponentType;
Icon: IconName;
iconClassName?: string;
};
@@ -90,7 +90,7 @@ const HeaderIconActionButton = React.memo(function HeaderIconActionButton({
ariaLabel,
onClick,
className,
Icon,
Icon: iconName,
iconClassName,
}: HeaderIconActionButtonProps) {
if (!visible) {
@@ -106,7 +106,7 @@ const HeaderIconActionButton = React.memo(function HeaderIconActionButton({
aria-label={ariaLabel}
className={className ?? DESKTOP_HEADER_ICON_BUTTON_CLASS}
>
<Icon className={iconClassName ?? 'h-[18px] w-[18px]'} />
<Icon name={iconName} className={iconClassName ?? 'h-[18px] w-[18px]'} />
</button>
</TooltipTrigger>
<TooltipContent>
@@ -162,7 +162,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
referrerPolicy="no-referrer"
/>
) : (
<RiGithubFill className="h-3.5 w-3.5 text-foreground" />
<Icon name="github-fill" className="h-3.5 w-3.5 text-foreground" />
)}
</button>
</DropdownMenuTrigger>
@@ -195,7 +195,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
/>
) : (
<div className="flex h-6 w-6 items-center justify-center rounded-full border border-border/60 bg-muted">
<RiGithubFill className="h-3 w-3 text-muted-foreground" />
<Icon name="github-fill" className="h-3 w-3 text-muted-foreground" />
</div>
)}
<span className="flex min-w-0 flex-1 flex-col">
@@ -208,7 +208,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
</span>
) : null}
</span>
{isCurrent ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
{isCurrent ? <Icon name="check" className="h-4 w-4 text-primary" /> : null}
</DropdownMenuItem>
);
})}
@@ -231,7 +231,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
referrerPolicy="no-referrer"
/>
) : (
<RiGithubFill className="h-3.5 w-3.5 text-foreground" />
<Icon name="github-fill" className="h-3.5 w-3.5 text-foreground" />
)}
</div>
);
@@ -321,7 +321,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
isDesktopApp ? 'w-auto max-w-[14rem] justify-start gap-1.5 px-2.5' : 'h-8 w-8'
)}
>
<RiStackLine className="h-[18px] w-[18px]" />
<Icon name="stack" className="h-[18px] w-[18px]" />
{isDesktopApp ? (
<span className="truncate typography-ui-label font-medium text-foreground">{compactCurrentInstanceLabel}</span>
) : null}
@@ -411,7 +411,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
disabled={isQuotaLoading || isUsageRefreshSpinning}
aria-label={t('header.services.refreshRateLimitsAria')}
>
<RiRefreshLine className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
<Icon name="refresh" className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
</button>
</div>
</div>
@@ -485,7 +485,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
>
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-md px-1 py-1.5 text-left hover:bg-[var(--interactive-hover)]/50 transition-colors">
<span className="typography-ui-label font-medium text-foreground">{family.familyLabel}</span>
{isExpanded ? <RiArrowDownSLine className="h-4 w-4 text-muted-foreground" /> : <RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />}
{isExpanded ? <Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" /> : <Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />}
</CollapsibleTrigger>
<CollapsibleContent>
<div className="space-y-2.5 pb-1 pl-1 pt-1">
@@ -552,7 +552,6 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
);
});
const isSameContextUsage = (
a: SessionContextUsage | null,
b: SessionContextUsage | null,
@@ -623,7 +622,7 @@ const getActiveContextMode = (panelState: {
interface TabConfig {
id: MainTab;
label: string;
icon: RemixiconComponentType | 'diff';
icon: IconName | 'diff';
badge?: number;
showDot?: boolean;
}
@@ -1179,7 +1178,6 @@ export const Header: React.FC<HeaderProps> = ({
return lastProjectActionsContextRef.current;
}, [actionDirectory, activeProjectRef]);
const planModeEnabled = useFeatureFlagsStore((state) => state.planModeEnabled);
const isSessionPlanAvailable = useSessionUIStore((state) => state.isSessionPlanAvailable);
const planTabAvailable = planModeEnabled && currentSessionId ? isSessionPlanAvailable(currentSessionId) : false;
@@ -1508,17 +1506,17 @@ export const Header: React.FC<HeaderProps> = ({
const tabs: TabConfig[] = React.useMemo(() => {
if (isMobile) {
const base: TabConfig[] = [
{ id: 'chat', label: t('layout.mainTab.chat'), icon: RiChat4Line },
{ id: 'chat', label: t('layout.mainTab.chat'), icon: "chat-4" },
];
if (showPlanTab) {
base.push({ id: 'plan', label: t('layout.mainTab.plan'), icon: RiFileTextLine });
base.push({ id: 'plan', label: t('layout.mainTab.plan'), icon: "file-text" });
}
base.push(
{ id: 'diff', label: t('layout.mainTab.diff'), icon: 'diff' },
{ id: 'files', label: t('layout.mainTab.files'), icon: RiFolder6Line },
{ id: 'terminal', label: t('layout.mainTab.terminal'), icon: RiTerminalBoxLine },
{ id: 'files', label: t('layout.mainTab.files'), icon: "folder-6" },
{ id: 'terminal', label: t('layout.mainTab.terminal'), icon: "terminal-box" },
);
return base;
@@ -1539,13 +1537,13 @@ export const Header: React.FC<HeaderProps> = ({
}, [activeMainTab, isMobile, setActiveMainTab]);
const servicesTabs = React.useMemo(() => {
const base: Array<{ value: 'instance' | 'usage' | 'mcp'; label: string; icon: RemixiconComponentType }> = [];
const base: Array<{ value: 'instance' | 'usage' | 'mcp'; label: string; icon: IconName }> = [];
if (isDesktopApp) {
base.push({ value: 'instance', label: t('layout.services.instance'), icon: RiServerLine });
base.push({ value: 'instance', label: t('layout.services.instance'), icon: "server" });
}
base.push(
{ value: 'usage', label: t('layout.services.usage'), icon: RiTimerLine },
{ value: 'mcp', label: 'MCP', icon: McpIcon as unknown as RemixiconComponentType }
{ value: 'usage', label: t('layout.services.usage'), icon: "timer" },
{ value: 'mcp', label: 'MCP', icon: McpIcon as unknown as IconName }
);
return base;
}, [isDesktopApp, t]);
@@ -1554,7 +1552,7 @@ export const Header: React.FC<HeaderProps> = ({
return servicesTabs.map((tab) => ({
id: tab.value,
label: tab.label,
icon: <tab.icon className="h-3.5 w-3.5" />,
icon: <Icon name={tab.icon} className="h-3.5 w-3.5" />,
}));
}, [servicesTabs]);
@@ -1628,8 +1626,8 @@ export const Header: React.FC<HeaderProps> = ({
const mobileServicesTabItems = React.useMemo<SortableTabsStripItem[]>(() => {
return [
{ id: 'usage', label: t('layout.services.usage'), icon: <RiTimerLine className="h-3.5 w-3.5" /> },
{ id: 'mcp', label: 'MCP', icon: <RiCommandLine className="h-3.5 w-3.5" /> },
{ id: 'usage', label: t('layout.services.usage'), icon: <Icon name="timer" className="h-3.5 w-3.5" /> },
{ id: 'mcp', label: 'MCP', icon: <Icon name="command" className="h-3.5 w-3.5" /> },
];
}, [t]);
@@ -1709,14 +1707,14 @@ export const Header: React.FC<HeaderProps> = ({
const renderTab = (tab: TabConfig) => {
const isActive = activeMainTab === tab.id;
const isDiffTab = tab.icon === 'diff';
const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType);
const tabIconName = isDiffTab ? null : (tab.icon as IconName);
const isChatTab = tab.id === 'chat';
const renderIcon = (iconSize: number) => {
if (isDiffTab) {
return <DiffIcon size={iconSize} />;
}
return Icon ? <Icon size={iconSize} /> : null;
return tabIconName ? <Icon name={tabIconName} className={`h-${iconSize/4} w-${iconSize/4}`} /> : null;
};
const tabButton = (
@@ -1766,7 +1764,7 @@ export const Header: React.FC<HeaderProps> = ({
onClick={handleOpenContextPlan}
className={cn(desktopHeaderIconButtonClass, isContextPlanActive && 'bg-[var(--interactive-hover)]')}
>
<RiFileTextLine className="h-[18px] w-[18px]" />
<Icon name="file-text" className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent>
@@ -1807,13 +1805,13 @@ export const Header: React.FC<HeaderProps> = ({
title={t('header.actions.terminalPanelWithShortcut', { shortcut: shortcutLabel('toggle_terminal') })}
ariaLabel={t('header.actions.toggleTerminalPanelAria')}
onClick={toggleBottomTerminal}
Icon={RiTerminalBoxLine}
Icon={'terminal-box'}
/>
<HeaderIconActionButton
title={t('header.actions.rightSidebarWithShortcut', { shortcut: shortcutLabel('toggle_right_sidebar') })}
ariaLabel={t('header.actions.toggleRightSidebarAria')}
onClick={toggleRightSidebar}
Icon={RiLayoutRightLine}
Icon={'layout-right'}
/>
<DesktopGitHubControl
isMobile={isMobile}
@@ -1848,7 +1846,7 @@ export const Header: React.FC<HeaderProps> = ({
ariaLabel={t('header.actions.openSessionsAria')}
onClick={handleOpenSessionSwitcher}
className={`${desktopHeaderIconButtonClass} shrink-0`}
Icon={RiLayoutLeftLine}
Icon={'layout-left'}
/>
<div className={cn('flex min-w-0 flex-1 items-center', !isSidebarOpen && 'pl-3')}>
@@ -1861,7 +1859,7 @@ export const Header: React.FC<HeaderProps> = ({
onClick={handleHeaderNewSession}
className={cn(desktopHeaderIconButtonClass, 'mr-6 shrink-0')}
>
<RiChatNewLine className="h-[18px] w-[18px]" />
<Icon name="chat-new" className="h-[18px] w-[18px]" />
</button>
</TooltipTrigger>
<TooltipContent>
@@ -1886,7 +1884,7 @@ export const Header: React.FC<HeaderProps> = ({
{activeProjectLabel ? <span className="truncate">{activeProjectLabel}</span> : null}
{currentBranchLabel ? (
<span className="inline-flex min-w-0 items-center gap-0.5">
<RiGitBranchLine className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" />
<Icon name="git-branch" className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" />
<span className="truncate">{currentBranchLabel}</span>
</span>
) : null}
@@ -1902,7 +1900,7 @@ export const Header: React.FC<HeaderProps> = ({
"inline-flex min-w-0 items-center gap-0.5",
worktreeBadgeKind === 'attention' || worktreeBadgeKind === 'invalid' || worktreeBadgeKind === 'missing' ? 'text-status-warning' : 'text-muted-foreground/60'
)}>
<RiAlertLine className="h-3 w-3 flex-shrink-0" />
<Icon name="alert" className="h-3 w-3 flex-shrink-0" />
<span className="truncate">{worktreeBadge}</span>
</span>
) : null}
@@ -1943,7 +1941,7 @@ export const Header: React.FC<HeaderProps> = ({
ariaLabel={isNewSessionDraftOpen ? t('header.actions.newMiniChatAria') : t('header.actions.openSessionMiniChatAria')}
onClick={handleOpenCurrentMiniChat}
className={cn(desktopHeaderIconButtonClass, desktopSidebarActionsInline && showDesktopHeaderContextUsage ? 'mr-3.5' : 'mr-1')}
Icon={RiPictureInPicture2Line}
Icon={'picture-in-picture-2'}
/>
{desktopSidebarActionsInline ? desktopSidebarActions : null}
{!desktopSidebarActionsInline && desktopRightSidebarActionsHost
@@ -1968,7 +1966,7 @@ export const Header: React.FC<HeaderProps> = ({
)}
aria-label={leftDrawerOpen ? t('header.actions.closeSessionsAria') : t('header.actions.openSessionsAria')}
>
<RiLayoutLeftLine className="h-5 w-5" />
<Icon name="layout-left" className="h-5 w-5" />
</button>
) : isSessionSwitcherOpen ? (
<button
@@ -1977,7 +1975,7 @@ export const Header: React.FC<HeaderProps> = ({
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-interactive-active"
aria-label={t('header.actions.backAria')}
>
<RiArrowLeftSLine className="h-5 w-5" />
<Icon name="arrow-left-s" className="h-5 w-5" />
</button>
) : (
<button
@@ -1986,7 +1984,7 @@ export const Header: React.FC<HeaderProps> = ({
className="app-region-no-drag h-9 w-9 p-2 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded-md active:bg-interactive-active"
aria-label={t('header.actions.openSessionsAria')}
>
<RiPlayListAddLine className="h-5 w-5" />
<Icon name="play-list-add" className="h-5 w-5" />
</button>
)}
@@ -2009,7 +2007,7 @@ export const Header: React.FC<HeaderProps> = ({
{tabs.map((tab) => {
const isActive = activeMainTab === tab.id;
const isDiffTab = tab.icon === 'diff';
const Icon = isDiffTab ? null : (tab.icon as RemixiconComponentType);
const tabIconName = isDiffTab ? null : (tab.icon as IconName);
return (
<Tooltip key={tab.id}>
<TooltipTrigger asChild>
@@ -2032,8 +2030,8 @@ export const Header: React.FC<HeaderProps> = ({
>
{isDiffTab ? (
<DiffIcon className="h-5 w-5" />
) : Icon ? (
<Icon className="h-5 w-5" />
) : tabIconName ? (
<Icon name={tabIconName} className="h-5 w-5" />
) : null}
{tab.badge !== undefined && tab.badge > 0 && (
<span className="absolute -top-1 -right-1 text-[10px] font-semibold text-primary">
@@ -2088,7 +2086,7 @@ export const Header: React.FC<HeaderProps> = ({
aria-label={t('header.services.viewAria')}
className={mobileHeaderIconButtonClass}
>
<RiStackLine className="h-5 w-5" />
<Icon name="stack" className="h-5 w-5" />
</button>
</DropdownMenuTrigger>
</TooltipTrigger>
@@ -2128,7 +2126,7 @@ export const Header: React.FC<HeaderProps> = ({
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover"
aria-label={t('header.services.closeAria')}
>
<RiCloseLine className="h-5 w-5" />
<Icon name="close" className="h-5 w-5" />
</button>
</div>
</div>
@@ -2187,7 +2185,7 @@ export const Header: React.FC<HeaderProps> = ({
disabled={isQuotaLoading || isUsageRefreshSpinning}
aria-label={t('header.services.refreshRateLimitsAria')}
>
<RiRefreshLine className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
<Icon name="refresh" className={cn('h-4 w-4', isUsageRefreshSpinning && 'animate-spin')} />
</button>
</div>
</div>
@@ -2279,9 +2277,9 @@ export const Header: React.FC<HeaderProps> = ({
{family.familyLabel}
</span>
{isExpanded ? (
<RiArrowDownSLine className="h-4 w-4 text-muted-foreground" />
<Icon name="arrow-down-s" className="h-4 w-4 text-muted-foreground" />
) : (
<RiArrowRightSLine className="h-4 w-4 text-muted-foreground" />
<Icon name="arrow-right-s" className="h-4 w-4 text-muted-foreground" />
)}
</CollapsibleTrigger>
<CollapsibleContent>
@@ -2348,7 +2346,7 @@ export const Header: React.FC<HeaderProps> = ({
)}
aria-label={rightDrawerOpen ? 'Close git sidebar' : 'Open git sidebar'}
>
<RiLayoutRightLine className="h-5 w-5" />
<Icon name="layout-right" className="h-5 w-5" />
</button>
</TooltipTrigger>
<TooltipContent>
@@ -1,13 +1,4 @@
import React from 'react';
import {
RiAddLine,
RiArrowDownSLine,
RiGlobalLine,
RiLoader4Line,
RiPlayLine,
RiSearchLine,
RiStopLine,
} from '@remixicon/react';
import {
DropdownMenu,
DropdownMenuContent,
@@ -17,6 +8,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from '@/components/ui';
import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useDeviceInfo } from '@/lib/device';
@@ -737,9 +729,9 @@ export const ProjectActionsButton = ({
}
const selectedIconKey = (resolvedSelected.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
const SelectedIcon = resolvedSelected.id === AUTO_DISCOVER_ACTION_ID
? RiSearchLine
: PROJECT_ACTION_ICON_MAP[selectedIconKey] || RiPlayLine;
const selectedIconName = resolvedSelected.id === AUTO_DISCOVER_ACTION_ID
? 'search'
: PROJECT_ACTION_ICON_MAP[selectedIconKey] || 'play';
const selectedButtonLabel = formatActionButtonLabel(
resolvedSelected.name,
t('projectActions.label.fallbackAction'),
@@ -778,10 +770,10 @@ export const ProjectActionsButton = ({
: t('projectActions.actions.runNamedAria', { name: resolvedSelected.name })}
>
{isStoppingSelected || isWaitingForSelectedPreview
? <RiLoader4Line className="h-5 w-5 animate-spin text-[var(--status-warning)]" />
? <Icon name="loader-4" className="h-5 w-5 animate-spin text-[var(--status-warning)]" />
: selectedRunning
? <RiStopLine className="h-5 w-5 text-[var(--status-warning)]" />
: <SelectedIcon className="h-5 w-5" />}
? <Icon name="stop" className="h-5 w-5 text-[var(--status-warning)]" />
: <Icon name={selectedIconName} className="h-5 w-5" />}
</button>
{showSelectedPreviewButton ? (
<Tooltip>
@@ -792,7 +784,7 @@ export const ProjectActionsButton = ({
aria-label={t('projectActions.actions.openPreview')}
onClick={handleOpenSelectedPreview}
>
<RiGlobalLine className="h-4 w-4" />
<Icon name="global" className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent sideOffset={6}>{t('projectActions.actions.openPreview')}</TooltipContent>
@@ -805,20 +797,20 @@ export const ProjectActionsButton = ({
className="app-region-no-drag -ml-1 inline-flex h-9 w-5 items-center justify-center rounded-[10px] text-muted-foreground hover:bg-interactive-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
aria-label={t('projectActions.actions.chooseActionAria')}
>
<RiArrowDownSLine className="h-3.5 w-3.5" />
<Icon name="arrow-down-s" className="h-3.5 w-3.5" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 max-h-[70vh] overflow-y-auto">
<DropdownMenuItem className="flex items-center gap-2" onClick={openProjectActionsSettings}>
<RiAddLine className="h-4 w-4" />
<Icon name="add" className="h-4 w-4" />
<span className="typography-ui-label text-foreground">{t('projectActions.actions.addNewAction')}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{displayActions.map((entry) => {
const iconKey = (entry.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
const Icon = entry.id === AUTO_DISCOVER_ACTION_ID
? RiSearchLine
: PROJECT_ACTION_ICON_MAP[iconKey] || RiPlayLine;
const iconName = entry.id === AUTO_DISCOVER_ACTION_ID
? 'search'
: PROJECT_ACTION_ICON_MAP[iconKey] || 'play';
const runKey = toProjectActionRunKey(normalizedDirectory, entry.id);
const runState = projectActionRuns[runKey];
const isRunning = Boolean(runState);
@@ -832,12 +824,12 @@ export const ProjectActionsButton = ({
handleSelectAction(entry, true);
}}
>
<Icon className="h-4 w-4" />
<Icon name={iconName} className="h-4 w-4" />
<span className="typography-ui-label text-foreground truncate">{entry.name}</span>
{isStopping || runState?.status === 'waiting-for-preview'
? <RiLoader4Line className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
? <Icon name="loader-4" className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
: isRunning
? <RiStopLine className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
? <Icon name="stop" className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
: null}
</DropdownMenuItem>
);
@@ -873,10 +865,10 @@ export const ProjectActionsButton = ({
>
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
{isStoppingSelected || isWaitingForSelectedPreview
? <RiLoader4Line className="h-4 w-4 animate-spin text-[var(--status-warning)]" />
? <Icon name="loader-4" className="h-4 w-4 animate-spin text-[var(--status-warning)]" />
: selectedRunning
? <RiStopLine className="h-4 w-4 text-[var(--status-warning)]" />
: <SelectedIcon className="h-4 w-4" />}
? <Icon name="stop" className="h-4 w-4 text-[var(--status-warning)]" />
: <Icon name={selectedIconName} className="h-4 w-4" />}
</span>
{!compact ? <span className="header-open-label whitespace-nowrap">{selectedButtonLabel}</span> : null}
</button>
@@ -894,7 +886,7 @@ export const ProjectActionsButton = ({
)}
aria-label={t('projectActions.actions.openPreview')}
>
<RiGlobalLine className="h-4 w-4" />
<Icon name="global" className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent sideOffset={6}>{t('projectActions.actions.openPreview')}</TooltipContent>
@@ -912,20 +904,20 @@ export const ProjectActionsButton = ({
)}
aria-label={t('projectActions.actions.chooseActionAria')}
>
<RiArrowDownSLine className="h-4 w-4" />
<Icon name="arrow-down-s" className="h-4 w-4" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="w-52 max-h-[70vh] overflow-y-auto" style={{ translate: '-30px 0' }}>
<DropdownMenuItem className="flex items-center gap-2" onClick={openProjectActionsSettings}>
<RiAddLine className="h-4 w-4" />
<Icon name="add" className="h-4 w-4" />
<span className="typography-ui-label text-foreground">{t('projectActions.actions.addNewAction')}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
{displayActions.map((entry) => {
const iconKey = (entry.icon || 'play') as keyof typeof PROJECT_ACTION_ICON_MAP;
const Icon = entry.id === AUTO_DISCOVER_ACTION_ID
? RiSearchLine
: PROJECT_ACTION_ICON_MAP[iconKey] || RiPlayLine;
const iconName = entry.id === AUTO_DISCOVER_ACTION_ID
? 'search'
: PROJECT_ACTION_ICON_MAP[iconKey] || 'play';
const runKey = toProjectActionRunKey(normalizedDirectory, entry.id);
const runState = projectActionRuns[runKey];
const isRunning = Boolean(runState);
@@ -939,12 +931,12 @@ export const ProjectActionsButton = ({
handleSelectAction(entry);
}}
>
<Icon className="h-4 w-4" />
<Icon name={iconName} className="h-4 w-4" />
<span className="typography-ui-label text-foreground truncate">{entry.name}</span>
{isStopping || runState?.status === 'waiting-for-preview'
? <RiLoader4Line className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
? <Icon name="loader-4" className="ml-auto h-4 w-4 animate-spin text-[var(--status-warning)]" />
: isRunning
? <RiStopLine className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
? <Icon name="stop" className="ml-auto h-4 w-4 text-[var(--status-warning)]" />
: null}
</DropdownMenuItem>
);
@@ -14,6 +14,7 @@ import { PROJECT_ICONS, PROJECT_COLORS, PROJECT_COLOR_MAP, getProjectIconImageUr
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useI18n } from '@/lib/i18n';
import { Icon } from "@/components/icon/Icon";
interface ProjectEditDialogProps {
open: boolean;
@@ -329,7 +330,7 @@ export const ProjectEditDialog: React.FC<ProjectEditDialogProps> = ({
<span className="w-4 h-0.5 bg-muted-foreground/40 rotate-45 rounded-full" />
</button>
{PROJECT_ICONS.map((i) => {
const IconComponent = i.Icon;
const iconName = i.Icon;
return (
<button
key={i.key}
@@ -343,7 +344,7 @@ export const ProjectEditDialog: React.FC<ProjectEditDialogProps> = ({
)}
title={i.label}
>
<IconComponent
<Icon name={iconName}
className="w-4 h-4"
style={currentColorVar ? { color: currentColorVar } : undefined}
/>
@@ -1,9 +1,9 @@
import React from 'react';
import { RiBookletLine, RiFolder3Line, RiGitBranchLine } from '@remixicon/react';
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
import { ProjectNotesTodoPanel } from '@/components/session/ProjectNotesTodoPanel';
import { GitView } from '@/components/views/GitView';
import { Icon } from "@/components/icon/Icon";
import { useGitStore } from '@/stores/useGitStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -103,17 +103,17 @@ export const RightSidebarTabs: React.FC = () => {
{
id: 'git',
label: t('layout.rightSidebar.git'),
icon: <RiGitBranchLine className="h-3.5 w-3.5" />,
icon: <Icon name="git-branch" className="h-3.5 w-3.5" />,
},
{
id: 'files',
label: t('layout.rightSidebar.files'),
icon: <RiFolder3Line className="h-3.5 w-3.5" />,
icon: <Icon name="folder-3" className="h-3.5 w-3.5" />,
},
{
id: 'context',
label: t('layout.rightSidebar.context'),
icon: <RiBookletLine className="h-3.5 w-3.5" />,
icon: <Icon name="booklet" className="h-3.5 w-3.5" />,
},
], [t]);
@@ -1,20 +1,4 @@
import React from 'react';
import {
RiCloseLine,
RiDeleteBinLine,
RiEditLine,
RiFileAddLine,
RiFileCopyLine,
RiFolder3Fill,
RiFolderAddLine,
RiFolderOpenFill,
RiFolderReceivedLine,
RiLoader4Line,
RiMore2Fill,
RiRefreshLine,
RiSearchLine,
RiDownloadLine,
} from '@remixicon/react';
import { toast } from '@/components/ui';
import {
@@ -48,6 +32,7 @@ import { copyTextToClipboard } from '@/lib/clipboard';
import { cn, getRevealLabelKey } from '@/lib/utils';
import { opencodeClient } from '@/lib/opencode/client';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
import { Icon } from "@/components/icon/Icon";
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
import { useI18n } from '@/lib/i18n';
@@ -221,9 +206,9 @@ const FileRow: React.FC<FileRowProps> = ({
>
{isDir ? (
isExpanded ? (
<RiFolderOpenFill className="h-4 w-4 flex-shrink-0 text-primary/60" />
<Icon name="folder-open-fill" className="h-4 w-4 flex-shrink-0 text-primary/60" />
) : (
<RiFolder3Fill className="h-4 w-4 flex-shrink-0 text-primary/60" />
<Icon name="folder-3-fill" className="h-4 w-4 flex-shrink-0 text-primary/60" />
)
) : (
getFileIcon(node.path, node.extension)
@@ -252,13 +237,13 @@ const FileRow: React.FC<FileRowProps> = ({
className="h-6 w-6"
onClick={handleMenuButtonClick}
>
<RiMore2Fill className="h-4 w-4" />
<Icon name="more-2-fill" className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" onCloseAutoFocus={() => setContextMenuPath(null)}>
{canRename && (
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('rename', node); }}>
<RiEditLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.rename')}
<Icon name="edit" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.rename')}
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={(e) => {
@@ -271,19 +256,19 @@ const FileRow: React.FC<FileRowProps> = ({
toast.error(t('sidebarFilesTree.toast.copyFailed'));
});
}}>
<RiFileCopyLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.copyPath')}
<Icon name="file-copy" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.copyPath')}
</DropdownMenuItem>
{!isDir && downloadFile && (
<DropdownMenuItem onClick={(e) => {
e.stopPropagation();
void downloadFile(node.path);
}}>
<RiDownloadLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.save')}
<Icon name="download" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.save')}
</DropdownMenuItem>
)}
{canReveal && (
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onRevealPath(node.path); }}>
<RiFolderReceivedLine className="mr-2 h-4 w-4" /> {t(getRevealLabelKey())}
<Icon name="folder-received" className="mr-2 h-4 w-4" /> {t(getRevealLabelKey())}
</DropdownMenuItem>
)}
{isDir && (canCreateFile || canCreateFolder) && (
@@ -291,12 +276,12 @@ const FileRow: React.FC<FileRowProps> = ({
<DropdownMenuSeparator />
{canCreateFile && (
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('createFile', node); }}>
<RiFileAddLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFile')}
<Icon name="file-add" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFile')}
</DropdownMenuItem>
)}
{canCreateFolder && (
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onOpenDialog('createFolder', node); }}>
<RiFolderAddLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFolder')}
<Icon name="folder-add" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.newFolder')}
</DropdownMenuItem>
)}
</>
@@ -308,7 +293,7 @@ const FileRow: React.FC<FileRowProps> = ({
onClick={(e) => { e.stopPropagation(); onOpenDialog('delete', node); }}
className="text-destructive focus:text-destructive"
>
<RiDeleteBinLine className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.delete')}
<Icon name="delete-bin" className="mr-2 h-4 w-4" /> {t('sidebarFilesTree.menu.delete')}
</DropdownMenuItem>
</>
)}
@@ -817,7 +802,7 @@ export const SidebarFilesTree: React.FC = () => {
<section className="flex h-full min-h-0 flex-col overflow-hidden bg-sidebar">
<div className="flex items-center gap-2 border-b border-border/40 px-3 py-2">
<div className="relative min-w-0 flex-1">
<RiSearchLine className="pointer-events-none absolute left-2 top-2 h-4 w-4 text-muted-foreground" />
<Icon name="search" className="pointer-events-none absolute left-2 top-2 h-4 w-4 text-muted-foreground" />
<Input
ref={searchInputRef}
value={searchQuery}
@@ -835,7 +820,7 @@ export const SidebarFilesTree: React.FC = () => {
searchInputRef.current?.focus();
}}
>
<RiCloseLine className="h-4 w-4" />
<Icon name="close" className="h-4 w-4" />
</button>
) : null}
</div>
@@ -847,7 +832,7 @@ export const SidebarFilesTree: React.FC = () => {
className="h-8 w-8 p-0 flex-shrink-0"
title={t('sidebarFilesTree.actions.newFileTitle')}
>
<RiFileAddLine className="h-4 w-4" />
<Icon name="file-add" className="h-4 w-4" />
</Button>
)}
{canCreateFolder && (
@@ -858,11 +843,11 @@ export const SidebarFilesTree: React.FC = () => {
className="h-8 w-8 p-0 flex-shrink-0"
title={t('sidebarFilesTree.actions.newFolderTitle')}
>
<RiFolderAddLine className="h-4 w-4" />
<Icon name="folder-add" className="h-4 w-4" />
</Button>
)}
<Button variant="ghost" size="sm" onClick={() => void refreshRoot()} className="h-8 w-8 p-0 flex-shrink-0" title={t('sidebarFilesTree.actions.refreshTitle')}>
<RiRefreshLine className="h-4 w-4" />
<Icon name="refresh" className="h-4 w-4" />
</Button>
</div>
@@ -870,7 +855,7 @@ export const SidebarFilesTree: React.FC = () => {
<ul className="flex flex-col">
{searching ? (
<li className="flex items-center gap-1.5 px-2 py-1 typography-meta text-muted-foreground">
<RiLoader4Line className="h-4 w-4 animate-spin" />
<Icon name="loader-4" className="h-4 w-4 animate-spin" />
{t('sidebarFilesTree.state.searching')}
</li>
) : searchResults.length > 0 ? (
@@ -956,7 +941,7 @@ export const SidebarFilesTree: React.FC = () => {
onClick={() => void handleDialogSubmit()}
disabled={isDialogSubmitting || (activeDialog !== 'delete' && !dialogInputValue.trim())}
>
{isDialogSubmitting ? <RiLoader4Line className="animate-spin" /> : (
{isDialogSubmitting ? <Icon name="loader-4" className="animate-spin" /> : (
activeDialog === 'delete' ? t('sidebarFilesTree.dialog.delete.confirm') : t('sidebarFilesTree.dialog.confirm')
)}
</Button>
@@ -23,6 +23,7 @@ import { useI18n } from '@/lib/i18n';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { UsageProgressBar } from '@/components/sections/usage/UsageProgressBar';
import { PaceIndicator } from '@/components/sections/usage/PaceIndicator';
import { Icon } from "@/components/icon/Icon";
import { formatQuotaValueLabel, formatWindowLabel, QUOTA_PROVIDERS, calculatePace, calculateExpectedUsagePercent } from '@/lib/quota';
import { useQuotaAutoRefresh, useQuotaStore } from '@/stores/useQuotaStore';
import { useUpdateStore } from '@/stores/useUpdateStore';
@@ -30,7 +31,6 @@ import { updateDesktopSettings } from '@/lib/persistence';
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
import type { UsageWindow } from '@/types';
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
import { RiAddLine, RiArrowLeftLine, RiRefreshLine, RiRobot2Line, RiSettings3Line, RiTimerLine } from '@remixicon/react';
const SettingsView = lazyWithChunkRecovery(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView })));
@@ -220,7 +220,6 @@ export const VSCodeLayout: React.FC = () => {
setCurrentView('sessions');
}, []);
// Listen for connection status changes
React.useEffect(() => {
// Catch up with the latest status even if the extension posted the connection message
@@ -695,7 +694,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
className="inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
aria-label={t('vscodeLayout.actions.backToSessionsAria')}
>
<RiArrowLeftLine className="h-5 w-5" />
<Icon name="arrow-left" className="h-5 w-5" />
</button>
)}
<h1 className="text-sm font-medium truncate flex-1" title={title}>{title}</h1>
@@ -705,7 +704,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
aria-label={t('vscodeLayout.actions.newSessionAria')}
>
<RiAddLine className="h-5 w-5" />
<Icon name="add" className="h-5 w-5" />
</button>
)}
{onAgentManager && (
@@ -714,7 +713,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
aria-label={t('vscodeLayout.actions.openAgentManagerAria')}
>
<RiRobot2Line className="h-5 w-5" />
<Icon name="robot-2" className="h-5 w-5" />
</button>
)}
{showMcp && (
@@ -737,7 +736,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
disabled={isQuotaLoading}
>
<RiTimerLine className="h-5 w-5" />
<Icon name="timer" className="h-5 w-5" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
@@ -785,7 +784,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
disabled={isQuotaLoading}
aria-label={t('vscodeLayout.quota.actions.refreshAria')}
>
<RiRefreshLine className="h-4 w-4" />
<Icon name="refresh" className="h-4 w-4" />
</button>
</div>
</DropdownMenuLabel>
@@ -870,7 +869,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
className="inline-flex h-9 w-9 items-center justify-center p-2 text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
aria-label={t('vscodeLayout.actions.settingsAria')}
>
<RiSettings3Line className="h-5 w-5" />
<Icon name="settings-3" className="h-5 w-5" />
</button>
)}
{showContextUsage && stableContextUsage && stableContextUsage.totalTokens > 0 && (