= ({ onOpenSettings, scrollTo
title={t('chat.chatInput.actions.attachFiles')}
aria-label={t('chat.chatInput.actions.attachFiles')}
>
-
+
diff --git a/packages/ui/src/components/chat/CommandAutocomplete.tsx b/packages/ui/src/components/chat/CommandAutocomplete.tsx
index 0b9625fc..ff5caceb 100644
--- a/packages/ui/src/components/chat/CommandAutocomplete.tsx
+++ b/packages/ui/src/components/chat/CommandAutocomplete.tsx
@@ -1,11 +1,11 @@
import React from 'react';
-import { RiCommandLine, RiFileLine, RiFlashlightLine, RiRefreshLine, RiScissorsLine, RiTerminalBoxLine, RiArrowGoBackLine, RiArrowGoForwardLine, RiSearchEyeLine, RiTimeLine } from '@remixicon/react';
import { cn, fuzzyMatch } from '@/lib/utils';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessionMessages } from '@/sync/sync-context';
import { useCommandsStore } from '@/stores/useCommandsStore';
import { useSkillsStore } from '@/stores/useSkillsStore';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
type CommandSource = 'openchamber' | 'opencode';
@@ -243,26 +243,26 @@ export const CommandAutocomplete = React.forwardRef ;
+ return ;
case 'undo':
- return ;
+ return ;
case 'redo':
- return ;
+ return ;
case 'timeline':
- return ;
+ return ;
case 'compact':
- return ;
+ return ;
case 'review':
- return ;
+ return ;
case 'test':
case 'build':
case 'run':
- return ;
+ return ;
default:
if (command.isBuiltIn) {
- return ;
+ return ;
}
- return ;
+ return ;
}
};
@@ -315,7 +315,7 @@ export const CommandAutocomplete = React.forwardRef
{loading ? (
-
+
) : (
diff --git a/packages/ui/src/components/chat/FileAttachment.tsx b/packages/ui/src/components/chat/FileAttachment.tsx
index 1caecbb0..55f9ff9e 100644
--- a/packages/ui/src/components/chat/FileAttachment.tsx
+++ b/packages/ui/src/components/chat/FileAttachment.tsx
@@ -1,5 +1,4 @@
import React, { useRef, memo } from 'react';
-import { RiAttachment2, RiCloseLine, RiFileImageLine, RiFileLine, RiFilePdfLine, RiGithubLine, RiGitPullRequestLine, RiAddLine, RiPushpin2Line } from '@remixicon/react';
import { useInputStore } from '@/sync/input-store';
import type { AttachedFile } from '@/sync/session-ui-store';
import { useUIStore } from '@/stores/useUIStore';
@@ -9,6 +8,7 @@ import { openExternalUrl } from '@/lib/url';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { useIsVSCodeRuntime } from '@/hooks/useRuntimeAPIs';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { useDeviceInfo } from '@/lib/device';
@@ -108,7 +108,7 @@ export const FileAttachmentButton = memo(() => {
)}
aria-label={t('chat.fileAttachment.actions.attachAria')}
>
-
+
@@ -171,7 +171,7 @@ const ImagePreview = memo(({ file, onRemove }: ImagePreviewProps) => {
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
>
-
+
);
@@ -194,7 +194,7 @@ const ImagePreview = memo(({ file, onRemove }: ImagePreviewProps) => {
title={t('chat.fileAttachment.actions.removeImage')}
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
>
-
+
);
@@ -263,7 +263,7 @@ const FileChip = memo(({ file, onRemove }: FileChipProps) => {
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
aria-label={t('chat.fileAttachment.actions.removeNamed', { name: displayName })}
>
-
+
);
@@ -301,7 +301,7 @@ const VSCodeFileChip = memo(({ file, onRemove }: FileChipProps) => {
aria-label={t('chat.fileAttachment.activeEditor.remove')}
title={t('chat.fileAttachment.activeEditor.remove')}
>
-
+
@@ -450,7 +450,7 @@ export const ActiveEditorFileSuggestion = memo(() => {
onClick={() => { void handlePinSelection(); }}
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
>
-
+
{`${displayName}:${selectionRange}`}
@@ -469,7 +469,7 @@ export const ActiveEditorFileSuggestion = memo(() => {
onClick={handleAddFile}
className="flex items-center justify-center h-5 w-5 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors cursor-pointer"
>
-
+
{displayName}
@@ -610,9 +610,9 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
className="inline-flex items-center bg-muted/30 border border-border/30 typography-meta gap-1 px-2 py-0.5 rounded-lg text-foreground hover:text-primary transition-colors"
>
{githubLinkKind === 'pr' ? (
-
+
) : (
-
+
)}
{fileName}
@@ -621,7 +621,7 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
) : (
{file.mime?.includes('pdf') ? (
-
+
) : (
)}
@@ -668,7 +668,7 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
/>
) : (
-
+
)}
{filename}
@@ -735,9 +735,9 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
>
{githubLinkKind === 'pr' ? (
-
+
) : (
-
+
)}
@@ -779,11 +779,11 @@ export const MessageFilesDisplay = memo(({ files, onShowPopup, compact = false }
>
{file.mime?.startsWith('image/') ? (
-
+
) : file.mime?.includes('pdf') ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/chat/FileMentionAutocomplete.tsx b/packages/ui/src/components/chat/FileMentionAutocomplete.tsx
index f3bf02bd..e607fca4 100644
--- a/packages/ui/src/components/chat/FileMentionAutocomplete.tsx
+++ b/packages/ui/src/components/chat/FileMentionAutocomplete.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiCodeLine, RiFileImageLine, RiFileLine, RiFilePdfLine, RiFolder3Fill, RiRefreshLine } from '@remixicon/react';
import { cn, truncatePathMiddle } from '@/lib/utils';
import { useFileSearchStore } from '@/stores/useFileSearchStore';
import { useConfigStore } from '@/stores/useConfigStore';
@@ -9,6 +8,7 @@ import { useDebouncedValue } from '@/hooks/useDebouncedValue';
import { useChatSearchDirectory } from '@/hooks/useChatSearchDirectory';
import type { ProjectFileSearchHit } from '@/lib/opencode/client';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
import { useI18n } from '@/lib/i18n';
@@ -431,20 +431,20 @@ export const FileMentionAutocomplete = React.forwardRef
;
+ return
;
case 'json':
- return
;
+ return
;
case 'md':
case 'mdx':
- return
;
+ return
;
case 'png':
case 'jpg':
case 'jpeg':
case 'gif':
case 'svg':
- return
;
+ return
;
default:
- return
;
+ return
;
}
};
@@ -499,7 +499,7 @@ export const FileMentionAutocomplete = React.forwardRef
{(!scopeResultsToActiveTab || activeTab === 'files') && loading ? (
-
+
) : (
@@ -550,7 +550,7 @@ export const FileMentionAutocomplete = React.forwardRef
handleFileSelect(dir)}
onMouseEnter={() => setSelectedIndex(rowIndex)}
>
-
+
{displayPath}
diff --git a/packages/ui/src/components/chat/MarkdownRendererImpl.tsx b/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
index 87c226ad..4cd70dbb 100644
--- a/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
+++ b/packages/ui/src/components/chat/MarkdownRendererImpl.tsx
@@ -11,9 +11,9 @@ import remend from 'remend';
import { FadeInOnReveal } from './message/FadeInOnReveal';
import type { Part } from '@opencode-ai/sdk/v2';
import { cn } from '@/lib/utils';
-import { RiFileCopyLine, RiCheckLine, RiDownloadLine, RiEyeLine, RiCodeLine } from '@remixicon/react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { copyTextToClipboard } from '@/lib/clipboard';
import { useI18n } from '@/lib/i18n';
@@ -228,7 +228,7 @@ const TableCopyButton: React.FC<{ tableRef: React.RefObject
- {copied ? : }
+ {copied ? : }
{showMenu && (
@@ -286,7 +286,7 @@ const TableDownloadButton: React.FC<{ tableRef: React.RefObject
-
+
{showMenu && (
@@ -425,7 +425,7 @@ const MermaidBlock: React.FC<{ source: string; mode: 'svg' | 'ascii' }> = ({ sou
className="p-1 rounded hover:bg-interactive-hover/60 text-muted-foreground hover:text-foreground transition-colors"
title={t('markdownRenderer.mermaid.actions.copyTitle')}
>
- {copied ? : }
+ {copied ? : }
@@ -449,7 +449,7 @@ const MermaidBlock: React.FC<{ source: string; mode: 'svg' | 'ascii' }> = ({ sou
className="p-1 rounded hover:bg-interactive-hover/60 text-muted-foreground hover:text-foreground transition-colors"
title={t('markdownRenderer.mermaid.actions.copyTitle')}
>
- {copied ? : }
+ {copied ? : }
@@ -472,14 +472,14 @@ const MermaidBlock: React.FC<{ source: string; mode: 'svg' | 'ascii' }> = ({ sou
className="p-1 rounded hover:bg-interactive-hover/60 text-muted-foreground hover:text-foreground transition-colors"
title={t('markdownRenderer.mermaid.actions.copySourceTitle')}
>
- {copied ?
:
}
+ {copied ?
:
}
- {downloaded ? : }
+ {downloaded ? : }
@@ -793,7 +793,7 @@ const MarkdownCodeBlock: React.FC<{
aria-pressed={viewMode === 'preview'}
aria-label={viewMode === 'preview' ? 'Show code' : 'Preview HTML'}
>
- {viewMode === 'preview' ?
:
}
+ {viewMode === 'preview' ?
:
}
) : null}
{canPreview ? (
@@ -804,7 +804,7 @@ const MarkdownCodeBlock: React.FC<{
title="Download HTML"
aria-label="Download HTML"
>
-
+
) : null}
- {copied ? : }
+ {copied ? : }
@@ -959,7 +959,7 @@ const buildMarkdownComponents = ({
title={previewTitle ?? previewLabel ?? 'Open preview pane'}
data-loopback-preview-trigger="true"
>
-
+
{previewLabel ?? 'Preview'}
) : null}
diff --git a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx
index d927ead9..801e82ea 100644
--- a/packages/ui/src/components/chat/MobileSessionStatusBar.tsx
+++ b/packages/ui/src/components/chat/MobileSessionStatusBar.tsx
@@ -9,15 +9,6 @@ import type { Session } from '@opencode-ai/sdk/v2';
import type { ProjectEntry } from '@/lib/api/types';
import { cn, formatDirectoryName } from '@/lib/utils';
import { getAgentColor } from '@/lib/agentColors';
-import {
- RiLoader4Line,
- RiAddLine,
- RiDragMove2Line,
- RiDeleteBinLine,
- RiEditLine,
- RiArrowUpLine,
- RiArrowDownLine,
-} from '@remixicon/react';
import {
DndContext,
closestCenter,
@@ -51,6 +42,7 @@ import { Button } from '@/components/ui/button';
import { ProjectEditDialog } from '@/components/layout/ProjectEditDialog';
import { useDrawerSwipe } from '@/hooks/useDrawerSwipe';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useNotificationStore } from '@/sync/notification-store';
import { useI18n } from '@/lib/i18n';
@@ -275,7 +267,7 @@ function useProjectStatus(
function StatusIndicator({ isRunning, needsAttention }: { isRunning: boolean; needsAttention: boolean }) {
if (isRunning) {
- return ;
+ return ;
}
if (needsAttention) {
return
;
@@ -287,7 +279,7 @@ function RunningIndicator({ count }: { count: number }) {
if (count === 0) return null;
return (
-
+
{count}
);
@@ -369,10 +361,9 @@ function SessionItem({
className="flex-shrink-0"
title={`Sub-session: ${getSessionTitle(child)}`}
>
-
+
);
})}
@@ -428,7 +419,7 @@ function SessionStatusHeader({
childIndicators = []
}: SessionStatusHeaderProps) {
const [imageFailed, setImageFailed] = React.useState(false);
- const ProjectIcon = currentProjectIcon ? PROJECT_ICON_MAP[currentProjectIcon] : null;
+ const projectIconName = currentProjectIcon ? PROJECT_ICON_MAP[currentProjectIcon] : null;
const imageUrl = !imageFailed ? currentProjectIconImageUrl : null;
const projectColorVar = currentProjectColor ? (PROJECT_COLOR_MAP[currentProjectColor] ?? null) : null;
const extraCount = childIndicators.length > 3 ? childIndicators.length - 3 : 0;
@@ -459,8 +450,8 @@ function SessionStatusHeader({
onError={() => setImageFailed(true)}
/>
- ) : ProjectIcon && (
-
@@ -492,10 +483,9 @@ function SessionStatusHeader({
className="flex-shrink-0"
title={`Sub-session: ${child.session.title || 'Untitled'}`}
>
-
+
);
})}
@@ -513,8 +503,6 @@ function SessionStatusHeader({
);
}
-
-
// Hook for long press with movement detection
function useLongPress(
onLongPress: () => void,
@@ -625,7 +613,7 @@ function SortableProjectItem({
};
const [imageFailed, setImageFailed] = React.useState(false);
- const ProjectIcon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
+ const projectIconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const projectIconImageUrl = !imageFailed
? getProjectIconImageUrl(project, {
themeVariant: currentTheme.metadata.variant,
@@ -650,7 +638,7 @@ function SortableProjectItem({
{...attributes}
{...listeners}
>
-
+
{/* Project info */}
@@ -668,8 +656,8 @@ function SortableProjectItem({
onError={() => setImageFailed(true)}
/>
- ) : ProjectIcon ? (
-
@@ -690,7 +678,7 @@ function SortableProjectItem({
disabled={isFirst}
className="p-1.5 rounded text-[var(--surface-mutedForeground)] hover:text-[var(--surface-foreground)] hover:bg-[var(--interactive-hover)] disabled:opacity-30 disabled:cursor-not-allowed"
>
-
+
-
+
@@ -709,7 +697,7 @@ function SortableProjectItem({
onClick={onEdit}
className="p-1.5 rounded text-[var(--surface-mutedForeground)] hover:text-[var(--primary-base)] hover:bg-[var(--primary-base)]/10"
>
-
+
{/* Delete button */}
@@ -718,7 +706,7 @@ function SortableProjectItem({
onClick={onDelete}
className="p-1.5 rounded text-[var(--surface-mutedForeground)] hover:text-[var(--status-error)] hover:bg-[var(--status-error)]/10"
>
-
+
@@ -863,7 +851,7 @@ function ProjectButton({
}: ProjectButtonProps) {
const { currentTheme } = useThemeSystem();
const [imageFailed, setImageFailed] = React.useState(false);
- const ProjectIcon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
+ const projectIconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const projectIconImageUrl = !imageFailed
? getProjectIconImageUrl(project, {
themeVariant: currentTheme.metadata.variant,
@@ -900,7 +888,7 @@ function ProjectButton({
{/* Status indicators */}
{status.hasRunning && (
-
+
)}
{!status.hasRunning && status.hasUnread && (
@@ -921,8 +909,8 @@ function ProjectButton({
onError={() => setImageFailed(true)}
/>
- ) : ProjectIcon && (
-
@@ -1021,7 +1009,7 @@ function ProjectBar({
className="flex items-center justify-center !py-1.5 px-2 rounded-md border border-[var(--primary-base)]/60 bg-[var(--primary-base)]/5 text-[var(--primary-base)]/80 hover:text-[var(--primary-base)] hover:bg-[var(--primary-base)]/10 !min-h-0"
aria-label={t('chat.mobileStatus.projects.addAria')}
>
-
+
);
@@ -1097,7 +1085,7 @@ function ProjectBar({
className="flex items-center justify-center !py-1.5 px-2 rounded-md border border-[var(--primary-base)]/60 bg-[var(--primary-base)]/5 text-[var(--primary-base)]/80 hover:text-[var(--primary-base)] hover:bg-[var(--primary-base)]/10 shrink-0 !min-h-0"
aria-label={t('chat.mobileStatus.projects.addAria')}
>
-
+
{/* Delete confirmation dialog */}
diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx
index c6357440..c7959c8c 100644
--- a/packages/ui/src/components/chat/ModelControls.tsx
+++ b/packages/ui/src/components/chat/ModelControls.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import type { ComponentType } from 'react';
import {
DndContext,
PointerSensor,
@@ -10,31 +9,6 @@ import {
} from '@dnd-kit/core';
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { CSS as DndCSS } from '@dnd-kit/utilities';
-import {
- RiAddLine,
- RiAiAgentLine,
- RiArrowDownSLine,
- RiArrowGoBackLine,
- RiArrowRightSLine,
- RiBrainAi3Line,
- RiCheckLine,
- RiCheckboxCircleLine,
- RiCloseCircleLine,
- RiDraggable,
- RiFileImageLine,
- RiFileMusicLine,
- RiFilePdfLine,
- RiFileVideoLine,
- RiLoader4Line,
- RiPencilAiLine,
- RiQuestionLine,
- RiSearchLine,
- RiStarFill,
- RiStarLine,
- RiText,
- RiTimeLine,
- RiToolsLine,
-} from '@remixicon/react';
import type { EditPermissionMode } from '@/stores/types/sessionTypes';
import type { ModelMetadata } from '@/types';
import {
@@ -51,6 +25,8 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { TextLoop } from '@/components/ui/TextLoop';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
+import type { IconName } from "@/components/icon/icons";
import { useIsVSCodeRuntime } from '@/hooks/useRuntimeAPIs';
import { isDesktopShell } from '@/lib/desktop';
import { getAgentColor } from '@/lib/agentColors';
@@ -71,8 +47,8 @@ import { formatEffortLabel, getCycledPrimaryAgentName, type MobileControlsPanel
import { useI18n } from '@/lib/i18n';
import { useOpenCodeReadiness } from '@/hooks/useOpenCodeReadiness';
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type IconComponent = ComponentType;
+
+type IconComponent = IconName;
type ProviderModel = Record & { id?: string; name?: string };
@@ -172,13 +148,13 @@ interface CapabilityDefinition {
const CAPABILITY_DEFINITIONS: CapabilityDefinition[] = [
{
key: 'tool_call',
- icon: RiToolsLine,
+ icon: "tools",
label: 'Tool calling',
isActive: (metadata) => metadata?.tool_call === true,
},
{
key: 'reasoning',
- icon: RiBrainAi3Line,
+ icon: "brain-ai-3",
label: 'Reasoning',
isActive: (metadata) => metadata?.reasoning === true,
},
@@ -198,11 +174,11 @@ type ModalityIcon = {
type ModelApplyResult = 'applied' | 'provider-missing' | 'model-missing';
const MODALITY_ICON_MAP: Record = {
- text: { icon: RiText, label: 'Text' },
- image: { icon: RiFileImageLine, label: 'Image' },
- video: { icon: RiFileVideoLine, label: 'Video' },
- audio: { icon: RiFileMusicLine, label: 'Audio' },
- pdf: { icon: RiFilePdfLine, label: 'PDF' },
+ text: { icon: "text", label: 'Text' },
+ image: { icon: "file-image", label: 'Image' },
+ video: { icon: "file-video", label: 'Video' },
+ audio: { icon: "file-music", label: 'Audio' },
+ pdf: { icon: "file-pdf", label: 'PDF' },
};
const normalizeModality = (value: string) => value.trim().toLowerCase();
@@ -587,15 +563,15 @@ export const ModelControls: React.FC = ({
const iconStyle = { color: iconColor };
if (mode === 'full') {
- return ;
+ return ;
}
if (mode === 'allow') {
- return ;
+ return ;
}
if (mode === 'deny') {
- return ;
+ return ;
}
- return ;
+ return ;
}, [editToggleIconClass]);
const currentProvider = getCurrentProvider();
@@ -1359,7 +1335,7 @@ export const ModelControls: React.FC = ({
return name.charAt(0).toUpperCase() + name.slice(1);
};
- const renderIconBadge = (IconComp: IconComponent, label: string, key: string) => (
+ const renderIconBadge = (iconName: IconComponent, label: string, key: string) => (
= ({
aria-label={label}
role="img"
>
-
+
);
@@ -1577,7 +1553,6 @@ export const ModelControls: React.FC = ({
)}
-
{}
{t('chat.modelControls.permissions')}
@@ -1617,7 +1592,7 @@ export const ModelControls: React.FC
= ({
{t('chat.modelControls.customPrompt')}
-
+
)}
@@ -1756,7 +1731,7 @@ export const ModelControls: React.FC = ({
{getModelDisplayName(model)}
- {isSelected ? : null}
+ {isSelected ? : null}
{contextText || indicatorIcons.length > 0 ? (
@@ -1770,14 +1745,14 @@ export const ModelControls: React.FC
= ({
) : null}
{indicatorIcons.length > 0 ? (
- {indicatorIcons.map(({ key, icon: IconComponent, label }) => (
+ {indicatorIcons.map(({ key, icon: iconName, label }) => (
-
+
))}
@@ -1795,7 +1770,7 @@ export const ModelControls: React.FC = ({
aria-label={isExpanded ? t('chat.modelControls.hideThinkingModes') : t('chat.modelControls.showThinkingModes')}
>
{variantLabel}
- {isExpanded ? : }
+ {isExpanded ? : }
) : null}
@@ -1818,9 +1793,9 @@ export const ModelControls: React.FC = ({
: t('chat.modelControls.addToFavorites')}
>
{isFavoriteModel(providerId, modelId) ? (
-
+
) : (
-
+
)}
@@ -1875,7 +1850,7 @@ export const ModelControls: React.FC = ({
-
+
setMobileModelQuery(event.target.value)}
@@ -1889,7 +1864,7 @@ export const ModelControls: React.FC = ({
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
aria-label={t('chat.modelControls.clearSearch')}
>
-
+
)}
@@ -1905,7 +1880,7 @@ export const ModelControls: React.FC
= ({
{filteredFavorites.length > 0 && (
-
+
{t('chat.modelControls.favorites')}
@@ -1923,7 +1898,7 @@ export const ModelControls: React.FC
= ({
{filteredRecents.length > 0 && (
-
+
{t('chat.modelControls.recent')}
@@ -1971,9 +1946,9 @@ export const ModelControls: React.FC = ({
)}
{isExpanded ? (
-
+
) : (
-
+
)}
@@ -2037,7 +2012,7 @@ export const ModelControls: React.FC
= ({
onClick={handleBack}
className="flex items-center gap-1 rounded-lg px-1.5 py-1 typography-meta text-muted-foreground hover:bg-interactive-hover"
>
-
+
{t('onboarding.common.actions.back')}
{t('chat.modelControls.thinking')}
@@ -2056,7 +2031,7 @@ export const ModelControls: React.FC = ({
onClick={() => handleSelect(undefined)}
>
{t('chat.modelControls.default')}
- {isDefault && }
+ {isDefault && }
{targetVariants.map((variant) => {
@@ -2075,7 +2050,7 @@ export const ModelControls: React.FC = ({
onClick={() => handleSelect(variant)}
>
{label}
- {selected && }
+ {selected && }
);
})}
@@ -2122,7 +2097,7 @@ export const ModelControls: React.FC = ({
{capitalizeAgentName(agent.name)}
{isSelected && (
-
+
)}
{agent.description && (
@@ -2288,7 +2263,7 @@ export const ModelControls: React.FC = ({
if (hasCapabilities) {
slides.push(
- {indicatorIcons.map(({ id, icon: Icon, label }) => (
+ {indicatorIcons.map(({ id, icon: iconName, label }) => (
= ({
role="img"
title={label}
>
-
+
))}
@@ -2355,7 +2330,7 @@ export const ModelControls: React.FC = ({
aria-label={t('chat.modelControls.reorderFavoriteAria')}
title={t('chat.modelControls.reorderFavoriteTitle')}
>
-
+
) : null}
@@ -2395,7 +2370,7 @@ export const ModelControls: React.FC = ({
) : null}
{isSelected && (
-
+
)}
{
@@ -2415,9 +2390,9 @@ export const ModelControls: React.FC = ({
: t('chat.modelControls.addToFavorites')}
>
{isFavorite ? (
-
+
) : (
-
+
)}
@@ -2669,7 +2644,7 @@ export const ModelControls: React.FC
= ({
>
{!isReady ? (
<>
-
+
= ({
providerId={currentProviderId}
className={cn(controlIconSize, 'flex-shrink-0')}
/>
-
+
>
) : (
-
+
)}
{isReady && (
= ({
{/* Search Input */}
@@ -2923,7 +2898,7 @@ export const ModelControls: React.FC = ({
>
{!isReady ? (
<>
-
+
{readinessLabel}
@@ -2936,7 +2911,7 @@ export const ModelControls: React.FC = ({
className={cn(controlIconSize, 'flex-shrink-0')}
/>
) : (
-
+
)}
= ({
)}
-
{t('chat.modelControls.permissions')}
@@ -3078,7 +3052,7 @@ export const ModelControls: React.FC
= ({
{hasCustomPrompt && (
{t('chat.modelControls.customPrompt')}
-
+
)}
@@ -3106,7 +3080,7 @@ export const ModelControls: React.FC
= ({
'cursor-pointer hover:bg-transparent hover:opacity-70',
)}
>
-
+
= ({
buttonHeight,
)}
>
-
+
= ({
handleVariantSelect(undefined)}>
{t('chat.modelControls.default')}
- {isDefault && }
+ {isDefault && }
{availableVariants.length > 0 && }
@@ -3166,7 +3140,7 @@ export const ModelControls: React.FC = ({
>
{label}
- {selected && }
+ {selected && }
);
@@ -3194,7 +3168,7 @@ export const ModelControls: React.FC = ({
)}>
{!isReady ? (
<>
- = ({
>
) : (
<>
- = ({
-
+
= ({
onSelect={() => handleAgentChange(defaultAgentName)}
>
-
+
{t('chat.modelControls.resetToDefault')}
@@ -3323,7 +3297,7 @@ export const ModelControls: React.FC
= ({
>
{!isReady ? (
<>
- = ({
>
) : (
<>
- {
type="button"
className="flex min-w-0 max-w-full items-center gap-1 text-left text-muted-foreground"
>
-
+
{labelHead}
{t('chat.pendingChanges.changedInWorkspace')}
@@ -113,9 +113,9 @@ export const PendingChangesBar: React.FC = React.memo(() => {
{totalRemoved > 0 ? -{totalRemoved} : null}
{isExpanded ? (
-
+
) : (
-
+
)}
}
diff --git a/packages/ui/src/components/chat/PermissionCard.tsx b/packages/ui/src/components/chat/PermissionCard.tsx
index 6b06bd14..dc91234a 100644
--- a/packages/ui/src/components/chat/PermissionCard.tsx
+++ b/packages/ui/src/components/chat/PermissionCard.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiCheckLine, RiCloseLine, RiFileEditLine, RiGlobalLine, RiPencilAiLine, RiQuestionLine, RiTerminalBoxLine, RiTimeLine, RiToolsLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { PermissionRequest, PermissionResponse } from '@/types/permission';
import { useSessionUIStore } from '@/sync/session-ui-store';
@@ -9,6 +8,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { DiffPreview, WritePreview } from './DiffPreview';
import { useI18n } from '@/lib/i18n';
@@ -52,22 +52,22 @@ const getToolIcon = (toolName: string) => {
const tool = toolName.toLowerCase();
if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') {
- return ;
+ return ;
}
if (tool === 'write' || tool === 'create' || tool === 'file_write') {
- return ;
+ return ;
}
if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal' || tool === 'shell_command') {
- return ;
+ return ;
}
if (tool === 'webfetch' || tool === 'fetch' || tool === 'curl' || tool === 'wget') {
- return ;
+ return ;
}
- return ;
+ return ;
};
const getToolDisplayName = (toolName: string): string => {
@@ -325,7 +325,7 @@ export const PermissionCard: React.FC = ({
-
+
Permission Required
@@ -376,7 +376,7 @@ export const PermissionCard: React.FC
= ({
e.currentTarget.style.backgroundColor = 'rgb(var(--status-success) / 0.1)';
}}
>
-
+
Allow Once
@@ -399,7 +399,7 @@ export const PermissionCard: React.FC = ({
e.currentTarget.style.backgroundColor = 'rgb(var(--muted) / 0.5)';
}}
>
-
+
{(() => {
const always = (permission.always as string[]) || (permission.metadata.always as string[]) || [];
if (always.length === 0) return "Always Allow";
@@ -432,7 +432,7 @@ export const PermissionCard: React.FC = ({
e.currentTarget.style.backgroundColor = 'rgb(var(--muted) / 0.5)';
}}
>
-
+
Always Allow
)}
@@ -455,7 +455,7 @@ export const PermissionCard: React.FC = ({
e.currentTarget.style.backgroundColor = 'rgb(var(--status-error) / 0.1)';
}}
>
-
+
Deny
diff --git a/packages/ui/src/components/chat/PermissionRequest.tsx b/packages/ui/src/components/chat/PermissionRequest.tsx
index e741cd33..c7c3e879 100644
--- a/packages/ui/src/components/chat/PermissionRequest.tsx
+++ b/packages/ui/src/components/chat/PermissionRequest.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { RiCheckLine, RiCloseLine, RiTimeLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { PermissionRequest as PermissionRequestPayload, PermissionResponse } from '@/types/permission';
import * as sessionActions from '@/sync/session-actions';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
interface PermissionRequestProps {
permission: PermissionRequestPayload;
@@ -73,7 +73,7 @@ export const PermissionRequest: React.FC = ({
e.currentTarget.style.backgroundColor = 'transparent';
}}
>
-
+
{t('chat.permissionRequest.actions.once')}
@@ -95,7 +95,7 @@ export const PermissionRequest: React.FC = ({
e.currentTarget.style.backgroundColor = 'transparent';
}}
>
-
+
{t('chat.permissionRequest.actions.always')}
@@ -117,7 +117,7 @@ export const PermissionRequest: React.FC = ({
e.currentTarget.style.backgroundColor = 'transparent';
}}
>
-
+
{t('chat.permissionRequest.actions.reject')}
diff --git a/packages/ui/src/components/chat/QuestionCard.tsx b/packages/ui/src/components/chat/QuestionCard.tsx
index e2d696cf..7015e89c 100644
--- a/packages/ui/src/components/chat/QuestionCard.tsx
+++ b/packages/ui/src/components/chat/QuestionCard.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiArrowRightSLine, RiCheckLine, RiCloseLine, RiEditLine, RiListCheck3, RiQuestionLine } from '@remixicon/react';
import { Checkbox } from '@/components/ui/checkbox';
import { Radio } from '@/components/ui/radio';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { isIMECompositionEvent } from '@/lib/ime';
@@ -217,7 +217,7 @@ export const QuestionCard: React.FC = ({ question }) => {
{/* Header */}
-
+
{t('chat.questionCard.inputNeeded')}
{isFromSubagent ? (
@@ -257,7 +257,7 @@ export const QuestionCard: React.FC = ({ question }) => {
: 'text-foreground/85 hover:text-foreground hover:bg-interactive-hover/20'
)}
>
- {isSummary ? : null}
+ {isSummary ? : null}
{tab.label}
);
@@ -366,7 +366,7 @@ export const QuestionCard: React.FC = ({ question }) => {
)}
>
-
@@ -427,7 +427,7 @@ export const QuestionCard: React.FC = ({ question }) => {
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
- {requiredSatisfied ? : }
+ {requiredSatisfied ? : }
{requiredSatisfied ? t('chat.questionCard.submit') : t('chat.questionCard.next')}
@@ -441,7 +441,7 @@ export const QuestionCard: React.FC = ({ question }) => {
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
-
+
{t('chat.questionCard.dismiss')}
diff --git a/packages/ui/src/components/chat/QueuedMessageChips.tsx b/packages/ui/src/components/chat/QueuedMessageChips.tsx
index 60029896..bf85b24b 100644
--- a/packages/ui/src/components/chat/QueuedMessageChips.tsx
+++ b/packages/ui/src/components/chat/QueuedMessageChips.tsx
@@ -1,9 +1,9 @@
import React, { memo } from 'react';
-import { RiCloseLine, RiMessage2Line } from '@remixicon/react';
import { useMessageQueueStore, type QueuedMessage } from '@/stores/messageQueueStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useInputStore } from '@/sync/input-store';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
interface QueuedMessageChipProps {
message: QueuedMessage;
@@ -35,9 +35,8 @@ const QueuedMessageChip = memo(({ message, sessionId, onEdit }: QueuedMessageChi
onClick={() => onEdit(message)}
className="flex min-w-0 flex-1 items-center gap-1.5 text-left hover:opacity-80 transition-opacity"
>
-
+
Queued
{attachmentCount > 0 && (
@@ -54,7 +53,7 @@ const QueuedMessageChip = memo(({ message, sessionId, onEdit }: QueuedMessageChi
className="flex items-center justify-center h-6 w-6 flex-shrink-0 hover:bg-[var(--interactive-hover)] rounded-full transition-colors"
aria-label={t('chat.queuedMessage.removeAria')}
>
-
+
);
diff --git a/packages/ui/src/components/chat/StatusRow.tsx b/packages/ui/src/components/chat/StatusRow.tsx
index 6b2d1735..4dba3e65 100644
--- a/packages/ui/src/components/chat/StatusRow.tsx
+++ b/packages/ui/src/components/chat/StatusRow.tsx
@@ -1,14 +1,5 @@
import React from "react";
import { useSessionUIStore } from '@/sync/session-ui-store';
-import {
- RiArrowDownSLine,
- RiArrowUpDoubleLine,
- RiArrowUpSLine,
- RiCheckboxCircleLine,
- RiCloseCircleLine,
- RiRecordCircleLine,
- RiTimeLine,
-} from "@remixicon/react";
import { cn } from "@/lib/utils";
import { useDirectorySync } from "@/sync/sync-context";
import type { Todo } from "@opencode-ai/sdk/v2/client";
@@ -22,6 +13,7 @@ import { useTodosPersistStore } from "@/stores/useTodosPersistStore";
import { WorkingPlaceholder } from "./message/parts/WorkingPlaceholder";
import { isVSCodeRuntime } from "@/lib/desktop";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from "@/lib/i18n";
const STATUS_ROW_CONTAINER_STYLE = { containerType: "inline-size" as const, containerName: "status-row" };
@@ -48,9 +40,9 @@ const priorityClassName: Record = {
};
const priorityIcon: Record = {
- high: ,
- medium: ,
- low: ,
+ high: ,
+ medium: ,
+ low: ,
};
const statusLabelKey: Record = {
@@ -78,11 +70,11 @@ const TodoItemRow: React.FC = ({ todo }) => {
const statusIcon =
todo.status === "in_progress" ? (
-
+
) : todo.status === "completed" ? (
-
+
) : (
-
+
);
return (
@@ -248,7 +240,7 @@ export const StatusRow: React.FC = ({
className="flex items-center justify-center h-[1.2rem] w-[1.2rem] text-[var(--status-error)] transition-opacity hover:opacity-80 focus-visible:outline-none flex-shrink-0"
aria-label={t('chat.statusRow.actions.stopGeneratingAria')}
>
-
+
) : null;
@@ -271,19 +263,19 @@ export const StatusRow: React.FC = ({
)}
-
+
{statusSummary.active}
·
-
+
{statusSummary.left}
{isExpanded ? (
-
+
) : (
-
+
)}
) : null;
@@ -301,7 +293,7 @@ export const StatusRow: React.FC = ({
{showAssistantStatus && showAbortStatus ? (
-
+
{t('chat.statusRow.aborted')}
diff --git a/packages/ui/src/components/chat/TimelineDialog.tsx b/packages/ui/src/components/chat/TimelineDialog.tsx
index 6790b631..ee18631f 100644
--- a/packages/ui/src/components/chat/TimelineDialog.tsx
+++ b/packages/ui/src/components/chat/TimelineDialog.tsx
@@ -9,8 +9,8 @@ import {
import { Input } from '@/components/ui/input';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessionMessageRecords } from '@/sync/sync-context';
-import { RiLoader4Line, RiSearchLine, RiTimeLine, RiGitBranchLine, RiArrowGoBackLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import type { Part } from '@opencode-ai/sdk/v2';
import { useI18n } from '@/lib/i18n';
import { useDeviceInfo } from '@/lib/device';
@@ -151,7 +151,7 @@ export const TimelineDialog: React.FC = ({
-
+
{t('chat.timeline.title')}
@@ -160,7 +160,7 @@ export const TimelineDialog: React.FC = ({
-
+
= ({
onOpenChange(false);
}}
>
-
+
{t('chat.timeline.actions.revertFromHere')}
@@ -253,9 +253,9 @@ export const TimelineDialog: React.FC = ({
disabled={forkingMessageId === message.info.id}
>
{forkingMessageId === message.info.id ? (
-
+
) : (
-
+
)}
@@ -299,11 +299,11 @@ export const TimelineDialog: React.FC = ({
{t('chat.timeline.help.clickMessage')}
-
+
{t('chat.timeline.help.undoToPoint')}
-
+
{t('chat.timeline.help.createSessionFromHere')}
diff --git a/packages/ui/src/components/chat/TurnChangedFilesDropdown.tsx b/packages/ui/src/components/chat/TurnChangedFilesDropdown.tsx
index e7c81c5f..1b2cdf88 100644
--- a/packages/ui/src/components/chat/TurnChangedFilesDropdown.tsx
+++ b/packages/ui/src/components/chat/TurnChangedFilesDropdown.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiFileEditLine, RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react';
import type { ToolPart } from '@opencode-ai/sdk/v2';
import { Popover } from '@base-ui/react/popover';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -17,6 +16,7 @@ import {
import { ChangedFilesList } from './ChangedFilesList';
import { changedFilesPopoverClassName, changedFilesPopoverStyle } from './changedFilesPopover';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import type { TurnActivityRecord } from './lib/turns/types';
interface TurnChangedFilesDropdownProps {
@@ -96,12 +96,12 @@ export const TurnChangedFilesDropdown: React.FC
=
onPointerDownCapture={syncPortalContainer}
onFocusCapture={syncPortalContainer}
>
-
+
{label}
{isExpanded ? (
-
+
) : (
-
+
)}
}
diff --git a/packages/ui/src/components/chat/components/ScrollToBottomButton.tsx b/packages/ui/src/components/chat/components/ScrollToBottomButton.tsx
index 814c4ce1..15726e76 100644
--- a/packages/ui/src/components/chat/components/ScrollToBottomButton.tsx
+++ b/packages/ui/src/components/chat/components/ScrollToBottomButton.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiArrowDownLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -26,7 +26,7 @@ const ScrollToBottomButton: React.FC = ({ visible, on
className="size-8 rounded-full [corner-shape:round] p-0 shadow-none bg-background/95 hover:bg-interactive-hover"
aria-label={t('chat.scrollToBottom.aria')}
>
-
+
);
diff --git a/packages/ui/src/components/chat/message/DiffViewToggle.tsx b/packages/ui/src/components/chat/message/DiffViewToggle.tsx
index dc883099..0e5409d5 100644
--- a/packages/ui/src/components/chat/message/DiffViewToggle.tsx
+++ b/packages/ui/src/components/chat/message/DiffViewToggle.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiAlignJustify, RiLayoutColumnLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
export type DiffViewMode = 'side-by-side' | 'unified';
@@ -30,9 +30,9 @@ export const DiffViewToggle: React.FC = ({ mode, onModeChan
title={mode === 'side-by-side' ? 'Switch to unified view' : 'Switch to side-by-side view'}
>
{mode === 'side-by-side' ? (
-
+
) : (
-
+
)}
);
diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx
index d98ce164..f014785f 100644
--- a/packages/ui/src/components/chat/message/MessageBody.tsx
+++ b/packages/ui/src/components/chat/message/MessageBody.tsx
@@ -16,7 +16,6 @@ import { FadeInOnReveal } from './FadeInOnReveal';
import { Button } from '@/components/ui/button';
import { SaveProjectPlanDialog } from '@/components/session/SaveProjectPlanDialog';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { RiCheckLine, RiFileCopyLine, RiChatNewLine, RiArrowGoBackLine, RiGitBranchLine, RiHourglassLine, RiTimeLine, RiVolumeUpLine, RiStopLine, RiImageDownloadLine, RiLoader4Line, RiErrorWarningLine, RiBookletLine, RiGlobalLine, RiInformationLine } from '@remixicon/react';
import { ArrowsMerge } from '@/components/icons/ArrowsMerge';
import type { ContentChangeReason } from '@/hooks/useChatAutoFollow';
@@ -34,6 +33,7 @@ import { useChatSurfaceMode } from '@/components/chat/useChatSurfaceMode';
import { isVSCodeRuntime } from '@/lib/desktop';
import { toPng } from 'html-to-image';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { formatTimestampForDisplay } from './timeFormat';
import { ToolRevealOnMount } from './parts/ToolRevealOnMount';
import { StaticToolRow } from './parts/ProgressiveGroup';
@@ -89,7 +89,6 @@ const normalizeSubtaskModel = (model: SubtaskPartLike['model']): string | null =
return `${providerID}/${modelID}`;
};
-
const UserSubtaskPart: React.FC<{ part: SubtaskPartLike }> = ({ part }) => {
const [expanded, setExpanded] = React.useState(false);
const setCurrentSession = useSessionUIStore((state) => state.setCurrentSession);
@@ -244,7 +243,7 @@ const UserShellActionPart: React.FC<{ part: ShellActionPartLike }> = ({ part })
aria-label={copiedOutput ? t('chat.messageBody.shellCommand.copied') : t('chat.messageBody.shellCommand.copyOutput')}
title={copiedOutput ? t('chat.messageBody.shellCommand.copied') : t('chat.messageBody.shellCommand.copyOutput')}
>
- {copiedOutput ? : }
+ {copiedOutput ? : }
{expanded ? (
@@ -268,8 +267,6 @@ const formatTurnDuration = (durationMs: number): string => {
return `${minutes}m ${seconds}s`;
};
-
-
interface MessageBodyProps {
sessionId?: string;
messageId: string;
@@ -463,7 +460,7 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
onRevert();
}}
>
-
+
{t('chat.messageBody.actions.revert')}
@@ -484,7 +481,7 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
effectiveOnFork();
}}
>
-
+
{t('chat.messageBody.actions.fork')}
@@ -510,9 +507,9 @@ const UserMessageBody = React.memo(({ messageId, parts, isMobile, alwaysShowActi
}}
>
{isMessageCopied ? (
-
+
) : (
-
+
)}
@@ -770,9 +767,9 @@ const AssistantMessageActionButtons = React.memo(({
}}
>
{isMessageCopied ? (
-
+
) : (
-
+
)}
@@ -796,9 +793,9 @@ const AssistantMessageActionButtons = React.memo(({
}}
>
{isSharing ? (
-
+
) : (
-
+
)}
@@ -820,9 +817,9 @@ const AssistantMessageActionButtons = React.memo(({
onClick={handleTTSClick}
>
{isTTSPlaying ? (
-
+
) : (
-
+
)}
@@ -1033,7 +1030,6 @@ const AssistantMessageBody = React.memo(({
return resolved ? { id: resolved.id, path: resolved.path } : null;
}, [availableWorktreesByProject, currentSession?.directory, effectiveDirectory, projects]);
-
const hasTools = toolParts.length > 0;
const hasPendingTools = React.useMemo(() => {
@@ -1082,7 +1078,6 @@ const AssistantMessageBody = React.memo(({
return toolParts.every((toolPart) => isToolFinalized(toolPart));
}, [toolParts, hasPendingTools, isToolFinalized]);
-
const reasoningParts = React.useMemo(() => {
return visibleParts.filter((part) => part.type === 'reasoning');
}, [visibleParts]);
@@ -1105,7 +1100,6 @@ const AssistantMessageBody = React.memo(({
hasTools &&
(hasPendingTools || hasOpenStep || !allToolsFinalized);
-
const shouldHoldTools = awaitingMessageCompletion
|| (hasTools && (hasPendingTools || hasOpenStep || !allToolsFinalized));
const shouldHoldReasoning = awaitingMessageCompletion || shouldHoldForReasoning;
@@ -1398,7 +1392,7 @@ const AssistantMessageBody = React.memo(({
const shouldDeferSortedInlineText = isSortedRenderMode && !hasStopFinish;
const showErrorMessage = Boolean(errorMessage);
- const ErrorIcon = errorVariant === 'info' ? RiInformationLine : RiErrorWarningLine;
+ const errorIconName = errorVariant === 'info' ? 'information' : 'error-warning';
const shouldShowMessageActions = hasCopyableText;
const shouldShowTurnFooter = isLastAssistantInTurn && hasTextContent && (hasStopFinish || Boolean(errorMessage));
const shouldRenderActionsInActivity = isSortedRenderMode;
@@ -1466,7 +1460,6 @@ const AssistantMessageBody = React.memo(({
const shouldRenderStandaloneActionsAfterContent = shouldShowStandaloneMessageActions && lastRenderableTextPartIndex < 0;
-
const renderedParts = React.useMemo(() => {
const rendered: React.ReactNode[] = [];
@@ -1729,7 +1722,7 @@ const AssistantMessageBody = React.memo(({
openContextPreview(directory, messagePreviewUrl);
}}
>
-
+
{t('chat.messageBody.actions.openPreview')}
@@ -1750,7 +1743,7 @@ const AssistantMessageBody = React.memo(({
onPointerDown={(event) => event.stopPropagation()}
onClick={handleSaveAsPlanClick}
>
-
+
{t('chat.messageBody.actions.saveAsPlan')}
@@ -1766,7 +1759,7 @@ const AssistantMessageBody = React.memo(({
onPointerDown={(event) => event.stopPropagation()}
onClick={handleForkClick}
>
-
+
{t('chat.messageBody.actions.startNewSession')}
@@ -1824,7 +1817,7 @@ const AssistantMessageBody = React.memo(({
: 'bg-[var(--status-error-background)] border-[var(--status-error-border)]',
)}>
-
@@ -1862,7 +1855,7 @@ const AssistantMessageBody = React.memo(({
-
+
{turnDurationText}
@@ -1876,7 +1869,7 @@ const AssistantMessageBody = React.memo(({
className={footerTimestampClassName}
aria-label={`Message time: ${footerTimestamp}`}
>
-
+
{footerTimestamp}
diff --git a/packages/ui/src/components/chat/message/MessageHeader.tsx b/packages/ui/src/components/chat/message/MessageHeader.tsx
index 80ecd822..a70bf88d 100644
--- a/packages/ui/src/components/chat/message/MessageHeader.tsx
+++ b/packages/ui/src/components/chat/message/MessageHeader.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiAiAgentLine, RiBrainAi3Line, RiUser3Line } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { getAgentColor } from '@/lib/agentColors';
import { useProviderLogo } from '@/hooks/useProviderLogo';
+import { Icon } from "@/components/icon/Icon";
interface MessageHeaderProps {
isUser: boolean;
@@ -23,7 +23,7 @@ const MessageHeader: React.FC = ({ isUser, providerID, agent
{isUser ? (
-
+
) : (
@@ -38,10 +38,9 @@ const MessageHeader: React.FC = ({ isUser, providerID, agent
onError={handleLogoError}
/>
) : (
-
+
)}
)}
@@ -64,7 +63,7 @@ const MessageHeader: React.FC
= ({ isUser, providerID, agent
getAgentColor(agentName).class
)}
>
-
+
{agentName}
)}
@@ -85,7 +84,7 @@ const MessageHeader: React.FC = ({ isUser, providerID, agent
: undefined
}
>
-
+
{variant.length > 0 ? variant[0].toLowerCase() + variant.slice(1) : variant}
)}
diff --git a/packages/ui/src/components/chat/message/TextSelectionMenu.tsx b/packages/ui/src/components/chat/message/TextSelectionMenu.tsx
index 4dcce714..07c061b3 100644
--- a/packages/ui/src/components/chat/message/TextSelectionMenu.tsx
+++ b/packages/ui/src/components/chat/message/TextSelectionMenu.tsx
@@ -5,10 +5,10 @@ import { useSessions } from '@/sync/sync-context';
import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
-import { RiBookletLine, RiChatNewLine, RiAddLine, RiFileCopyLine, RiLoader4Line } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { copyTextToClipboard } from '@/lib/clipboard';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { getProjectNotesAndTodos, saveProjectNotesAndTodos } from '@/lib/openchamberConfig';
import { resolveProjectForSessionDirectory } from '@/lib/projectResolution';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
@@ -32,7 +32,6 @@ interface SelectionPayload {
rect: DOMRect;
}
-
const appendDistilledInsightToNotes = (existingNotes: string, insight: string): string => {
const trimmedInsight = insight.trim().replace(/^[-*+]\s+/, '');
if (!trimmedInsight) {
@@ -592,7 +591,7 @@ export const TextSelectionMenu: React.FC
= ({ containerR
title={t('chat.textSelection.title.addToCurrentChat')}
type="button"
>
-
+
{t('chat.textSelection.actions.addToChat')}
@@ -608,7 +607,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.title.newSessionWithSelection')}
type="button"
>
-
+
{t('chat.textSelection.actions.newSession')}
@@ -624,7 +623,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.actions.copy')}
type="button"
>
-
+
{t('chat.textSelection.actions.copy')}
@@ -642,7 +641,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.title.saveInsightToNotes')}
type="button"
>
- {isAddingToNotes ? : }
+ {isAddingToNotes ? : }
{t('chat.textSelection.actions.addToNotes')}
) : null}
@@ -685,7 +684,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.title.addToCurrentChat')}
type="button"
>
-
+
{t('chat.textSelection.actions.addToChat')}
@@ -703,7 +702,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.title.newSessionWithSelection')}
type="button"
>
-
+
{t('chat.textSelection.actions.newSession')}
@@ -724,7 +723,7 @@ export const TextSelectionMenu: React.FC = ({ containerR
title={t('chat.textSelection.title.saveInsightToNotes')}
type="button"
>
- {isAddingToNotes ? : }
+ {isAddingToNotes ? : }
{t('chat.textSelection.actions.addToNotes')}
>
diff --git a/packages/ui/src/components/chat/message/ToolOutputDialog.tsx b/packages/ui/src/components/chat/message/ToolOutputDialog.tsx
index 5041c9aa..3a2cce18 100644
--- a/packages/ui/src/components/chat/message/ToolOutputDialog.tsx
+++ b/packages/ui/src/components/chat/message/ToolOutputDialog.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { Dialog, DialogContent } from '@/components/ui/dialog';
-import { RiArrowLeftSLine, RiArrowRightSLine, RiBrainAi3Line, RiCloseLine, RiFileImageLine, RiFileList2Line, RiFilePdfLine, RiFileSearchLine, RiFolder6Line, RiGitBranchLine, RiGlobalLine, RiListCheck3, RiLoader4Line, RiPencilAiLine, RiSearchLine, RiTaskLine, RiTerminalBoxLine, RiToolsLine } from '@remixicon/react';
import { File as PierreFile, PatchDiff } from '@pierre/diffs/react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { createPortal } from 'react-dom';
@@ -26,6 +25,7 @@ import type { ToolPopupContent, DiffViewMode } from './types';
import { DiffViewToggle } from './DiffViewToggle';
import { VirtualizedCodeBlock, type CodeLine } from './parts/VirtualizedCodeBlock';
import { JsonTreeView } from '@/components/ui/JsonTreeView';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface ToolOutputDialogProps {
@@ -40,54 +40,54 @@ const getToolIcon = (toolName: string) => {
const tool = toolName.toLowerCase();
if (tool === 'reasoning') {
- return ;
+ return ;
}
if (tool === 'image-preview') {
- return ;
+ return ;
}
if (tool === 'mermaid-preview') {
- return ;
+ return ;
}
if (tool === 'edit' || tool === 'multiedit' || tool === 'apply_patch' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') {
- return ;
+ return ;
}
if (tool === 'write' || tool === 'create' || tool === 'file_write') {
- return ;
+ return ;
}
if (tool === 'read' || tool === 'view' || tool === 'file_read' || tool === 'cat') {
- return ;
+ return ;
}
if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal') {
- return ;
+ return ;
}
if (tool === 'list' || tool === 'ls' || tool === 'dir' || tool === 'list_files') {
- return ;
+ return ;
}
if (tool === 'search' || tool === 'grep' || tool === 'find' || tool === 'ripgrep') {
- return ;
+ return ;
}
if (tool === 'glob') {
- return ;
+ return ;
}
if (tool === 'fetch' || tool === 'curl' || tool === 'wget' || tool === 'webfetch') {
- return ;
+ return ;
}
if (tool === 'web-search' || tool === 'websearch' || tool === 'search_web' || tool === 'google' || tool === 'bing' || tool === 'duckduckgo') {
- return ;
+ return ;
}
if (tool === 'todowrite' || tool === 'todoread') {
- return ;
+ return ;
}
if (tool === 'plan_enter') {
- return ;
+ return ;
}
if (tool === 'plan_exit') {
- return ;
+ return ;
}
if (tool.startsWith('git')) {
- return ;
+ return ;
}
- return ;
+ return ;
};
const PREVIEW_ANIMATION_MS = 150;
@@ -442,7 +442,7 @@ const ImagePreviewDialog: React.FC<{
className="absolute left-3 top-1/2 -translate-y-1/2 z-10 h-10 w-10 flex items-center justify-center rounded-full bg-black/40 text-foreground/90 hover:bg-black/55 focus:outline-none focus:ring-2 focus:ring-primary/60"
aria-label={t('chat.toolOutputDialog.image.previousAria')}
>
-
+
-
+
>
)}
@@ -480,7 +480,7 @@ const ImagePreviewDialog: React.FC<{
onClick={() => onOpenChange(false)}
aria-label={t('chat.toolOutputDialog.image.closeAria')}
>
-
+
@@ -927,7 +927,7 @@ const MermaidPreviewDialog: React.FC<{
onClick={() => onOpenChange(false)}
aria-label={t('chat.toolOutputDialog.mermaid.closeAria')}
>
-
+
{status === 'loading' && (
-
+
{t('chat.toolOutputDialog.mermaid.loading')}
)}
@@ -1016,7 +1016,7 @@ const ToolOutputDialog: React.FC
= ({ popup, onOpenChange
{popup.metadata?.tool ? getToolIcon(popup.metadata.tool as string) : (
-
+
)}
{popup.title}
{popup.isDiff && (
diff --git a/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx b/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx
index 9e838999..7ec819df 100644
--- a/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx
+++ b/packages/ui/src/components/chat/message/parts/ProgressiveGroup.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiStackLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { TurnActivityRecord as TurnActivityPart } from '../../lib/turns/types';
import type { ToolPart as ToolPartType } from '@opencode-ai/sdk/v2';
@@ -11,6 +10,7 @@ import { MinDurationShineText } from './MinDurationShineText';
import { ToolRevealOnMount } from './ToolRevealOnMount';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
import { Text } from '@/components/ui/text';
+import { Icon } from "@/components/icon/Icon";
import { FadeInOnReveal } from '../FadeInOnReveal';
import { getToolIcon } from './toolPresentation';
import { getToolMetadata } from '@/lib/toolHelpers';
@@ -927,7 +927,7 @@ const ProgressiveGroup: React.FC
= ({
onClick={onToggle}
>
-
+
;
-
const variantConfig: Record<
ReasoningVariant,
- { label: string; Icon: IconComponent }
+ { label: string; Icon: IconName }
> = {
- thinking: { label: 'Thinking', Icon: RiBrainAi3Line },
- justification: { label: 'Justification', Icon: RiChatAi3Line },
+ thinking: { label: 'Thinking', Icon: 'brain-ai-3' },
+ justification: { label: 'Justification', Icon: 'chat-ai-3' },
};
const cleanReasoningText = (text: string): string => {
@@ -99,7 +96,7 @@ export const ReasoningTimelineBlock: React.FC = ({
const [isExpanded, setIsExpanded] = React.useState(false);
const summary = React.useMemo(() => getReasoningSummary(text), [text]);
- const { label, Icon } = variantConfig[variant];
+ const { label, Icon: iconName } = variantConfig[variant];
const timeStart = typeof time?.start === 'number' && Number.isFinite(time.start) ? time.start : undefined;
const timeEnd = typeof time?.end === 'number' && Number.isFinite(time.end) ? time.end : undefined;
@@ -131,7 +128,7 @@ export const ReasoningTimelineBlock: React.FC = ({
!isExpanded && (alwaysShowActions ? 'opacity-0' : 'group-hover/tool:opacity-0')
)}
>
-
+
= ({
!isExpanded && (alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/tool:opacity-100')
)}
>
- {isExpanded ? : }
+ {isExpanded ? : }
{label}
diff --git a/packages/ui/src/components/chat/message/parts/ToolPart.tsx b/packages/ui/src/components/chat/message/parts/ToolPart.tsx
index d3b4ed2b..b060ff2b 100644
--- a/packages/ui/src/components/chat/message/parts/ToolPart.tsx
+++ b/packages/ui/src/components/chat/message/parts/ToolPart.tsx
@@ -1,7 +1,6 @@
import React from 'react';
import { RuntimeAPIContext } from '@/contexts/runtimeAPIContext';
-import { RiArrowDownSLine, RiArrowRightSLine, RiExternalLinkLine } from '@remixicon/react';
import { PatchDiff } from '@pierre/diffs/react';
import { cn } from '@/lib/utils';
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
@@ -34,6 +33,7 @@ import {
tryParseJsonOutput,
} from '../toolRenderers';
import { JsonTreeViewer } from '@/components/ui/JsonTreeViewer';
+import { Icon } from "@/components/icon/Icon";
import { DiffViewToggle, type DiffViewMode } from '../DiffViewToggle';
import { MinDurationShineText } from './MinDurationShineText';
import { ToolRevealOnMount } from './ToolRevealOnMount';
@@ -722,7 +722,7 @@ const ToolScrollableSection: React.FC = ({
disableHorizontal ? 'overflow-y-auto overflow-x-hidden' : 'overflow-auto',
className,
)}
- size={24}
+
>
{children}
@@ -1190,7 +1190,7 @@ const TaskToolSummary: React.FC<{
onPointerDown={(event) => event.stopPropagation()}
onClick={handleOpenSession}
>
-
+
{t('chat.toolPart.openSubtask', { type: agentType.charAt(0).toUpperCase() + agentType.slice(1) })}
)}
@@ -1208,9 +1208,9 @@ const TaskToolSummary: React.FC<{
}}
>
{isOutputExpanded ? (
-
+
) : (
-
+
)}
{t('chat.toolPart.output')}
@@ -1884,8 +1884,6 @@ const ToolPart: React.FC = ({
sessionEvents.requestGitRefresh({ directory: currentDirectory });
}, [currentDirectory, isError, isFinalized, normalizedPartTool, part.id, status]);
-
-
const shouldNotifyStructuralChange = isFinalized || isTaskTool;
const onContentChangeRef = React.useRef(onContentChange);
@@ -2419,7 +2417,6 @@ const ToolPart: React.FC = ({
taskSessionId,
]);
-
const taskSummaryLenRef = React.useRef(taskSummaryEntries.length);
React.useEffect(() => {
if (!isTaskTool) {
@@ -2565,7 +2562,7 @@ const ToolPart: React.FC = ({
!isExpanded && (alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/tool:opacity-100')
)}
>
- {isExpanded ? : }
+ {isExpanded ? : }
{isMultiFileApplyPatch ? (
diff --git a/packages/ui/src/components/chat/message/parts/UserTextPart.tsx b/packages/ui/src/components/chat/message/parts/UserTextPart.tsx
index 41f48956..c054b41e 100644
--- a/packages/ui/src/components/chat/message/parts/UserTextPart.tsx
+++ b/packages/ui/src/components/chat/message/parts/UserTextPart.tsx
@@ -4,7 +4,7 @@ import type { Part } from '@opencode-ai/sdk/v2';
import type { AgentMentionInfo } from '../types';
import { SimpleMarkdownRenderer } from '../../MarkdownRenderer';
import { useUIStore } from '@/stores/useUIStore';
-import { RiArrowUpSLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
type PartWithText = Part & { text?: string; content?: string; value?: string };
@@ -149,7 +149,7 @@ const UserTextPart: React.FC = ({ part, messageId, agentMenti
className="absolute top-0 right-0 z-10 flex items-center justify-center rounded-sm bg-[var(--surface-elevated)] p-0.5 text-[var(--surface-mutedForeground)] hover:text-[var(--surface-foreground)] hover:bg-[var(--interactive-hover)] transition-colors"
aria-label="Collapse"
>
-
+
)}
{
const iconClass = 'h-3.5 w-3.5 flex-shrink-0';
const tool = toolName.toLowerCase();
if (tool === 'edit' || tool === 'multiedit' || tool === 'apply_patch' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') {
- return
;
+ return
;
}
if (tool === 'write' || tool === 'create' || tool === 'file_write') {
- return
;
+ return
;
}
if (tool === 'read' || tool === 'view' || tool === 'file_read' || tool === 'cat') {
- return
;
+ return
;
}
if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal') {
- return
;
+ return
;
}
if (tool === 'list' || tool === 'ls' || tool === 'dir' || tool === 'list_files') {
- return
;
+ return
;
}
if (tool === 'search' || tool === 'grep' || tool === 'find' || tool === 'ripgrep') {
- return
;
+ return
;
}
if (tool === 'glob') {
- return
;
+ return
;
}
if (tool === 'fetch' || tool === 'curl' || tool === 'wget' || tool === 'webfetch') {
- return
;
+ return
;
}
if (
tool === 'web-search' ||
@@ -57,31 +39,31 @@ export const getToolIcon = (toolName: string) => {
tool === 'duckduckgo' ||
tool === 'perplexity'
) {
- return
;
+ return
;
}
if (tool === 'todowrite' || tool === 'todoread') {
- return
;
+ return
;
}
if (tool === 'structuredoutput' || tool === 'structured_output') {
- return
;
+ return
;
}
if (tool === 'skill') {
- return
;
+ return
;
}
if (tool === 'task') {
- return
;
+ return
;
}
if (tool === 'question') {
- return
;
+ return
;
}
if (tool === 'plan_enter') {
- return
;
+ return
;
}
if (tool === 'plan_exit') {
- return
;
+ return
;
}
if (tool.startsWith('git')) {
- return
;
+ return
;
}
- return
;
+ return
;
};
diff --git a/packages/ui/src/components/chat/message/toolRenderers.tsx b/packages/ui/src/components/chat/message/toolRenderers.tsx
index 8344cd88..d63ab4bc 100644
--- a/packages/ui/src/components/chat/message/toolRenderers.tsx
+++ b/packages/ui/src/components/chat/message/toolRenderers.tsx
@@ -1,9 +1,9 @@
-import { RiCheckLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { typography } from '@/lib/typography';
import { formatToolInput, detectToolOutputLanguage } from '@/lib/toolHelpers';
import { SimpleMarkdownRenderer } from '../MarkdownRenderer';
+import { Icon } from "@/components/icon/Icon";
const cleanOutput = (output: string) => {
let cleaned = output.replace(/^
\s*\n?/, '').replace(/\n?<\/file>\s*$/, '');
@@ -473,13 +473,13 @@ export const renderTodoOutput = (
{todosByStatus.completed.length > 0 && (
-
+
{labels.completed}
{todosByStatus.completed.map((todo, idx) => (
-
+
{todo.content}
))}
diff --git a/packages/ui/src/components/comments/InlineCommentCard.tsx b/packages/ui/src/components/comments/InlineCommentCard.tsx
index 72b39351..6eae933e 100644
--- a/packages/ui/src/components/comments/InlineCommentCard.tsx
+++ b/packages/ui/src/components/comments/InlineCommentCard.tsx
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
-import { RiMoreLine, RiDeleteBinLine, RiEditLine, RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react';
import type { InlineCommentDraft } from '@/stores/useInlineCommentDraftStore';
import { useOptionalThemeSystem } from '@/contexts/useThemeSystem';
import {
@@ -11,6 +10,7 @@ import {
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface InlineCommentCardProps {
@@ -76,12 +76,12 @@ export function InlineCommentCard({
>
{isOpen ? (
<>
-
+
{t('inlineComment.actions.showLess')}
>
) : (
<>
-
+
{t('inlineComment.actions.showMore')}
>
)}
@@ -102,16 +102,16 @@ export function InlineCommentCard({
size="icon"
className="h-6 w-6 -mr-1 text-muted-foreground hover:text-foreground"
>
-
+
-
+
{t('inlineComment.actions.editComment')}
-
+
{t('inlineComment.actions.deleteComment')}
diff --git a/packages/ui/src/components/desktop/DesktopHostSwitcher.tsx b/packages/ui/src/components/desktop/DesktopHostSwitcher.tsx
index 6fcc8b71..8fdd616e 100644
--- a/packages/ui/src/components/desktop/DesktopHostSwitcher.tsx
+++ b/packages/ui/src/components/desktop/DesktopHostSwitcher.tsx
@@ -15,26 +15,9 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import {
- RiAddLine,
- RiCheckLine,
- RiCloudOffLine,
- RiEarthLine,
- RiLoader4Line,
- RiMore2Line,
- RiPencilLine,
- RiPlug2Line,
- RiRefreshLine,
- RiServerLine,
- RiSettings3Line,
- RiShieldKeyholeLine,
- RiStarFill,
- RiStarLine,
- RiDeleteBinLine,
- RiWindowLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { isTauriShell, isDesktopShell } from '@/lib/desktop';
import { useUIStore } from '@/stores/useUIStore';
import { useI18n } from '@/lib/i18n';
@@ -117,11 +100,11 @@ const statusLabelKey = (status: HostProbeResult['status'] | null):
};
const statusIcon = (status: HostProbeResult['status'] | null) => {
- if (status === 'ok') return
;
- if (status === 'auth') return
;
- if (status === 'wrong-service') return
;
- if (status === 'unreachable') return
;
- return
;
+ if (status === 'ok') return
;
+ if (status === 'auth') return
;
+ if (status === 'wrong-service') return
;
+ if (status === 'unreachable') return
;
+ return
;
};
const sleep = (ms: number) => new Promise((resolve) => window.setTimeout(resolve, ms));
@@ -732,14 +715,14 @@ export function DesktopHostSwitcherDialog({
disabled={!tauriAvailable || isLoading || isProbing}
aria-label={t('desktopHostSwitcher.actions.refreshInstancesAria')}
>
-
+
) : (
-
+
{t('desktopHostSwitcher.title')}
@@ -764,7 +747,7 @@ export function DesktopHostSwitcherDialog({
onClick={() => void probeAll(allHosts)}
disabled={!tauriAvailable || isLoading || isProbing}
>
-
+
{t('desktopHostSwitcher.actions.refresh')}
@@ -775,7 +758,7 @@ export function DesktopHostSwitcherDialog({
{t('desktopHostSwitcher.ssh.needInstancesHint')}
-
+
{t('desktopHostSwitcher.actions.remoteSsh')}
@@ -868,7 +851,7 @@ export function DesktopHostSwitcherDialog({
disabled={isSaving}
onClick={(e) => e.stopPropagation()}
>
-
+
@@ -879,7 +862,7 @@ export function DesktopHostSwitcherDialog({
}}
disabled={isSaving}
>
-
+
{t('desktopHostSwitcher.actions.edit')}
-
+
{t('desktopHostSwitcher.actions.delete')}
@@ -917,7 +900,7 @@ export function DesktopHostSwitcherDialog({
void connectSshHostInPlace(host);
}}
>
- {switchingHostId === host.id ? : }
+ {switchingHostId === host.id ? : }
{t('desktopHostSwitcher.actions.connect')}
) : (
@@ -942,7 +925,7 @@ export function DesktopHostSwitcherDialog({
aria-label={isDefault ? t('desktopHostSwitcher.actions.defaultInstanceAria') : t('desktopHostSwitcher.actions.setAsDefaultAria')}
disabled={isSaving || (!isDefault && (statusKind === 'unreachable' || statusKind === 'wrong-service'))}
>
- {isDefault ? : }
+ {isDefault ? : }
@@ -967,7 +950,7 @@ export function DesktopHostSwitcherDialog({
disabled={statusKind === 'unreachable' || statusKind === 'wrong-service'}
aria-label={t('desktopHostSwitcher.actions.openInNewWindowAria')}
>
-
+
@@ -993,7 +976,7 @@ export function DesktopHostSwitcherDialog({
{t('desktopHostSwitcher.actions.cancel')}
void commitEdit()} disabled={isSaving}>
- {isSaving ? : null}
+ {isSaving ? : null}
{t('desktopHostSwitcher.actions.save')}
@@ -1025,7 +1008,7 @@ export function DesktopHostSwitcherDialog({
onClick={() => setIsAddFormOpen(true)}
disabled={!tauriAvailable || isSaving}
>
-
+
{t('desktopHostSwitcher.actions.addInstance')}
@@ -1056,7 +1039,7 @@ export function DesktopHostSwitcherDialog({
onClick={() => void addHost()}
disabled={!tauriAvailable || isSaving || !newUrl.trim()}
>
- {isSaving ? : null}
+ {isSaving ? : null}
{t('desktopHostSwitcher.actions.add')}
@@ -1104,7 +1087,7 @@ export function DesktopHostSwitcherDialog({
-
+
{t('desktopHostSwitcher.ssh.connectingTo', { host: sshSwitchModal.hostLabel || t('desktopHostSwitcher.ssh.instanceFallback') })}
@@ -1337,7 +1320,7 @@ export function DesktopHostSwitcherButton({ headerIconButtonClass }: DesktopHost
data-oc-host-switcher
className={cn(headerIconButtonClass, 'relative w-auto px-3')}
>
-
+
{safeEffectiveLabel}
@@ -1397,7 +1380,7 @@ export function DesktopHostSwitcherButton({ headerIconButtonClass }: DesktopHost
onClick={retryStartupSsh}
disabled={startupSshModal.connecting || !startupSshModal.hostId}
>
- {startupSshModal.connecting ? : null}
+ {startupSshModal.connecting ? : null}
{t('desktopHostSwitcher.actions.retry')}
@@ -1425,7 +1408,7 @@ export function DesktopHostSwitcherInline() {
className="w-full justify-center"
onClick={() => setOpen(true)}
>
-
+
{t('desktopHostSwitcher.actions.switchInstance')}
diff --git a/packages/ui/src/components/desktop/OpenInAppButton.tsx b/packages/ui/src/components/desktop/OpenInAppButton.tsx
index 2dd8fda4..f890fb70 100644
--- a/packages/ui/src/components/desktop/OpenInAppButton.tsx
+++ b/packages/ui/src/components/desktop/OpenInAppButton.tsx
@@ -7,12 +7,12 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { copyTextToClipboard } from '@/lib/clipboard';
import { cn } from '@/lib/utils';
import { isDesktopLocalOriginActive, isTauriShell, openDesktopPath, openDesktopProjectInApp } from '@/lib/desktop';
import { DEFAULT_OPEN_IN_APP_ID, OPEN_IN_APPS } from '@/lib/openInApps';
import { useOpenInAppsStore, type OpenInAppOption } from '@/stores/useOpenInAppsStore';
-import { RiArrowDownSLine, RiCheckLine, RiFileCopyLine, RiRefreshLine } from '@remixicon/react';
import { useI18n } from '@/lib/i18n';
const FINDER_DEFAULT_ICON_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAeGVYSWZNTQAqAAAACAAEARoABQAAAAEAAAA+ARsABQAAAAEAAABGASgAAwAAAAEAAgAAh2kABAAAAAEAAABOAAAAAAAAAJAAAAABAAAAkAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAAB+C9pSAAAACXBIWXMAABYlAAAWJQFJUiTwAAABnWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yNTY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+MjU2PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cl6wHhsAAAXaSURBVFgJ7VddbBRVFP5mdme6dOnu2tZawOAPjfxU+bGQYCRAsvw8qNGEQPTRJ0I0amL0wfjgA/HBR8KLD8YgDxJEUkVFxSYYTSRii6DQQCMGIQqlW7p0u+zMzo/fuTuzO9Nt0Td94CRn7pl7z5zznZ977y5wh/7jDGiz+T979qD5Ujbfd90xlll+stOF1uI40B1+4HhkjnZk9CgLQ9iXp2/BdcbgVc/h0sAgduywudJEMwLY9Of4ugtW5p3CpL7W1jTN88VmjdQYvnDKF1mczkYuNZLeCVg3X8fa9u+nqzUB2HRpdN2pSseRQknPoUL1Jo2ICTrPGcCzdwPdHENcAnicKRqcAk7cpL5J1r0JlAtPYV1XDETM/FtH3m19r+f5by+XjNX/xnmCX3/cCzydi4CKiC7lw+PArhGgoPPFq/6E0+9vwM6d5VBNpuv03cLNfeNTRh9KnJIiV2/PvSngycC5RD+dE5zb3g7s6QESzAZc2l6wuY9SnWIAxv10r81uU85Vt1FvtpEtlc/SMFUkUofeZ2IBta0DWDmXgkfbyTRz1qAYAMczOz3p1elOxYPyEllj421hdELViPO6Kudk3ia3UGe5ABDbvtnJZ52SdYmCZ3stdeexBabFdeAbYopEowtagVUZqFapBrtAGqpiVaFrGgyjZlrmTD5yEqoEJj4iFMuA62i6L3WPZkAiuHgarZ/vbWSBkTzO2rfTR4XOJVJhjfX44MBn+OTocVWbcF5MalxXPeVL6zYonoGo44YOtDI7qHC1lkL5nHnOc+tJRi3K6iygLNGMjt1A1XVV6iUzOvVtAvMlS2I/yBYlRf8MgA6szmXQ1jDfKhSgjft6DRtrkgarAiAw5nI9v2WDSn+Zxfd9DawGxIlPPQUg0A2HGABfEIYlCDU4+q0d8O+jRzHCCFYy+nu4BaeYAoksBCDrPYsXQQ6iitgiSQaS1FHHtMzFil4DpxTl4UhORSn4WOaaiGsbu4iFRkMnYQlEV0oSJQGQ4FyYgSRDjpqPZcCR6EOOWonIEsBqArAIQOMLzw0VXRRERF2VoA6Atk1+MzsASekMJYgaFEeHR4Cr85lNGntYzgKCYd/NSNIDCXr0ZJ2jwTsjSvEMzFQCCVmKHBRahn2DNb4rDRx8pnbXOOIg0JELLMHOF1AUkaRj1V8c2TookkMS83WK9QCVpRwtf5wCykQWRKDyJ44Ytc452QUV6inmN9IDIv/6y2+YLDuqTywBEHxv8rsoxQC4Fpf4cZ2pbJ4/huxXr0EvFmoRCrAIVymLQ3Eid0GJYPsPfISBLwdwi79YQnCqBNS7LQDP5qYSAKEDypOrX4WVWYLsFy+i9cwh6CUmUKIJI2Gq5cSbnLLw849D2Ld3L4olC1u3P0c1ow5Ozgixa3puWChONG1D3eLZUQOglvng+Vp5dBfseesx5/yHyI4cBTL3wsssRGs2g6/ppHijiMLoNSSMNHofy6Nn6SPsAR02nUoTtrDTSrdoi8CTni55rlOsCf1ypaDxlFMNU1epCV5XL6Y6dmOq+BeS48NIlq7Anpjg5dOFbPdDWLQyj/aubnUKSkMKi3NhkUd4kieYtbRbYS0bFAOQKI8NO363z1RJHmamtnlwhGksxV2w/gl29WRtm8kWtWUnRShLnQvXgDOXmLg2HzlvbDiyHD8Y517YP2i4FtueFPbB9FFqKcyobk4A5y7zquUFa7IXojyHoeXmAFcY755vaI6A56Xsofm/7+cmblBTpOldQ5vs3PJDVS+RVSAaus2SpJTO80t4NTNSOQfCDrtFkBevA0ME6HGvPdDpFlekzm7rf3nFQNRQEwBZTL9warObWfx21Uv1+fx1ERqVNampGoOHpF1tsdp07RnoGMxK1vT97rbK4IP6+Tc+fWXVsahaYGL6VO09d//GXHXr7jVeqmuppqU6ff4x0RO6lqRxgxHJpWKSlcw5eWfjq5rq/CdhaL5l6JWxjDc6bP7w5sn+/uMs2B36H2bgb6v9raK0+o9IAAAAAElFTkSuQmCC';
@@ -167,7 +167,7 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps)
)}
aria-label={t('openInApp.actions.chooseAppAria')}
>
-
+
void handleCopyPath()}>
-
+
{t('openInApp.actions.copyPath')}
@@ -195,7 +195,7 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps)
/>
{app.label}
{selectedApp.id === app.id ? (
-
+
) : null}
);
@@ -205,7 +205,7 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps)
className="flex items-center gap-2"
onClick={() => void loadInstalledApps(true)}
>
-
+
{t('openInApp.actions.refreshApps')}
) : null}
diff --git a/packages/ui/src/components/icon/Icon.tsx b/packages/ui/src/components/icon/Icon.tsx
new file mode 100644
index 00000000..3956cfba
--- /dev/null
+++ b/packages/ui/src/components/icon/Icon.tsx
@@ -0,0 +1,57 @@
+import React from "react"
+import { cn } from "@/lib/utils"
+import { iconSpriteData } from "./sprite"
+import type { IconName } from "./icons"
+
+const SPRITE_ID = "openchamber-icon-sprite"
+
+let spriteInjected = false
+
+function ensureSpriteOnce() {
+ if (spriteInjected) return
+ if (typeof document === "undefined") return
+ const body = document.body
+ if (!body) return
+
+ const existing = document.getElementById(SPRITE_ID)
+ if (existing) {
+ spriteInjected = true
+ return
+ }
+
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
+ svg.id = SPRITE_ID
+ svg.setAttribute("aria-hidden", "true")
+ svg.style.display = "none"
+ svg.innerHTML = Object.entries(iconSpriteData)
+ .map(([name, content]) => `${content} `)
+ .join("")
+ body.insertBefore(svg, body.firstChild)
+ spriteInjected = true
+}
+
+export interface IconProps extends React.ComponentPropsWithoutRef<"svg"> {
+ name: IconName
+}
+
+export const Icon = React.memo(({ name, className, ...rest }: IconProps) => {
+ // Inline sprite injection during render – must run before tries
+ // to resolve the #oc-* reference during the same commit.
+ if (typeof document !== "undefined") {
+ ensureSpriteOnce()
+ }
+
+ return (
+
+
+
+ )
+})
+
+Icon.displayName = "Icon"
diff --git a/packages/ui/src/components/icon/README.md b/packages/ui/src/components/icon/README.md
new file mode 100644
index 00000000..71f2765d
--- /dev/null
+++ b/packages/ui/src/components/icon/README.md
@@ -0,0 +1,78 @@
+# Icon System
+
+OpenChamber uses an SVG sprite-based icon system for performance. All icons are rendered via a single hidden SVG sprite injected into the DOM, referenced by ` `.
+
+## Usage
+
+```tsx
+import { Icon } from "@/components/icon/Icon";
+
+
+
+```
+
+## Icon Names
+
+Icons use kebab-case names based on Remixicon. To find an icon name:
+
+1. Start from the Remixicon component name: `RiArrowDownSLine`
+2. Remove `Ri` prefix → `ArrowDownSLine`
+3. Convert to kebab-case → `arrow-down-s`
+
+Common suffixes:
+- `Line` / `Fill` are dropped from the sprite name
+- Numbers are preserved: `RiChat4Line` → `chat-4`
+
+## Adding a New Icon
+
+1. Import and use it in your code: ` `
+2. Run `bun run icons:sprite` to regenerate the sprite with the new icon
+3. The script scans `packages/ui/src` for all `RiX` usages and extracts SVG paths
+
+If the icon doesn't exist in the sprite, the script will warn you.
+
+## Sizing
+
+The `Icon` component does not have a `size` prop. Use Tailwind classes instead:
+
+```tsx
+ {/* 16px */}
+ {/* 20px */}
+ {/* 24px */}
+```
+
+## Type Safety
+
+```tsx
+import type { IconName } from "@/components/icon/icons";
+
+const icon: IconName = "arrow-down-s"; // type-checked
+```
+
+## Architecture
+
+- `sprite.ts` — Auto-generated SVG path data (run `bun run generate-icon-sprite` to regenerate)
+- `Icon.tsx` — The `` component, injects sprite on first mount
+- `icons.ts` — TypeScript type `IconName`
+
+The sprite is injected as a hidden `` element containing `` elements. Each `` renders ` `.
+
+## Migration from @remixicon/react
+
+Old:
+```tsx
+import { RiArrowDownSLine } from "@remixicon/react";
+
+```
+
+New:
+```tsx
+import { Icon } from "@/components/icon/Icon";
+
+```
+
+If an icon is used as a component reference (not JSX):
+```tsx
+// Old: const icon = RiStackLine; return ;
+// New: const icon: IconName = "stack"; return ;
+```
diff --git a/packages/ui/src/components/icon/icons.ts b/packages/ui/src/components/icon/icons.ts
new file mode 100644
index 00000000..abd0fe49
--- /dev/null
+++ b/packages/ui/src/components/icon/icons.ts
@@ -0,0 +1 @@
+export type IconName = keyof typeof import("./sprite").iconSpriteData;
diff --git a/packages/ui/src/components/icon/sprite.ts b/packages/ui/src/components/icon/sprite.ts
new file mode 100644
index 00000000..862e545f
--- /dev/null
+++ b/packages/ui/src/components/icon/sprite.ts
@@ -0,0 +1,228 @@
+// This file is auto-generated by scripts/generate-icon-sprite.mjs
+// Do not edit manually. Run the script to update.
+
+export const iconSpriteData: Record = {
+ "add-circle": ` `,
+ "add": ` `,
+ "ai-agent-fill": ` `,
+ "ai-agent": ` `,
+ "ai-generate-2": ` `,
+ "alert": ` `,
+ "align-justify": ` `,
+ "apple": ` `,
+ "archive": ` `,
+ "archive-stack": ` `,
+ "arrow-down": ` `,
+ "arrow-down-s": ` `,
+ "arrow-go-back": ` `,
+ "arrow-go-forward": ` `,
+ "arrow-left": ` `,
+ "arrow-left-long": ` `,
+ "arrow-left-right": ` `,
+ "arrow-left-s": ` `,
+ "arrow-right": ` `,
+ "arrow-right-s": ` `,
+ "arrow-up-double": ` `,
+ "arrow-up": ` `,
+ "arrow-up-s": ` `,
+ "attachment-2": ` `,
+ "bar-chart-2": ` `,
+ "bar-chart-box": ` `,
+ "book": ` `,
+ "book-open": ` `,
+ "booklet": ` `,
+ "brain-ai-3": ` `,
+ "brain": ` `,
+ "briefcase": ` `,
+ "bug": ` `,
+ "calendar": ` `,
+ "calendar-schedule": ` `,
+ "camera": ` `,
+ "chat-1": ` `,
+ "chat-3": ` `,
+ "chat-4": ` `,
+ "chat-ai-3": ` `,
+ "chat-history": ` `,
+ "chat-new": ` `,
+ "check": ` `,
+ "checkbox-blank-circle-fill": ` `,
+ "checkbox-blank": ` `,
+ "checkbox-circle": ` `,
+ "checkbox": ` `,
+ "checkbox-multiple": ` `,
+ "clipboard": ` `,
+ "close-circle": ` `,
+ "close": ` `,
+ "cloud": ` `,
+ "cloud-off": ` `,
+ "code-ai": ` `,
+ "code-box": ` `,
+ "code": ` `,
+ "code-sslash": ` `,
+ "command": ` `,
+ "computer": ` `,
+ "contract-up-down": ` `,
+ "corner-down-left": ` `,
+ "cursor": ` `,
+ "database-2": ` `,
+ "delete-bin": ` `,
+ "discord-fill": ` `,
+ "donut-chart-fill": ` `,
+ "donut-chart": ` `,
+ "download-cloud": ` `,
+ "download": ` `,
+ "drag-move-2": ` `,
+ "draggable": ` `,
+ "earth": ` `,
+ "edit-2": ` `,
+ "edit": ` `,
+ "emotion-happy": ` `,
+ "equalizer-2": ` `,
+ "error-warning": ` `,
+ "expand-up-down": ` `,
+ "external-link": ` `,
+ "eye": ` `,
+ "eye-off": ` `,
+ "file-add": ` `,
+ "file-check-fill": ` `,
+ "file-check": ` `,
+ "file-code": ` `,
+ "file-copy-2": ` `,
+ "file-copy": ` `,
+ "file-edit": ` `,
+ "file-image": ` `,
+ "file": ` `,
+ "file-list-2": ` `,
+ "file-music": ` `,
+ "file-pdf": ` `,
+ "file-search": ` `,
+ "file-text": ` `,
+ "file-transfer": ` `,
+ "file-video": ` `,
+ "flashlight": ` `,
+ "flask": ` `,
+ "folder-3-fill": ` `,
+ "folder-3": ` `,
+ "folder-6": ` `,
+ "folder-add": ` `,
+ "folder": ` `,
+ "folder-open-fill": ` `,
+ "folder-open": ` `,
+ "folder-received": ` `,
+ "folders": ` `,
+ "fullscreen-exit": ` `,
+ "fullscreen": ` `,
+ "gamepad": ` `,
+ "git-branch": ` `,
+ "git-close-pull-request": ` `,
+ "git-commit": ` `,
+ "git-merge": ` `,
+ "git-pr-draft": ` `,
+ "git-pull-request": ` `,
+ "git-repository": ` `,
+ "github-fill": ` `,
+ "github": ` `,
+ "global": ` `,
+ "graduation-cap": ` `,
+ "hammer": ` `,
+ "heart": ` `,
+ "history": ` `,
+ "home": ` `,
+ "hourglass-fill": ` `,
+ "hourglass": ` `,
+ "image-download": ` `,
+ "inbox-archive": ` `,
+ "inbox-unarchive-fill": ` `,
+ "inbox-unarchive": ` `,
+ "information": ` `,
+ "key": ` `,
+ "layout-column": ` `,
+ "layout-left": ` `,
+ "layout-right": ` `,
+ "leaf": ` `,
+ "lightbulb": ` `,
+ "link-unlink-m": ` `,
+ "list-check-2": ` `,
+ "list-check-3": ` `,
+ "list-unordered": ` `,
+ "loader-4": ` `,
+ "loader": ` `,
+ "lock-2": ` `,
+ "lock": ` `,
+ "lock-unlock": ` `,
+ "loop-right-ai": ` `,
+ "macbook": ` `,
+ "menu-fold-2": ` `,
+ "menu-search": ` `,
+ "message-2": ` `,
+ "mic": ` `,
+ "mic-off": ` `,
+ "more-2-fill": ` `,
+ "more-2": ` `,
+ "more": ` `,
+ "music": ` `,
+ "node-tree": ` `,
+ "notification-3": ` `,
+ "palette": ` `,
+ "pencil-ai": ` `,
+ "pencil": ` `,
+ "picture-in-picture-2": ` `,
+ "pie-chart": ` `,
+ "play": ` `,
+ "play-list-add": ` `,
+ "plug-2": ` `,
+ "plug": ` `,
+ "pulse": ` `,
+ "pushpin-2-fill": ` `,
+ "pushpin-2": ` `,
+ "pushpin": ` `,
+ "question": ` `,
+ "record-circle": ` `,
+ "refresh": ` `,
+ "restart": ` `,
+ "robot-2": ` `,
+ "robot": ` `,
+ "rocket": ` `,
+ "save-3": ` `,
+ "scissors": ` `,
+ "search-eye": ` `,
+ "search": ` `,
+ "send-plane-2": ` `,
+ "send-plane": ` `,
+ "server": ` `,
+ "settings-3": ` `,
+ "share-2": ` `,
+ "shield-check": ` `,
+ "shield-keyhole": ` `,
+ "shield": ` `,
+ "shield-user": ` `,
+ "shuffle": ` `,
+ "slash-commands-2": ` `,
+ "smartphone": ` `,
+ "sparkling": ` `,
+ "split-cells-horizontal": ` `,
+ "stack": ` `,
+ "star-fill": ` `,
+ "star": ` `,
+ "sticky-note": ` `,
+ "stop-circle": ` `,
+ "stop": ` `,
+ "subtract": ` `,
+ "survey": ` `,
+ "task": ` `,
+ "terminal-box": ` `,
+ "terminal": ` `,
+ "terminal-window": ` `,
+ "text": ` `,
+ "text-wrap": ` `,
+ "time": ` `,
+ "timer": ` `,
+ "tools": ` `,
+ "twitter-xfill": ` `,
+ "unpin": ` `,
+ "user-3": ` `,
+ "user": ` `,
+ "voice-recognition": ` `,
+ "volume-up": ` `,
+ "window": ` `,
+};
diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx
index 22e2406e..7f12ba0d 100644
--- a/packages/ui/src/components/layout/ContextPanel.tsx
+++ b/packages/ui/src/components/layout/ContextPanel.tsx
@@ -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 ;
+ return ;
}
if (tab.mode === 'plan') {
- return ;
+ return ;
}
if (tab.mode === 'context') {
- return ;
+ return ;
}
if (tab.mode === 'chat') {
- return ;
+ return ;
}
if (tab.mode === 'preview') {
- return ;
+ return ;
}
return undefined;
@@ -919,7 +919,7 @@ const PreviewPane: React.FC = ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.actions.reload')}
disabled={!effectiveSrc}
>
-
+
= ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.actions.openExternal')}
disabled={!directSrc}
>
-
+
{isLoopback ? (
= ({ rawUrl, onNavigate }) => {
aria-label={t('contextPanel.preview.inspect.toggle')}
disabled={!bridgeReady}
>
-
+
) : null}
{isLoopback ? (
@@ -961,7 +961,7 @@ const PreviewPane: React.FC = ({ rawUrl, onNavigate }) => {
aria-label={bridgeReady ? t('contextPanel.preview.console.open') : t('contextPanel.preview.console.waiting')}
disabled={!bridgeReady && consoleEvents.length === 0}
>
-
+
{consoleErrorCount > 0 ? (
{consoleErrorCount}
) : null}
@@ -1380,7 +1380,7 @@ export const ContextPanel: React.FC = () => {
? openContextPreview(effectiveDirectory, url)} />
: (
-
+
{t('contextPanel.preview.title')}
{t('contextPanel.preview.description')}
@@ -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 ? : }
+ {isExpanded ? : }
{
title={t('contextPanel.actions.closePanel')}
aria-label={t('contextPanel.actions.closePanel')}
>
-
+
diff --git a/packages/ui/src/components/layout/ContextSidebarTab.tsx b/packages/ui/src/components/layout/ContextSidebarTab.tsx
index f0613d1d..15879613 100644
--- a/packages/ui/src/components/layout/ContextSidebarTab.tsx
+++ b/packages/ui/src/components/layout/ContextSidebarTab.tsx
@@ -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 ? : }
+ {isCopied ? : }
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}
>
-
+
@@ -162,7 +162,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
referrerPolicy="no-referrer"
/>
) : (
-
+
)}
@@ -195,7 +195,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
/>
) : (
-
+
)}
@@ -208,7 +208,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
) : null}
- {isCurrent ? : null}
+ {isCurrent ? : null}
);
})}
@@ -231,7 +231,7 @@ const DesktopGitHubControl = React.memo(function DesktopGitHubControl({
referrerPolicy="no-referrer"
/>
) : (
-
+
)}
);
@@ -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'
)}
>
-
+
{isDesktopApp ? (
{compactCurrentInstanceLabel}
) : null}
@@ -411,7 +411,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
disabled={isQuotaLoading || isUsageRefreshSpinning}
aria-label={t('header.services.refreshRateLimitsAria')}
>
-
+
@@ -485,7 +485,7 @@ const DesktopServicesMenu = React.memo(function DesktopServicesMenu({
>
{family.familyLabel}
- {isExpanded ? : }
+ {isExpanded ? : }
@@ -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
= ({
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 = ({
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 = ({
}, [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 = ({
return servicesTabs.map((tab) => ({
id: tab.value,
label: tab.label,
- icon: ,
+ icon: ,
}));
}, [servicesTabs]);
@@ -1628,8 +1626,8 @@ export const Header: React.FC = ({
const mobileServicesTabItems = React.useMemo(() => {
return [
- { id: 'usage', label: t('layout.services.usage'), icon: },
- { id: 'mcp', label: 'MCP', icon: },
+ { id: 'usage', label: t('layout.services.usage'), icon: },
+ { id: 'mcp', label: 'MCP', icon: },
];
}, [t]);
@@ -1709,14 +1707,14 @@ export const Header: React.FC = ({
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 ;
}
- return Icon ? : null;
+ return tabIconName ? : null;
};
const tabButton = (
@@ -1766,7 +1764,7 @@ export const Header: React.FC = ({
onClick={handleOpenContextPlan}
className={cn(desktopHeaderIconButtonClass, isContextPlanActive && 'bg-[var(--interactive-hover)]')}
>
-
+
@@ -1807,13 +1805,13 @@ export const Header: React.FC = ({
title={t('header.actions.terminalPanelWithShortcut', { shortcut: shortcutLabel('toggle_terminal') })}
ariaLabel={t('header.actions.toggleTerminalPanelAria')}
onClick={toggleBottomTerminal}
- Icon={RiTerminalBoxLine}
+ Icon={'terminal-box'}
/>
= ({
ariaLabel={t('header.actions.openSessionsAria')}
onClick={handleOpenSessionSwitcher}
className={`${desktopHeaderIconButtonClass} shrink-0`}
- Icon={RiLayoutLeftLine}
+ Icon={'layout-left'}
/>
@@ -1861,7 +1859,7 @@ export const Header: React.FC = ({
onClick={handleHeaderNewSession}
className={cn(desktopHeaderIconButtonClass, 'mr-6 shrink-0')}
>
-
+
@@ -1886,7 +1884,7 @@ export const Header: React.FC = ({
{activeProjectLabel ? {activeProjectLabel} : null}
{currentBranchLabel ? (
-
+
{currentBranchLabel}
) : null}
@@ -1902,7 +1900,7 @@ export const Header: React.FC = ({
"inline-flex min-w-0 items-center gap-0.5",
worktreeBadgeKind === 'attention' || worktreeBadgeKind === 'invalid' || worktreeBadgeKind === 'missing' ? 'text-status-warning' : 'text-muted-foreground/60'
)}>
-
+
{worktreeBadge}
) : null}
@@ -1943,7 +1941,7 @@ export const Header: React.FC = ({
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 = ({
)}
aria-label={leftDrawerOpen ? t('header.actions.closeSessionsAria') : t('header.actions.openSessionsAria')}
>
-
+
) : isSessionSwitcherOpen ? (
= ({
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')}
>
-
+
) : (
= ({
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')}
>
-
+
)}
@@ -2009,7 +2007,7 @@ export const Header: React.FC = ({
{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 (
@@ -2032,8 +2030,8 @@ export const Header: React.FC = ({
>
{isDiffTab ? (
- ) : Icon ? (
-
+ ) : tabIconName ? (
+
) : null}
{tab.badge !== undefined && tab.badge > 0 && (
@@ -2088,7 +2086,7 @@ export const Header: React.FC = ({
aria-label={t('header.services.viewAria')}
className={mobileHeaderIconButtonClass}
>
-
+
@@ -2128,7 +2126,7 @@ export const Header: React.FC = ({
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')}
>
-
+
@@ -2187,7 +2185,7 @@ export const Header: React.FC = ({
disabled={isQuotaLoading || isUsageRefreshSpinning}
aria-label={t('header.services.refreshRateLimitsAria')}
>
-
+
@@ -2279,9 +2277,9 @@ export const Header: React.FC = ({
{family.familyLabel}
{isExpanded ? (
-
+
) : (
-
+
)}
@@ -2348,7 +2346,7 @@ export const Header: React.FC = ({
)}
aria-label={rightDrawerOpen ? 'Close git sidebar' : 'Open git sidebar'}
>
-
+
diff --git a/packages/ui/src/components/layout/ProjectActionsButton.tsx b/packages/ui/src/components/layout/ProjectActionsButton.tsx
index 31e33324..54b08b75 100644
--- a/packages/ui/src/components/layout/ProjectActionsButton.tsx
+++ b/packages/ui/src/components/layout/ProjectActionsButton.tsx
@@ -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
- ?
+ ?
: selectedRunning
- ?
- : }
+ ?
+ : }
{showSelectedPreviewButton ? (
@@ -792,7 +784,7 @@ export const ProjectActionsButton = ({
aria-label={t('projectActions.actions.openPreview')}
onClick={handleOpenSelectedPreview}
>
-
+
{t('projectActions.actions.openPreview')}
@@ -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')}
>
-
+
-
+
{t('projectActions.actions.addNewAction')}
{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);
}}
>
-
+
{entry.name}
{isStopping || runState?.status === 'waiting-for-preview'
- ?
+ ?
: isRunning
- ?
+ ?
: null}
);
@@ -873,10 +865,10 @@ export const ProjectActionsButton = ({
>
{isStoppingSelected || isWaitingForSelectedPreview
- ?
+ ?
: selectedRunning
- ?
- : }
+ ?
+ : }
{!compact ? {selectedButtonLabel} : null}
@@ -894,7 +886,7 @@ export const ProjectActionsButton = ({
)}
aria-label={t('projectActions.actions.openPreview')}
>
-
+
{t('projectActions.actions.openPreview')}
@@ -912,20 +904,20 @@ export const ProjectActionsButton = ({
)}
aria-label={t('projectActions.actions.chooseActionAria')}
>
-
+
-
+
{t('projectActions.actions.addNewAction')}
{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);
}}
>
-
+
{entry.name}
{isStopping || runState?.status === 'waiting-for-preview'
- ?
+ ?
: isRunning
- ?
+ ?
: null}
);
diff --git a/packages/ui/src/components/layout/ProjectEditDialog.tsx b/packages/ui/src/components/layout/ProjectEditDialog.tsx
index 511d9fe6..06ee364e 100644
--- a/packages/ui/src/components/layout/ProjectEditDialog.tsx
+++ b/packages/ui/src/components/layout/ProjectEditDialog.tsx
@@ -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 = ({
{PROJECT_ICONS.map((i) => {
- const IconComponent = i.Icon;
+ const iconName = i.Icon;
return (
= ({
)}
title={i.label}
>
-
diff --git a/packages/ui/src/components/layout/RightSidebarTabs.tsx b/packages/ui/src/components/layout/RightSidebarTabs.tsx
index e2c270e5..7fef5f82 100644
--- a/packages/ui/src/components/layout/RightSidebarTabs.tsx
+++ b/packages/ui/src/components/layout/RightSidebarTabs.tsx
@@ -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: ,
+ icon: ,
},
{
id: 'files',
label: t('layout.rightSidebar.files'),
- icon: ,
+ icon: ,
},
{
id: 'context',
label: t('layout.rightSidebar.context'),
- icon: ,
+ icon: ,
},
], [t]);
diff --git a/packages/ui/src/components/layout/SidebarFilesTree.tsx b/packages/ui/src/components/layout/SidebarFilesTree.tsx
index 8803119c..6fbc67b8 100644
--- a/packages/ui/src/components/layout/SidebarFilesTree.tsx
+++ b/packages/ui/src/components/layout/SidebarFilesTree.tsx
@@ -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 = ({
>
{isDir ? (
isExpanded ? (
-
+
) : (
-
+
)
) : (
getFileIcon(node.path, node.extension)
@@ -252,13 +237,13 @@ const FileRow: React.FC = ({
className="h-6 w-6"
onClick={handleMenuButtonClick}
>
-
+
setContextMenuPath(null)}>
{canRename && (
{ e.stopPropagation(); onOpenDialog('rename', node); }}>
- {t('sidebarFilesTree.menu.rename')}
+ {t('sidebarFilesTree.menu.rename')}
)}
{
@@ -271,19 +256,19 @@ const FileRow: React.FC = ({
toast.error(t('sidebarFilesTree.toast.copyFailed'));
});
}}>
- {t('sidebarFilesTree.menu.copyPath')}
+ {t('sidebarFilesTree.menu.copyPath')}
{!isDir && downloadFile && (
{
e.stopPropagation();
void downloadFile(node.path);
}}>
- {t('sidebarFilesTree.menu.save')}
+ {t('sidebarFilesTree.menu.save')}
)}
{canReveal && (
{ e.stopPropagation(); onRevealPath(node.path); }}>
- {t(getRevealLabelKey())}
+ {t(getRevealLabelKey())}
)}
{isDir && (canCreateFile || canCreateFolder) && (
@@ -291,12 +276,12 @@ const FileRow: React.FC = ({
{canCreateFile && (
{ e.stopPropagation(); onOpenDialog('createFile', node); }}>
- {t('sidebarFilesTree.menu.newFile')}
+ {t('sidebarFilesTree.menu.newFile')}
)}
{canCreateFolder && (
{ e.stopPropagation(); onOpenDialog('createFolder', node); }}>
- {t('sidebarFilesTree.menu.newFolder')}
+ {t('sidebarFilesTree.menu.newFolder')}
)}
>
@@ -308,7 +293,7 @@ const FileRow: React.FC = ({
onClick={(e) => { e.stopPropagation(); onOpenDialog('delete', node); }}
className="text-destructive focus:text-destructive"
>
- {t('sidebarFilesTree.menu.delete')}
+ {t('sidebarFilesTree.menu.delete')}
>
)}
@@ -817,7 +802,7 @@ export const SidebarFilesTree: React.FC = () => {
-
+
{
searchInputRef.current?.focus();
}}
>
-
+
) : null}
@@ -847,7 +832,7 @@ export const SidebarFilesTree: React.FC = () => {
className="h-8 w-8 p-0 flex-shrink-0"
title={t('sidebarFilesTree.actions.newFileTitle')}
>
-
+
)}
{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')}
>
-
+
)}
void refreshRoot()} className="h-8 w-8 p-0 flex-shrink-0" title={t('sidebarFilesTree.actions.refreshTitle')}>
-
+
@@ -870,7 +855,7 @@ export const SidebarFilesTree: React.FC = () => {
{searching ? (
-
+
{t('sidebarFilesTree.state.searching')}
) : searchResults.length > 0 ? (
@@ -956,7 +941,7 @@ export const SidebarFilesTree: React.FC = () => {
onClick={() => void handleDialogSubmit()}
disabled={isDialogSubmitting || (activeDialog !== 'delete' && !dialogInputValue.trim())}
>
- {isDialogSubmitting ? : (
+ {isDialogSubmitting ? : (
activeDialog === 'delete' ? t('sidebarFilesTree.dialog.delete.confirm') : t('sidebarFilesTree.dialog.confirm')
)}
diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx
index 02eb82fe..5cf478c9 100644
--- a/packages/ui/src/components/layout/VSCodeLayout.tsx
+++ b/packages/ui/src/components/layout/VSCodeLayout.tsx
@@ -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 = ({ 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')}
>
-
+
)}
{title}
@@ -705,7 +704,7 @@ const VSCodeHeader: React.FC = ({ 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')}
>
-
+
)}
{onAgentManager && (
@@ -714,7 +713,7 @@ const VSCodeHeader: React.FC = ({ 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')}
>
-
+
)}
{showMcp && (
@@ -737,7 +736,7 @@ const VSCodeHeader: React.FC = ({ 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}
>
-
+
= ({ title, showBack, onBack, on
disabled={isQuotaLoading}
aria-label={t('vscodeLayout.quota.actions.refreshAria')}
>
-
+
@@ -870,7 +869,7 @@ const VSCodeHeader: React.FC = ({ 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')}
>
-
+
)}
{showContextUsage && stableContextUsage && stableContextUsage.totalTokens > 0 && (
diff --git a/packages/ui/src/components/mcp/McpDropdown.tsx b/packages/ui/src/components/mcp/McpDropdown.tsx
index 0acc8b62..d6976c89 100644
--- a/packages/ui/src/components/mcp/McpDropdown.tsx
+++ b/packages/ui/src/components/mcp/McpDropdown.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import type { McpStatus } from '@opencode-ai/sdk/v2';
-import { RiRefreshLine } from '@remixicon/react';
import {
DropdownMenu,
@@ -20,6 +19,7 @@ import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useMcpConfigStore } from '@/stores/useMcpConfigStore';
import { computeMcpHealth, useMcpStore } from '@/stores/useMcpStore';
import { McpIcon } from '@/components/icons/McpIcon';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
const statusTooltip = (
@@ -132,7 +132,7 @@ export const McpDropdownContent: React.FC = ({ active,
onClick={handleRefresh}
aria-label={t('mcpDropdown.actions.refreshAria')}
>
-
+
@@ -402,7 +402,7 @@ export const McpDropdown: React.FC = ({ headerIconButtonClass
onClick={handleRefresh}
aria-label={t('mcpDropdown.actions.refreshAria')}
>
-
+
{closeButton}
diff --git a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx
index fb8ec216..c3fc967a 100644
--- a/packages/ui/src/components/mini-chat/MiniChatLayout.tsx
+++ b/packages/ui/src/components/mini-chat/MiniChatLayout.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiExternalLinkLine, RiGitBranchLine, RiPushpin2Fill, RiPushpin2Line } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { ChatContainer } from '@/components/chat/ChatContainer';
import { ChatSurfaceProvider } from '@/components/chat/ChatSurfaceContext';
@@ -15,6 +14,7 @@ import { useProjectsStore } from '@/stores/useProjectsStore';
import { useGitBranchLabel, useGitStore } from '@/stores/useGitStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { resolveSessionDiffStats } from '@/components/session/sidebar/utils';
+import { Icon } from "@/components/icon/Icon";
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import type { SessionContextUsage } from '@/stores/types/sessionTypes';
@@ -266,7 +266,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => {
{projectLabel}
{branchLabel ? (
-
+
{branchLabel}
) : null}
@@ -302,7 +302,7 @@ const MiniChatHeader: React.FC<{ mode: MiniChatMode }> = ({ mode }) => {
title={pinned ? t('miniChat.actions.unpin') : t('miniChat.actions.pin')}
style={noDragRegionStyle}
>
- {pinned ? : }
+ {pinned ? : }
= ({ mode }) => {
title={t('miniChat.actions.openMain')}
style={noDragRegionStyle}
>
-
+
);
diff --git a/packages/ui/src/components/multirun/ModelMultiSelect.tsx b/packages/ui/src/components/multirun/ModelMultiSelect.tsx
index e10fcc22..c0b0a4e1 100644
--- a/packages/ui/src/components/multirun/ModelMultiSelect.tsx
+++ b/packages/ui/src/components/multirun/ModelMultiSelect.tsx
@@ -1,10 +1,10 @@
import React from 'react';
-import { RiAddLine, RiBrainAi3Line, RiCloseLine, RiSearchLine, RiStarFill, RiTimeLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { isIMECompositionEvent } from '@/lib/ime';
import { useConfigStore } from '@/stores/useConfigStore';
@@ -79,7 +79,7 @@ export const ModelChip: React.FC<{
onClick={onRemove}
className="text-muted-foreground hover:text-foreground ml-0.5"
>
-
+
);
@@ -349,7 +349,7 @@ export const ModelMultiSelect: React.FC = ({
setIsOpen(!isOpen);
}}
>
-
+
{addButtonLabel ?? t('multirun.modelMultiSelect.actions.addModel')}
@@ -426,7 +426,7 @@ export const ModelMultiSelect: React.FC = ({
{/* Search input */}
-
+
= ({
{filteredFavorites.length > 0 && (
<>
-
+
{t('multirun.modelMultiSelect.sections.favorites')}
{filteredFavorites.map(({ model, providerID, modelID }) => {
@@ -470,7 +470,7 @@ export const ModelMultiSelect: React.FC
= ({
<>
{filteredFavorites.length > 0 &&
}
-
+
{t('multirun.modelMultiSelect.sections.recent')}
{filteredRecents.map(({ model, providerID, modelID }) => {
@@ -554,7 +554,7 @@ export const ModelMultiSelect: React.FC = ({
size="chip"
className="px-2 gap-1.5 rounded-md !border-border/80 !bg-[var(--surface-subtle)] hover:!bg-[var(--interactive-hover)]/70 typography-meta font-medium text-foreground"
>
- = ({ children }) => (
-
+
@@ -168,7 +168,7 @@ export const MultiRunLauncher: React.FC = ({
iconColor: currentTheme.colors.surface.foreground,
},
);
- const ProjectIcon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
+ const projectIconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const iconColor = project.color ? PROJECT_COLOR_MAP[project.color] : undefined;
return (
@@ -180,10 +180,10 @@ export const MultiRunLauncher: React.FC = ({
>
- ) : ProjectIcon ? (
-
+ ) : projectIconName ? (
+
) : (
-
+
)}
{displayLabel}
@@ -615,7 +615,7 @@ export const MultiRunLauncher: React.FC = ({
aria-label={t('multirun.launcher.actions.closeEsc')}
className="inline-flex h-9 w-9 items-center justify-center p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary app-region-no-drag"
>
-
+
@@ -716,7 +716,7 @@ export const MultiRunLauncher: React.FC = ({
{/* ── Setup commands (collapsible, full width) ── */}
-
+
{t('multirun.launcher.setupCommands.label')}
@@ -733,7 +733,7 @@ export const MultiRunLauncher: React.FC = ({
{configuredSetupCount}
)}
-
@@ -765,7 +765,7 @@ export const MultiRunLauncher: React.FC = ({
className="flex-shrink-0 flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('multirun.launcher.setupCommands.removeCommandAria')}
>
-
+
))}
@@ -774,7 +774,7 @@ export const MultiRunLauncher: React.FC
= ({
onClick={() => setSetupCommands([...setupCommands, ''])}
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors px-1"
>
-
+
{t('multirun.launcher.setupCommands.addCommand')}
>
@@ -854,7 +854,7 @@ export const MultiRunLauncher: React.FC = ({
onClick={() => fileInputRef.current?.click()}
className="inline-flex items-center gap-1 h-6 px-2 rounded-md typography-micro text-muted-foreground hover:text-foreground hover:bg-[var(--interactive-hover)]/50 transition-colors"
>
-
+
{t('multirun.launcher.attachments.attach')}
@@ -871,9 +871,9 @@ export const MultiRunLauncher: React.FC = ({
}}
>
{file.mimeType.startsWith('image/') ? (
-
+
) : (
-
+
)}
{file.filename}
@@ -883,7 +883,7 @@ export const MultiRunLauncher: React.FC = ({
onClick={() => handleRemoveFile(file.id)}
className="text-muted-foreground hover:text-destructive"
>
-
+
))}
diff --git a/packages/ui/src/components/onboarding/ChooserScreen.tsx b/packages/ui/src/components/onboarding/ChooserScreen.tsx
index 955e027c..ddf68cf8 100644
--- a/packages/ui/src/components/onboarding/ChooserScreen.tsx
+++ b/packages/ui/src/components/onboarding/ChooserScreen.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine, RiArrowDownSLine } from '@remixicon/react';
import { isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { updateDesktopSettings } from '@/lib/persistence';
import { copyTextToClipboard } from '@/lib/clipboard';
import { restartDesktopApp } from '@/lib/desktop';
@@ -39,7 +39,7 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str
title={copyTitle}
aria-label={copyTitle}
>
-
+
);
@@ -306,7 +306,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
{copied ? (
-
+
{t('onboarding.common.status.copiedToClipboard')}
) : (
@@ -322,7 +322,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
className="text-xs text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1"
>
{platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')}
-
+
{t('onboarding.localSetup.advanced.title')}
-
+
@@ -419,7 +419,7 @@ export function ChooserScreen({ onCliAvailable }: ChooserScreenProps) {
>
{t('onboarding.localSetup.troubleshoot.title')}
-
+
{platform === 'windows' ? (
diff --git a/packages/ui/src/components/onboarding/DesktopConnectionRecovery.tsx b/packages/ui/src/components/onboarding/DesktopConnectionRecovery.tsx
index 1752cf6d..b3ae0307 100644
--- a/packages/ui/src/components/onboarding/DesktopConnectionRecovery.tsx
+++ b/packages/ui/src/components/onboarding/DesktopConnectionRecovery.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { RiRefreshLine, RiServerLine, RiMacbookLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { redactSensitiveUrl } from '@/lib/desktopHosts';
@@ -25,9 +25,9 @@ export type DesktopConnectionRecoveryProps = {
function getRecoveryIcon(iconKey: 'local' | 'remote'): React.ReactNode {
switch (iconKey) {
case 'local':
- return ;
+ return ;
case 'remote':
- return ;
+ return ;
}
}
@@ -99,7 +99,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="w-full"
>
-
+
{isRetrying
? t('onboarding.desktopRecovery.actions.retrying')
: t(retryLabelKey)}
@@ -114,7 +114,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="flex-1"
>
-
+
{t(config.useLocalLabelKey as Parameters[0])}
)}
@@ -126,7 +126,7 @@ export function DesktopConnectionRecovery({
disabled={isRetrying}
className="flex-1"
>
-
+
{t(config.useRemoteLabelKey as Parameters[0])}
)}
diff --git a/packages/ui/src/components/onboarding/LocalSetupScreen.tsx b/packages/ui/src/components/onboarding/LocalSetupScreen.tsx
index c2780389..f33ae588 100644
--- a/packages/ui/src/components/onboarding/LocalSetupScreen.tsx
+++ b/packages/ui/src/components/onboarding/LocalSetupScreen.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine } from '@remixicon/react';
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { updateDesktopSettings } from '@/lib/persistence';
import { copyTextToClipboard } from '@/lib/clipboard';
import { restartDesktopApp } from '@/lib/desktop';
@@ -40,7 +40,7 @@ function BashCommand({ onCopy, copyTitle }: { onCopy: () => void; copyTitle: str
className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors"
title={copyTitle}
>
-
+
);
@@ -264,7 +264,7 @@ export function LocalSetupScreen({
{copied ? (
-
+
{t('onboarding.common.status.copiedToClipboard')}
) : (
@@ -280,7 +280,7 @@ export function LocalSetupScreen({
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 justify-center"
>
{platform === 'windows' ? t('onboarding.localSetup.docs.windows') : t('onboarding.localSetup.docs.default')}
-
+
{checkError && (
diff --git a/packages/ui/src/components/sections/SectionPlaceholder.tsx b/packages/ui/src/components/sections/SectionPlaceholder.tsx
index 41d360a5..0cbeca1e 100644
--- a/packages/ui/src/components/sections/SectionPlaceholder.tsx
+++ b/packages/ui/src/components/sections/SectionPlaceholder.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { SIDEBAR_SECTION_CONFIG_MAP, SIDEBAR_SECTION_DESCRIPTIONS } from '@/constants/sidebar';
import type { SidebarSection } from '@/constants/sidebar';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
interface SectionPlaceholderProps {
sectionId: SidebarSection;
@@ -11,13 +12,13 @@ interface SectionPlaceholderProps {
export const SectionPlaceholder: React.FC
= ({ sectionId, variant }) => {
const { t } = useI18n();
const config = SIDEBAR_SECTION_CONFIG_MAP[sectionId];
- const Icon = config.icon;
+ const icon = config.icon;
if (variant === 'sidebar') {
return (
-
+
{config.label}
@@ -30,7 +31,7 @@ export const SectionPlaceholder: React.FC = ({ sectionI
return (
-
+
{config.label}
diff --git a/packages/ui/src/components/sections/agents/AgentsPage.tsx b/packages/ui/src/components/sections/agents/AgentsPage.tsx
index 092270fb..99c55b48 100644
--- a/packages/ui/src/components/sections/agents/AgentsPage.tsx
+++ b/packages/ui/src/components/sections/agents/AgentsPage.tsx
@@ -10,7 +10,6 @@ import { useDirectorySync } from '@/sync/sync-context';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
import { useDeviceInfo } from '@/lib/device';
import { opencodeClient } from '@/lib/opencode/client';
-import { RiCloseLine, RiInformationLine, RiRobot2Line, RiSubtractLine, RiUser3Line, RiFolderLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ModelSelector } from './ModelSelector';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
@@ -24,6 +23,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Icon } from "@/components/icon/Icon";
type PermissionAction = 'allow' | 'ask' | 'deny';
type PermissionRule = { permission: string; pattern: string; action: PermissionAction };
@@ -182,7 +182,6 @@ const buildPermissionConfigWithGlobal = (
return result as AgentConfig['permission'];
};
-
export const AgentsPage: React.FC = () => {
const { t } = useI18n();
const { isMobile } = useDeviceInfo();
@@ -305,7 +304,6 @@ export const AgentsPage: React.FC = () => {
currentRuleMap.get(buildRuleKey(permissionName, '*'))?.action
), [currentRuleMap]);
-
const getPatternRules = React.useCallback((permissionName: string): PermissionRule[] => (
permissionRules
.filter((rule) => rule.permission === permissionName && rule.pattern !== '*')
@@ -323,7 +321,6 @@ export const AgentsPage: React.FC = () => {
return Array.from(names).sort((a, b) => a.localeCompare(b));
}, [knownPermissionNames]);
-
const getPermissionSummary = React.useCallback((permissionName: string) => {
const defaultAction = permissionName === '*'
? globalPermission
@@ -608,12 +605,11 @@ export const AgentsPage: React.FC = () => {
}
};
-
if (!selectedAgentName) {
return (
-
+
{t('settings.agents.page.empty.title')}
{t('settings.agents.page.empty.description')}
@@ -669,13 +665,13 @@ export const AgentsPage: React.FC = () => {
-
+
{t('settings.common.scope.global')}
-
+
{t('settings.common.scope.project')}
@@ -704,7 +700,7 @@ export const AgentsPage: React.FC = () => {
{t('settings.agents.page.field.mode')}
-
+
{t('settings.agents.page.field.modeTooltip')}
@@ -781,7 +777,7 @@ export const AgentsPage: React.FC = () => {
{t('settings.agents.page.field.temperature')}
-
+
{t('settings.agents.page.field.temperatureTooltip')}
@@ -813,7 +809,7 @@ export const AgentsPage: React.FC = () => {
aria-label={t('settings.agents.page.field.clearTemperatureAria')}
title={t('settings.common.actions.clear')}
>
-
+
)}
@@ -825,7 +821,7 @@ export const AgentsPage: React.FC = () => {
{t('settings.agents.page.field.topP')}
-
+
{t('settings.agents.page.field.topPTooltip')}
@@ -857,7 +853,7 @@ export const AgentsPage: React.FC = () => {
aria-label={t('settings.agents.page.field.clearTopPAria')}
title={t('settings.common.actions.clear')}
>
-
+
)}
@@ -981,7 +977,7 @@ export const AgentsPage: React.FC = () => {
onClick={() => revertRule(permissionName, '*')}
className="px-1.5 py-0 h-5"
>
-
+
)}
@@ -1028,7 +1024,7 @@ export const AgentsPage: React.FC = () => {
onClick={() => isAdded ? removeRule(rule.permission, rule.pattern) : revertRule(rule.permission, rule.pattern)}
className="px-1.5 py-0 h-5"
>
-
+
)}
diff --git a/packages/ui/src/components/sections/agents/AgentsSidebar.tsx b/packages/ui/src/components/sections/agents/AgentsSidebar.tsx
index 7517ac92..c15c7ad9 100644
--- a/packages/ui/src/components/sections/agents/AgentsSidebar.tsx
+++ b/packages/ui/src/components/sections/agents/AgentsSidebar.tsx
@@ -17,7 +17,6 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { RiAddLine, RiAiAgentFill, RiAiAgentLine, RiDeleteBinLine, RiFileCopyLine, RiMore2Line, RiRobot2Line, RiRobotLine, RiRestartLine, RiEditLine } from '@remixicon/react';
import { useAgentsStore, isAgentBuiltIn, isAgentHidden, type AgentScope, type AgentDraft } from '@/stores/useAgentsStore';
import { useShallow } from 'zustand/react/shallow';
import { cn } from '@/lib/utils';
@@ -25,6 +24,7 @@ import type { Agent } from '@opencode-ai/sdk/v2';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { SidebarGroup } from '@/components/sections/shared/SidebarGroup';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface AgentsSidebarProps {
@@ -228,7 +228,6 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) =>
});
setSelectedAgent(newName);
-
};
const handleOpenRenameDialog = (agent: Agent) => {
@@ -293,11 +292,11 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) =>
const getAgentModeIcon = (mode?: string) => {
switch (mode) {
case 'primary':
- return ;
+ return ;
case 'all':
- return ;
+ return ;
case 'subagent':
- return ;
+ return ;
default:
return null;
}
@@ -339,7 +338,7 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) =>
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
-
+
@@ -347,7 +346,7 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) =>
{visibleAgents.length === 0 ? (
-
+
{t('settings.agents.sidebar.empty.title')}
{t('settings.agents.sidebar.empty.description')}
@@ -580,7 +579,7 @@ const AgentListItem: React.FC = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
-
+
@@ -591,7 +590,7 @@ const AgentListItem: React.FC = ({
onRename();
}}
>
-
+
{t('settings.common.actions.rename')}
)}
@@ -602,7 +601,7 @@ const AgentListItem: React.FC = ({
onDuplicate();
}}
>
-
+
{t('settings.common.actions.duplicate')}
@@ -613,7 +612,7 @@ const AgentListItem: React.FC = ({
onReset();
}}
>
-
+
{t('settings.common.actions.reset')}
)}
@@ -626,7 +625,7 @@ const AgentListItem: React.FC = ({
}}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
)}
diff --git a/packages/ui/src/components/sections/agents/ModelSelector.tsx b/packages/ui/src/components/sections/agents/ModelSelector.tsx
index 4348056a..e0e61666 100644
--- a/packages/ui/src/components/sections/agents/ModelSelector.tsx
+++ b/packages/ui/src/components/sections/agents/ModelSelector.tsx
@@ -11,11 +11,11 @@ import { Input } from '@/components/ui/input';
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device';
-import { RiArrowDownSLine, RiArrowRightSLine, RiCheckLine, RiCloseLine, RiLoader4Line, RiPencilAiLine, RiSearchLine, RiStarFill, RiStarLine, RiTimeLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { useModelLists } from '@/hooks/useModelLists';
import type { ModelMetadata } from '@/types';
import { useI18n } from '@/lib/i18n';
@@ -204,7 +204,7 @@ export const ModelSelector: React.FC = ({
{isSelected && (
-
+
)}
{
@@ -220,9 +220,9 @@ export const ModelSelector: React.FC = ({
title={isFavorite ? t('settings.agents.modelSelector.actions.removeFromFavorites') : t('settings.agents.modelSelector.actions.addToFavorites')}
>
{isFavorite ? (
-
+
) : (
-
+
)}
@@ -319,7 +319,7 @@ export const ModelSelector: React.FC = ({
className="model-favorite-button flex h-8 w-8 items-center justify-center text-primary hover:text-primary/80 active:scale-95 touch-manipulation"
aria-label={t('settings.agents.modelSelector.actions.unfavorite')}
>
-
+
);
@@ -373,7 +373,7 @@ export const ModelSelector: React.FC = ({
className="model-favorite-button flex h-8 w-8 items-center justify-center text-muted-foreground/50 hover:text-primary/80 active:scale-95 touch-manipulation"
aria-label={t('settings.agents.modelSelector.actions.favorite')}
>
-
+
);
@@ -409,9 +409,9 @@ export const ModelSelector: React.FC = ({
)}
{isExpanded ? (
-
+
) : (
-
+
)}
@@ -458,9 +458,9 @@ export const ModelSelector: React.FC = ({
: t('settings.agents.modelSelector.actions.favorite')}
>
{isFavoriteModel(provider.id as string, modelItem.id as string) ? (
-
+
) : (
-
+
)}
@@ -508,7 +508,7 @@ export const ModelSelector: React.FC = ({
{!isReady ? (
<>
-
+
{isUnavailable ? t('common.unavailable') : t('common.loading')}
>
) : providerId ? (
@@ -517,7 +517,7 @@ export const ModelSelector: React.FC = ({
className="h-3.5 w-3.5"
/>
) : (
-
+
)}
{isReady && (
@@ -525,7 +525,7 @@ export const ModelSelector: React.FC = ({
)}
-
+
) : (
@@ -536,7 +536,7 @@ export const ModelSelector: React.FC = ({
)}>
{!isReady ? (
<>
-
+
{isUnavailable ? t('common.unavailable') : t('common.loading')}
@@ -549,17 +549,17 @@ export const ModelSelector: React.FC = ({
providerId={providerId}
className="h-3.5 w-3.5 flex-shrink-0"
/>
-
+
>
) : (
-
+
)}
{providerId && modelId ? `${providerId}/${modelId}` : (placeholder || t('settings.agents.modelSelector.notSelected'))}
>
)}
-
+
@@ -619,7 +619,7 @@ export const ModelSelector: React.FC = ({
{/* Search Input */}
-
+
= ({
)}
onSelect={() => handleProviderAndModelChange('', '')}
>
-
+
{placeholder || t('settings.agents.modelSelector.notSelected')}
{!providerId && !modelId && (
-
+
)}
@@ -661,7 +661,7 @@ export const ModelSelector: React.FC
= ({
{filteredFavorites.length > 0 && (
-
+
{t('settings.agents.modelSelector.section.favorites')}
{filteredFavorites.map(({ model, providerID, modelID }) => {
@@ -676,7 +676,7 @@ export const ModelSelector: React.FC
= ({
{filteredFavorites.length > 0 &&
}
-
+
{t('settings.agents.modelSelector.section.recent')}
{filteredRecents.map(({ model, providerID, modelID }) => {
diff --git a/packages/ui/src/components/sections/behavior/BehaviorPage.tsx b/packages/ui/src/components/sections/behavior/BehaviorPage.tsx
index a2bfff92..2db8d88b 100644
--- a/packages/ui/src/components/sections/behavior/BehaviorPage.tsx
+++ b/packages/ui/src/components/sections/behavior/BehaviorPage.tsx
@@ -13,7 +13,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
-import { RiInformationLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import {
getResponseStylePresetInstructions,
isResponseStylePreset,
@@ -250,7 +250,7 @@ export const BehaviorPage: React.FC = () => {
-
+
@@ -295,7 +295,7 @@ export const BehaviorPage: React.FC = () => {
-
+
{t('settings.behavior.page.responseStyle.tooltip')}
diff --git a/packages/ui/src/components/sections/commands/AgentSelector.tsx b/packages/ui/src/components/sections/commands/AgentSelector.tsx
index 8e84a005..93cf7a7e 100644
--- a/packages/ui/src/components/sections/commands/AgentSelector.tsx
+++ b/packages/ui/src/components/sections/commands/AgentSelector.tsx
@@ -10,9 +10,9 @@ import { useAgentsStore, filterVisibleAgents } from '@/stores/useAgentsStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device';
-import { RiArrowDownSLine, RiLoader4Line, RiRobot2Line } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { useOpenCodeReadiness } from '@/hooks/useOpenCodeReadiness';
@@ -138,26 +138,26 @@ export const AgentSelector: React.FC = ({
{!isReady ? (
<>
-
+
{isUnavailable ? t('common.unavailable') : t('common.loading')}
>
) : (
<>
-
+
{agentName || t('settings.commands.agentSelector.selectAgentPlaceholder')}
>
)}
-
+
) : !isReady ? (
-
+
{isUnavailable ? t('common.unavailable') : t('common.loading')}
@@ -169,11 +169,11 @@ export const AgentSelector: React.FC
= ({
'flex items-center gap-2 px-2 rounded-lg bg-interactive-selection/20 border border-border/20 cursor-pointer hover:bg-interactive-hover/30 h-6 w-fit',
className
)}>
-
+
{agentName || t('settings.commands.agentSelector.notSelected')}
-
+
diff --git a/packages/ui/src/components/sections/commands/CommandsPage.tsx b/packages/ui/src/components/sections/commands/CommandsPage.tsx
index a87cdc3a..4be6a3bf 100644
--- a/packages/ui/src/components/sections/commands/CommandsPage.tsx
+++ b/packages/ui/src/components/sections/commands/CommandsPage.tsx
@@ -5,7 +5,6 @@ import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { useCommandsStore, type CommandConfig, type CommandScope } from '@/stores/useCommandsStore';
import { useShallow } from 'zustand/react/shallow';
-import { RiTerminalBoxLine, RiUser3Line, RiFolderLine } from '@remixicon/react';
import { ModelSelector } from '../agents/ModelSelector';
import { AgentSelector } from './AgentSelector';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
@@ -16,6 +15,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { parseModelIdentifier } from '@/lib/modelIdentifier';
@@ -180,7 +180,7 @@ export const CommandsPage: React.FC = () => {
return (
-
+
{t('settings.commands.page.empty.title')}
{t('settings.commands.page.empty.description')}
@@ -236,13 +236,13 @@ export const CommandsPage: React.FC = () => {
-
+
{t('settings.common.scope.global')}
-
+
{t('settings.common.scope.project')}
diff --git a/packages/ui/src/components/sections/commands/CommandsSidebar.tsx b/packages/ui/src/components/sections/commands/CommandsSidebar.tsx
index f6860de4..8ea75914 100644
--- a/packages/ui/src/components/sections/commands/CommandsSidebar.tsx
+++ b/packages/ui/src/components/sections/commands/CommandsSidebar.tsx
@@ -17,13 +17,13 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { RiAddLine, RiTerminalBoxLine, RiMore2Line, RiDeleteBinLine, RiFileCopyLine, RiRestartLine, RiEditLine } from '@remixicon/react';
import { useCommandsStore, isCommandBuiltIn, type Command } from '@/stores/useCommandsStore';
import { useSkillsStore } from '@/stores/useSkillsStore';
import { useShallow } from 'zustand/react/shallow';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { cn } from '@/lib/utils';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface CommandsSidebarProps {
@@ -97,7 +97,6 @@ export const CommandsSidebar: React.FC = ({ onItemSelect }
setSelectedCommand(newName);
onItemSelect?.();
-
};
const handleDeleteCommand = async (command: Command) => {
@@ -169,7 +168,6 @@ export const CommandsSidebar: React.FC = ({ onItemSelect }
});
setSelectedCommand(newName);
-
};
const handleOpenRenameDialog = (command: Command) => {
@@ -237,7 +235,7 @@ export const CommandsSidebar: React.FC = ({ onItemSelect }
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
-
+
@@ -245,7 +243,7 @@ export const CommandsSidebar: React.FC = ({ onItemSelect }
{commandOnlyItems.length === 0 ? (
-
+
{t('settings.commands.sidebar.empty.title')}
{t('settings.commands.sidebar.empty.description')}
@@ -440,7 +438,7 @@ const CommandListItem: React.FC = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
-
+
@@ -451,7 +449,7 @@ const CommandListItem: React.FC = ({
onRename();
}}
>
-
+
{t('settings.common.actions.rename')}
)}
@@ -462,7 +460,7 @@ const CommandListItem: React.FC = ({
onDuplicate();
}}
>
-
+
{t('settings.common.actions.duplicate')}
@@ -473,7 +471,7 @@ const CommandListItem: React.FC = ({
onReset();
}}
>
-
+
{t('settings.common.actions.reset')}
)}
@@ -486,7 +484,7 @@ const CommandListItem: React.FC = ({
}}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
)}
diff --git a/packages/ui/src/components/sections/git-identities/GitIdentityEditorDialog.tsx b/packages/ui/src/components/sections/git-identities/GitIdentityEditorDialog.tsx
index e085faf2..70d64cde 100644
--- a/packages/ui/src/components/sections/git-identities/GitIdentityEditorDialog.tsx
+++ b/packages/ui/src/components/sections/git-identities/GitIdentityEditorDialog.tsx
@@ -11,18 +11,8 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useGitIdentitiesStore, type GitIdentityProfile, type GitIdentityAuthType } from '@/stores/useGitIdentitiesStore';
-import {
- RiDeleteBinLine,
- RiGitBranchLine,
- RiBriefcaseLine,
- RiHomeLine,
- RiGraduationCapLine,
- RiCodeLine,
- RiInformationLine,
- RiKeyLine,
- RiLock2Line,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -35,11 +25,11 @@ const PROFILE_COLORS = [
];
const PROFILE_ICONS = [
- { key: 'branch', Icon: RiGitBranchLine, label: 'Branch' },
- { key: 'briefcase', Icon: RiBriefcaseLine, label: 'Work' },
- { key: 'house', Icon: RiHomeLine, label: 'Personal' },
- { key: 'graduation', Icon: RiGraduationCapLine, label: 'School' },
- { key: 'code', Icon: RiCodeLine, label: 'Code' },
+ { key: 'branch', Icon: 'git-branch', label: 'Branch' },
+ { key: 'briefcase', Icon: 'briefcase', label: 'Work' },
+ { key: 'house', Icon: 'home', label: 'Personal' },
+ { key: 'graduation', Icon: 'graduation-cap', label: 'School' },
+ { key: 'code', Icon: 'code', label: 'Code' },
];
interface GitIdentityEditorDialogProps {
@@ -261,7 +251,7 @@ export const GitIdentityEditorDialog: React.FC = (
{t('settings.gitIdentities.editor.field.icon')}
{PROFILE_ICONS.map((i) => {
- const IconComponent = i.Icon;
+ const iconName = i.Icon;
return (
= (
)}
title={i.label}
>
-
@@ -298,7 +288,7 @@ export const GitIdentityEditorDialog: React.FC = (
{!isGlobalProfile && * }
-
+
{t('settings.gitIdentities.editor.field.userNameTooltip')}
@@ -322,7 +312,7 @@ export const GitIdentityEditorDialog: React.FC = (
{!isGlobalProfile && * }
-
+
{t('settings.gitIdentities.editor.field.emailAddressTooltip')}
@@ -356,7 +346,7 @@ export const GitIdentityEditorDialog: React.FC = (
aria-pressed={authType === 'ssh'}
onClick={() => setAuthType('ssh')}
>
- SSH
+ SSH
= (
aria-pressed={authType === 'token'}
onClick={() => setAuthType('token')}
>
- {t('settings.gitIdentities.editor.field.authToken')}
+ {t('settings.gitIdentities.editor.field.authToken')}
@@ -375,7 +365,7 @@ export const GitIdentityEditorDialog: React.FC = (
{t('settings.gitIdentities.editor.field.sshKeyPath')}
-
+
{t('settings.gitIdentities.editor.field.sshKeyPathTooltip')}
@@ -398,7 +388,7 @@ export const GitIdentityEditorDialog: React.FC = (
*
-
+
{t('settings.gitIdentities.editor.field.hostTooltip')}
@@ -427,7 +417,7 @@ export const GitIdentityEditorDialog: React.FC = (
onClick={() => setIsDeleteDialogOpen(true)}
className="text-[var(--status-error)] hover:text-[var(--status-error)] border-[var(--status-error)]/30 hover:bg-[var(--status-error)]/10 mr-auto"
>
- {t('settings.common.actions.delete')}
+ {t('settings.common.actions.delete')}
)}
onOpenChange(false)} className="text-foreground hover:bg-interactive-hover hover:text-foreground">
diff --git a/packages/ui/src/components/sections/git-identities/GitPage.tsx b/packages/ui/src/components/sections/git-identities/GitPage.tsx
index 5c771ee8..c61bb5e9 100644
--- a/packages/ui/src/components/sections/git-identities/GitPage.tsx
+++ b/packages/ui/src/components/sections/git-identities/GitPage.tsx
@@ -15,35 +15,23 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import {
- RiAddLine,
- RiGitBranchLine,
- RiBriefcaseLine,
- RiHomeLine,
- RiGraduationCapLine,
- RiCodeLine,
- RiHeartLine,
- RiMore2Line,
- RiDeleteBinLine,
- RiDownloadLine,
- RiShieldKeyholeLine,
-} from '@remixicon/react';
import { useGitIdentitiesStore, type GitIdentityProfile, type DiscoveredGitCredential } from '@/stores/useGitIdentitiesStore';
import { useShallow } from 'zustand/react/shallow';
import { GitSettings } from '@/components/sections/openchamber/GitSettings';
import { GitHubSettings } from '@/components/sections/openchamber/GitHubSettings';
import { GitIdentityEditorDialog } from './GitIdentityEditorDialog';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
-const ICON_MAP: Record> = {
- branch: RiGitBranchLine,
- briefcase: RiBriefcaseLine,
- house: RiHomeLine,
- graduation: RiGraduationCapLine,
- code: RiCodeLine,
- heart: RiHeartLine,
+const ICON_MAP: Record = {
+ branch: 'git-branch',
+ briefcase: 'briefcase',
+ house: 'home',
+ graduation: 'graduation-cap',
+ code: 'code',
+ heart: 'heart',
};
const COLOR_MAP: Record = {
@@ -137,7 +125,7 @@ export const GitPage: React.FC = () => {
{t('settings.gitIdentities.page.section.title')}
openEditor('new')}>
- {t('settings.common.badge.new')}
+ {t('settings.common.badge.new')}
@@ -170,7 +158,7 @@ export const GitPage: React.FC = () => {
{/* Empty state */}
{!globalIdentity && profiles.length === 0 && unimportedCredentials.length === 0 && (
-
+
{t('settings.gitIdentities.page.empty.title')}
{t('settings.gitIdentities.page.empty.description')}
@@ -257,7 +245,7 @@ const IdentityRow: React.FC
= ({
hasBorder,
}) => {
const { t } = useI18n();
- const IconComponent = ICON_MAP[profile.icon || 'branch'] || RiGitBranchLine;
+ const iconName = ICON_MAP[profile.icon || 'branch'] || 'git-branch';
const iconColor = COLOR_MAP[profile.color || ''];
const authType = profile.authType || 'ssh';
@@ -273,7 +261,7 @@ const IdentityRow: React.FC = ({
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onEdit(); }}
>
-
+
{profile.name}
@@ -305,7 +293,7 @@ const IdentityRow: React.FC = ({
className="h-6 w-6 shrink-0 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
onClick={(e) => e.stopPropagation()}
>
-
+
@@ -317,7 +305,7 @@ const IdentityRow: React.FC = ({
onClick={(e) => { e.stopPropagation(); onDelete(); }}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
)}
@@ -355,7 +343,7 @@ const DiscoveredRow: React.FC = ({ credential, onImport, has
-
+
{t('settings.gitIdentities.page.actions.import')}
diff --git a/packages/ui/src/components/sections/magic-prompts/MagicPromptsPage.tsx b/packages/ui/src/components/sections/magic-prompts/MagicPromptsPage.tsx
index 967af3e6..9e66ce24 100644
--- a/packages/ui/src/components/sections/magic-prompts/MagicPromptsPage.tsx
+++ b/packages/ui/src/components/sections/magic-prompts/MagicPromptsPage.tsx
@@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { RiInformationLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import {
fetchMagicPromptOverrides,
getDefaultMagicPromptTemplate,
@@ -269,7 +269,7 @@ export const MagicPromptsPage: React.FC = () => {
{tUnsafe(pageConfig.titleKey)}
-
+
{tUnsafe(pageConfig.descriptionKey)}
@@ -306,7 +306,7 @@ export const MagicPromptsPage: React.FC = () => {
{tUnsafe(block.titleKey)}
-
+
{definition.description}
diff --git a/packages/ui/src/components/sections/mcp/McpPage.tsx b/packages/ui/src/components/sections/mcp/McpPage.tsx
index a273d68a..404c9fd6 100644
--- a/packages/ui/src/components/sections/mcp/McpPage.tsx
+++ b/packages/ui/src/components/sections/mcp/McpPage.tsx
@@ -20,20 +20,6 @@ import {
} from './mcpImport';
import { useMcpStore } from '@/stores/useMcpStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiClipboardLine,
- RiDeleteBinLine,
- RiEyeLine,
- RiEyeOffLine,
- RiExternalLinkLine,
- RiFileCodeLine,
- RiFolderLine,
- RiPlugLine,
- RiUser3Line,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { MCP_OAUTH_CALLBACK_PATH, parseMcpOAuthCallbackContext, parseMcpOAuthCallbackStateKey } from '@/components/sections/mcp/mcpOAuth';
@@ -52,6 +38,7 @@ import {
SelectItem,
SelectTrigger,
} from '@/components/ui/select';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
// ─────────────────────────────────────────────────────────────
@@ -175,7 +162,7 @@ const CommandTextarea: React.FC = ({
type="button"
title={pasteCommandTitle}
>
-
+
{pasteCommandLabel}
@@ -354,7 +341,7 @@ const EnvEditor: React.FC = ({
type="button"
title={pasteTitle}
>
-
+
{pasteLabel}
@@ -395,8 +382,8 @@ const EnvEditor: React.FC = ({
title={revealedKeys.has(idx) ? hideValueTitle : showValueTitle}
>
{revealedKeys.has(idx)
- ?
- : }
+ ?
+ : }
{/* Remove */}
@@ -406,7 +393,7 @@ const EnvEditor: React.FC = ({
onClick={() => removeRow(idx)}
aria-label={removeVariableAria}
>
-
+
))}
@@ -419,7 +406,7 @@ const EnvEditor: React.FC = ({
onClick={addRow}
type="button"
>
-
+
{addVariable}
@@ -1299,7 +1286,7 @@ export const McpPage: React.FC = () => {
return (
-
+
{t('settings.mcp.page.empty.selectServer')}
{t('settings.mcp.page.empty.addNewOne')}
@@ -1447,11 +1434,11 @@ export const McpPage: React.FC = () => {
{authUrl}
void openExternalUrl(authUrl)}>
-
+
{t('settings.mcp.page.actions.openInBrowser')}
void handleCopyAuthUrl()}>
-
+
{t('settings.mcp.page.actions.copyLink')}
@@ -1526,18 +1513,18 @@ export const McpPage: React.FC = () => {
/>
setDraftScope(value as McpScope)}>
- {draftScope === 'user' ? : }
+ {draftScope === 'user' ? : }
-
+
{t('settings.common.scope.global')}
-
+
{t('settings.common.scope.project')}
@@ -1558,7 +1545,7 @@ export const McpPage: React.FC = () => {
type="button"
title={t('settings.mcp.page.server.importJsonTitle')}
>
-
+
{t('settings.mcp.page.server.importJson')}
@@ -1663,9 +1650,9 @@ export const McpPage: React.FC = () => {
{isAdvancedRemoteOptionsOpen ? (
-
+
) : (
-
+
)}
@@ -1818,7 +1805,7 @@ export const McpPage: React.FC = () => {
className="!font-normal gap-1.5"
onClick={() => setEnvEntries([{ key: '', value: '' }])}
>
-
+
{t('settings.mcp.page.env.addEnvironmentVariable')}
) : (
@@ -1913,7 +1900,7 @@ export const McpPage: React.FC = () => {
onClick={handlePasteImportClipboard}
type="button"
>
-
+
{t('settings.mcp.page.importDialog.pasteFromClipboard')}
diff --git a/packages/ui/src/components/sections/mcp/McpSidebar.tsx b/packages/ui/src/components/sections/mcp/McpSidebar.tsx
index 4c554872..be2363e9 100644
--- a/packages/ui/src/components/sections/mcp/McpSidebar.tsx
+++ b/packages/ui/src/components/sections/mcp/McpSidebar.tsx
@@ -8,7 +8,6 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
-import { RiAddLine, RiDeleteBinLine, RiMore2Line, RiPlugLine, RiRefreshLine, RiServerLine, RiGlobalLine } from '@remixicon/react';
import { useMcpConfigStore, type McpDraft, type McpServerConfig } from '@/stores/useMcpConfigStore';
import { useShallow } from 'zustand/react/shallow';
import { useMcpStore } from '@/stores/useMcpStore';
@@ -178,7 +177,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
aria-label={t('settings.mcp.sidebar.actions.refreshStatusAria')}
title={t('settings.mcp.sidebar.actions.refreshStatusTitle')}
>
-
+
@@ -192,7 +191,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
onClick={handleCreateNew}
title={t('settings.mcp.sidebar.actions.addServerTitle')}
>
-
+
@@ -201,7 +200,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
{mcpServers.length === 0 ? (
-
+
{t('settings.mcp.sidebar.empty.title')}
{t('settings.mcp.sidebar.empty.description')}
@@ -246,9 +245,9 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
-
+
) : (
-
+
)}
@@ -262,7 +261,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
setOpenMenuMcp(open ? server.name : null)}>
-
+
@@ -273,7 +272,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
}}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
@@ -323,9 +322,9 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
: t('settings.mcp.sidebar.serverType.remoteTitle')}
>
{server.type === 'local' ? (
-
+
) : (
-
+
)}
@@ -339,7 +338,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
setOpenMenuMcp(open ? server.name : null)}>
-
+
@@ -350,7 +349,7 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
}}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
@@ -398,3 +397,4 @@ export const McpSidebar: React.FC = ({ onItemSelect }) => {
// Re-export for easy sidebar icon usage
export { McpIcon } from '@/components/icons/McpIcon';
+import { Icon } from "@/components/icon/Icon";
diff --git a/packages/ui/src/components/sections/openchamber/AboutSettings.tsx b/packages/ui/src/components/sections/openchamber/AboutSettings.tsx
index b52b6703..e7c8fcaa 100644
--- a/packages/ui/src/components/sections/openchamber/AboutSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/AboutSettings.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiDiscordFill, RiDownloadLine, RiGithubFill, RiLoaderLine, RiTwitterXFill } from '@remixicon/react';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { useShallow } from 'zustand/react/shallow';
import { UpdateDialog } from '@/components/ui/UpdateDialog';
@@ -7,6 +6,7 @@ import { useDeviceInfo } from '@/lib/device';
import { toast } from '@/components/ui';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
const GITHUB_URL = 'https://github.com/btriapitsyn/openchamber';
@@ -85,7 +85,7 @@ export const AboutSettings: React.FC = () => {
onClick={() => setUpdateDialogOpen(true)}
className="flex items-center gap-1 typography-meta text-[var(--primary-base)] hover:underline"
>
-
+
{t('settings.openchamber.about.actions.update')}
)}
@@ -103,7 +103,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
GitHub
@@ -113,7 +113,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
Discord
@@ -123,7 +123,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
@btriapitsyn
@@ -144,7 +144,6 @@ export const AboutSettings: React.FC = () => {
);
}
-
// Desktop layout (redesigned)
return (
@@ -164,7 +163,7 @@ export const AboutSettings: React.FC = () => {
{updateStore.checking && (
-
+
{t('settings.openchamber.about.state.checking')}
)}
@@ -174,7 +173,7 @@ export const AboutSettings: React.FC = () => {
variant="default"
onClick={() => setUpdateDialogOpen(true)}
>
-
+
{t('settings.openchamber.about.actions.updateToVersion', { version: updateStore.info?.version || '' })}
)}
@@ -206,7 +205,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground typography-meta transition-colors"
>
-
+
GitHub
@@ -216,7 +215,7 @@ export const AboutSettings: React.FC = () => {
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-muted-foreground hover:text-foreground typography-meta transition-colors"
>
-
+
@btriapitsyn
diff --git a/packages/ui/src/components/sections/openchamber/GitHubSettings.tsx b/packages/ui/src/components/sections/openchamber/GitHubSettings.tsx
index 89c75a16..6aa19a03 100644
--- a/packages/ui/src/components/sections/openchamber/GitHubSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/GitHubSettings.tsx
@@ -8,8 +8,8 @@ import { useDeviceInfo } from '@/lib/device';
import { cn } from '@/lib/utils';
import { openExternalUrl } from '@/lib/url';
import { useI18n } from '@/lib/i18n';
-import { RiGithubFill, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
type GitHubUser = {
login: string;
@@ -246,7 +246,7 @@ export const GitHubSettings: React.FC = () => {
GitHub
-
+
{t('settings.github.page.tooltip.connectAccount')}
@@ -276,7 +276,7 @@ export const GitHubSettings: React.FC = () => {
{user?.name?.trim() || user?.login || 'GitHub'}
-
+
{user?.login || t('settings.github.page.label.unknownUser')}
{user?.email &&
• }
{user?.email &&
{user.email} }
@@ -329,7 +329,7 @@ export const GitHubSettings: React.FC = () => {
/>
) : (
-
+
)}
diff --git a/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx b/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx
index 0ad40f3e..8dbd9108 100644
--- a/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { RiInformationLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { cn } from '@/lib/utils';
import {
@@ -155,7 +155,7 @@ export const KeyboardShortcutsSettings: React.FC = () => {
-
+
{t('settings.openchamber.keyboardShortcuts.tooltip')}
diff --git a/packages/ui/src/components/sections/openchamber/NotificationSettings.tsx b/packages/ui/src/components/sections/openchamber/NotificationSettings.tsx
index e91bf09b..17bda432 100644
--- a/packages/ui/src/components/sections/openchamber/NotificationSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/NotificationSettings.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiInformationLine, RiRestartLine } from '@remixicon/react';
import { useUIStore } from '@/stores/useUIStore';
import { useConfigStore } from '@/stores/useConfigStore';
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
@@ -13,6 +12,7 @@ import { NumberInput } from '@/components/ui/number-input';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -806,7 +806,7 @@ export const NotificationSettings: React.FC = () => {
{t('settings.notifications.page.summary.modelLabel')}
-
+
{t('settings.notifications.page.summary.modelTooltip')}
@@ -859,7 +859,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetThresholdAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -886,7 +886,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetLengthAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -915,7 +915,7 @@ export const NotificationSettings: React.FC = () => {
aria-label={t('settings.notifications.page.summary.resetMaxLengthAria')}
title={t('settings.common.actions.reset')}
>
-
+
diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
index d2d02d89..dd3becfa 100644
--- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiRestartLine, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -19,6 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import { Icon } from "@/components/icon/Icon";
import { isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
import { useDeviceInfo } from '@/lib/device';
import { usePwaDetection } from '@/hooks/usePwaDetection';
@@ -724,7 +724,7 @@ export const OpenChamberVisualSettings: React.FC
className="flex items-center justify-center rounded-md p-1 text-muted-foreground/70 hover:text-foreground"
aria-label={t('settings.openchamber.visual.field.themeImportInfoAria')}
>
-
+
@@ -835,7 +835,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetInstallAppNameAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -881,7 +881,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetInstallOrientationAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -927,7 +927,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetMobileKeyboardModeAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -971,7 +971,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetInterfaceFontAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1004,7 +1004,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetCodeFontAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1034,7 +1034,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetFontSizeAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1063,7 +1063,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetTerminalFontSizeAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1092,7 +1092,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetSpacingAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1105,7 +1105,7 @@ export const OpenChamberVisualSettings: React.FC
{t('settings.openchamber.visual.field.inputBarOffset')}
-
+
{t('settings.openchamber.visual.field.inputBarOffsetTooltip')}
@@ -1131,7 +1131,7 @@ export const OpenChamberVisualSettings: React.FC
aria-label={t('settings.openchamber.visual.actions.resetInputBarOffsetAria')}
title={t('settings.common.actions.reset')}
>
-
+
@@ -1171,7 +1171,7 @@ export const OpenChamberVisualSettings: React.FC
{t('settings.openchamber.visual.field.terminalQuickKeys')}
-
+
{t('settings.openchamber.visual.field.terminalQuickKeysTooltip')}
@@ -1187,8 +1187,6 @@ export const OpenChamberVisualSettings: React.FC
{hasBehaviorSettings && (
-
-
{(shouldShow('userMessageRendering') || shouldShow('mermaidRendering') || shouldShow('chatRenderMode') || shouldShow('messageTransport') || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || (shouldShow('diffLayout') && !isVSCode)) && (
{shouldShow('chatRenderMode') && (
@@ -1627,7 +1625,7 @@ export const OpenChamberVisualSettings: React.FC
{t('settings.openchamber.visual.field.showSplitAssistantMessageActions')}
-
+
{t('settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip')}
@@ -1729,7 +1727,7 @@ export const OpenChamberVisualSettings: React.FC
{t('settings.openchamber.visual.field.queueMessagesByDefault')}
-
+
{t('settings.openchamber.visual.field.queueMessagesByDefaultTooltip', { modifier: getModifierLabel() })}
diff --git a/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx
index 47fbac08..b889774e 100644
--- a/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/OpenCodeCliSettings.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { RiFolderLine, RiInformationLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { isDesktopShell, isTauriShell } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
@@ -95,7 +95,7 @@ export const OpenCodeCliSettings: React.FC = () => {
-
+
{t('settings.openchamber.opencodeCli.tooltipPrefix')}
@@ -130,7 +130,7 @@ export const OpenCodeCliSettings: React.FC = () => {
aria-label={t('settings.openchamber.opencodeCli.actions.browseAria')}
title={t('settings.openchamber.opencodeCli.actions.browse')}
>
-
+
diff --git a/packages/ui/src/components/sections/openchamber/SessionRetentionSettings.tsx b/packages/ui/src/components/sections/openchamber/SessionRetentionSettings.tsx
index 15dbff08..8044f95c 100644
--- a/packages/ui/src/components/sections/openchamber/SessionRetentionSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/SessionRetentionSettings.tsx
@@ -1,10 +1,10 @@
import React from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { RiInformationLine, RiRestartLine } from '@remixicon/react';
import { toast } from '@/components/ui';
import { NumberInput } from '@/components/ui/number-input';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
+import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { useSessionAutoCleanup } from '@/hooks/useSessionAutoCleanup';
import { useI18n } from '@/lib/i18n';
@@ -65,7 +65,7 @@ export const SessionRetentionSettings: React.FC = () => {
-
+
{t('settings.openchamber.sessionRetention.tooltip')}
@@ -120,7 +120,7 @@ export const SessionRetentionSettings: React.FC = () => {
aria-label={t('settings.openchamber.sessionRetention.actions.resetRetentionAria')}
title={t('settings.common.actions.reset')}
>
-
+
diff --git a/packages/ui/src/components/sections/openchamber/TunnelSettings.tsx b/packages/ui/src/components/sections/openchamber/TunnelSettings.tsx
index 49d94605..6b69317d 100644
--- a/packages/ui/src/components/sections/openchamber/TunnelSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/TunnelSettings.tsx
@@ -1,28 +1,12 @@
import React from 'react';
import QRCode from 'qrcode';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiCheckboxBlankCircleFill,
- RiCheckLine,
- RiCloseLine,
- RiCloudLine,
- RiDeleteBinLine,
- RiErrorWarningLine,
- RiExternalLinkLine,
- RiFileCopyLine,
- RiFolderLine,
- RiInformationLine,
- RiLoader4Line,
- RiRestartLine,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { requestFileAccess } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { useI18n } from '@/lib/i18n';
@@ -174,7 +158,7 @@ const ProviderOptionLabel: React.FC<{ provider: string }> = ({ provider }) => {
return (
-
+
{label}
);
@@ -1109,7 +1093,7 @@ export const TunnelSettings: React.FC = () => {
-
+
{t('settings.openchamber.tunnel.section.redeemedAccessLinks')}
@@ -1135,7 +1119,7 @@ export const TunnelSettings: React.FC = () => {
key={record.sessionId}
className="flex flex-wrap items-center gap-x-2 gap-y-1 rounded border border-[var(--surface-subtle)] bg-[var(--surface-elevated)] px-2 py-1.5"
>
-
+
{modeLabel}
@@ -1160,7 +1144,7 @@ export const TunnelSettings: React.FC = () => {
{state === 'not-available' && (
-
+
{t('settings.openchamber.tunnel.notAvailable.cloudflaredNotFound')}
{t('settings.openchamber.tunnel.notAvailable.installHint')}
@@ -1279,7 +1263,7 @@ export const TunnelSettings: React.FC = () => {
{tunnelMode === 'quick' && (
-
+
{t('settings.openchamber.tunnel.option.mode.quick.tooltip')}
@@ -1311,7 +1295,7 @@ export const TunnelSettings: React.FC = () => {
onClick={() => setIsAddingPreset((prev) => !prev)}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
-
+
{t('settings.common.actions.create')}
@@ -1345,8 +1329,8 @@ export const TunnelSettings: React.FC = () => {
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
{isOpen
- ?
- :
}
+ ?
+ :
}
{preset.name}
@@ -1360,7 +1344,7 @@ export const TunnelSettings: React.FC = () => {
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
-
+
@@ -1488,7 +1472,7 @@ export const TunnelSettings: React.FC = () => {
className="rounded p-0.5 text-muted-foreground/70 hover:text-foreground"
aria-label={t('settings.openchamber.tunnel.field.managedRemoteTokenInfoAria')}
>
-
+
@@ -1539,7 +1523,7 @@ export const TunnelSettings: React.FC = () => {
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
-
+
{managedLocalConfigPath && (
{
}}
disabled={state === 'starting' || state === 'stopping' || isSavingMode}
>
-
+
)}
@@ -1572,7 +1556,7 @@ export const TunnelSettings: React.FC = () => {
-
+
{tunnelMode === 'managed-remote' && (
<>
@@ -1587,7 +1571,7 @@ export const TunnelSettings: React.FC = () => {
}}
>
{t('settings.openchamber.tunnel.actions.openManagedRemoteDocs')}
-
+
>
)}
@@ -1604,7 +1588,7 @@ export const TunnelSettings: React.FC = () => {
}}
>
{t('settings.openchamber.tunnel.actions.openManagedLocalDocs')}
-
+
>
)}
@@ -1649,7 +1633,7 @@ export const TunnelSettings: React.FC = () => {
{willReplaceActiveTunnel && (
-
+
{t('settings.openchamber.tunnel.warning.replacesActiveTunnel')}
@@ -1669,7 +1653,7 @@ export const TunnelSettings: React.FC = () => {
className={cn(primaryCtaClass, state === 'starting' && 'opacity-70')}
>
{state === 'starting'
- ? <>
{t('settings.openchamber.tunnel.actions.startingTunnel')}>
+ ? <>
{t('settings.openchamber.tunnel.actions.startingTunnel')}>
: t('settings.openchamber.tunnel.actions.startTunnel')}
@@ -1703,8 +1687,8 @@ export const TunnelSettings: React.FC = () => {
{copied
- ?
- : }
+ ?
+ : }
{copied ? t('settings.openchamber.tunnel.actions.copied') : t('settings.common.actions.copyAll')}
@@ -1731,7 +1715,7 @@ export const TunnelSettings: React.FC = () => {
disabled={state === 'stopping' || isSavingMode || (tunnelMode === 'managed-local' && isManagedLocalConfigPathInvalid)}
className={primaryCtaClass}
>
-
+
{t('settings.openchamber.tunnel.actions.newConnectLink')}
@@ -1742,7 +1726,7 @@ export const TunnelSettings: React.FC = () => {
className="gap-2 text-[var(--status-error)]"
>
{state === 'stopping'
- ? <>
{t('settings.openchamber.tunnel.actions.stopping')}>
+ ? <>
{t('settings.openchamber.tunnel.actions.stopping')}>
: t('settings.openchamber.tunnel.actions.stopTunnel')}
diff --git a/packages/ui/src/components/sections/openchamber/VoiceSettings.tsx b/packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
index 70097362..455774cb 100644
--- a/packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/VoiceSettings.tsx
@@ -13,8 +13,8 @@ import {
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { NumberInput } from '@/components/ui/number-input';
-import { RiPlayLine, RiStopLine, RiCloseLine, RiAppleLine, RiInformationLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { browserVoiceService } from '@/lib/voice/browserVoiceService';
import { audioStreamService } from '@/lib/voice/audioStreamService';
import { cn } from '@/lib/utils';
@@ -445,7 +445,7 @@ export const VoiceSettings: React.FC = () => {
{t('settings.voice.page.field.provider')}
-
+
@@ -493,7 +493,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setVoiceProvider('say')}
className="!font-normal"
>
-
+
{t('settings.voice.page.provider.say')}
)}
@@ -528,7 +528,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setOpenaiApiKey('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
-
+
)}
@@ -559,7 +559,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setOpenaiCompatibleUrl('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
-
+
)}
@@ -592,7 +592,7 @@ export const VoiceSettings: React.FC = () => {
/>
- {isCompatiblePreviewPlaying ? : }
+ {isCompatiblePreviewPlaying ? : }
@@ -616,7 +616,7 @@ export const VoiceSettings: React.FC = () => {
- {isOpenAIPreviewPlaying ? : }
+ {isOpenAIPreviewPlaying ? : }
>
)}
@@ -638,7 +638,7 @@ export const VoiceSettings: React.FC = () => {
- {isPreviewPlaying ? : }
+ {isPreviewPlaying ? : }
>
)}
@@ -657,7 +657,7 @@ export const VoiceSettings: React.FC = () => {
- {isBrowserPreviewPlaying ? : }
+ {isBrowserPreviewPlaying ? : }
>
)}
@@ -734,7 +734,7 @@ export const VoiceSettings: React.FC = () => {
{t('settings.voice.page.field.provider')}
-
+
@@ -795,7 +795,7 @@ export const VoiceSettings: React.FC = () => {
onClick={() => setSttServerUrl('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
-
+
)}
diff --git a/packages/ui/src/components/sections/openchamber/WorktreeSectionContent.tsx b/packages/ui/src/components/sections/openchamber/WorktreeSectionContent.tsx
index 105c9091..355d3726 100644
--- a/packages/ui/src/components/sections/openchamber/WorktreeSectionContent.tsx
+++ b/packages/ui/src/components/sections/openchamber/WorktreeSectionContent.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiAddLine, RiCloseLine, RiDeleteBinLine, RiInformationLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessions } from '@/sync/sync-context';
@@ -241,8 +241,6 @@ export const WorktreeSectionContent: React.FC
= ({
});
}, [sessions, getWorktreeMetadata]);
-
-
// Refresh worktrees when sessions change (after deletion)
const sessionsKey = React.useMemo(() => sessions.map(s => s.id).join(','), [sessions]);
React.useEffect(() => {
@@ -276,7 +274,7 @@ export const WorktreeSectionContent: React.FC = ({
{t('settings.openchamber.worktrees.setup.title')}
-
+
{t('settings.openchamber.worktrees.setup.tooltipPrefix')}
@@ -310,7 +308,7 @@ export const WorktreeSectionContent: React.FC = ({
className="flex-shrink-0 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('settings.openchamber.worktrees.setup.removeCommandAria')}
>
-
+
))}
@@ -321,7 +319,7 @@ export const WorktreeSectionContent: React.FC = ({
className="!font-normal"
onClick={handleAddCommand}
>
-
+
{t('settings.openchamber.worktrees.setup.addCommand')}
@@ -335,7 +333,7 @@ export const WorktreeSectionContent: React.FC = ({
{t('settings.openchamber.worktrees.list.title')}
-
+
{t('settings.openchamber.worktrees.list.tooltip')}
@@ -379,7 +377,7 @@ export const WorktreeSectionContent: React.FC = ({
)}
aria-label={t('settings.openchamber.worktrees.list.deleteWorktreeAria', { name: worktree.branch || worktree.label || worktree.path })}
>
-
+
))}
diff --git a/packages/ui/src/components/sections/projects/ProjectActionsSection.tsx b/packages/ui/src/components/sections/projects/ProjectActionsSection.tsx
index e69f9f60..96643562 100644
--- a/packages/ui/src/components/sections/projects/ProjectActionsSection.tsx
+++ b/packages/ui/src/components/sections/projects/ProjectActionsSection.tsx
@@ -1,12 +1,4 @@
import React from 'react';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiDeleteBinLine,
- RiInformationLine,
- RiPlayLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
@@ -30,6 +22,7 @@ import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { isDesktopShell } from '@/lib/desktop';
import {
@@ -201,7 +194,7 @@ export const ProjectActionsSection: React.FC = ({ pr
{t('settings.projects.actions.description')}
-
+
{t('settings.projects.actions.actions.add')}
@@ -217,7 +210,7 @@ export const ProjectActionsSection: React.FC = ({ pr
{actions.map((action) => {
const selectedIconKey = (action.icon as keyof typeof PROJECT_ACTION_ICON_MAP) || 'play';
- const SelectedIcon = PROJECT_ACTION_ICON_MAP[selectedIconKey] || RiPlayLine;
+ const selectedIconName = PROJECT_ACTION_ICON_MAP[selectedIconKey] || 'play';
const isOpen = expandedActions[action.id] ?? false;
const title = action.name.trim() || t('settings.projects.actions.state.untitled');
@@ -238,11 +231,11 @@ export const ProjectActionsSection: React.FC
= ({ pr
{isOpen ? (
-
+
) : (
-
+
)}
-
+
{title}
@@ -257,7 +250,7 @@ export const ProjectActionsSection: React.FC
= ({ pr
className="!font-normal h-7 w-7 px-0 text-muted-foreground hover:text-[var(--status-error)]"
onClick={() => handleRemoveAction(action.id)}
>
-
+
@@ -271,13 +264,13 @@ export const ProjectActionsSection: React.FC
= ({ pr
className="inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-[var(--interactive-border)] text-foreground hover:bg-[var(--interactive-hover)]"
aria-label={t('settings.projects.actions.field.selectIconAria')}
>
-
+
{PROJECT_ACTION_ICONS.map((entry) => {
- const Icon = entry.Icon;
+ const iconName = entry.Icon;
const selected = (action.icon || 'play') === entry.key;
return (
= ({ pr
)}
aria-label={t('settings.projects.actions.field.iconAria', { icon: entry.label })}
>
-
+
);
})}
@@ -364,7 +357,7 @@ export const ProjectActionsSection: React.FC
= ({ pr
/>
-
+
{t('settings.projects.actions.field.overrideUrlTooltip')}
diff --git a/packages/ui/src/components/sections/projects/ProjectsPage.tsx b/packages/ui/src/components/sections/projects/ProjectsPage.tsx
index c8d9584c..ec58255e 100644
--- a/packages/ui/src/components/sections/projects/ProjectsPage.tsx
+++ b/packages/ui/src/components/sections/projects/ProjectsPage.tsx
@@ -7,9 +7,9 @@ import { cn } from '@/lib/utils';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useUIStore } from '@/stores/useUIStore';
import { PROJECT_COLORS, PROJECT_ICONS, PROJECT_COLOR_MAP as COLOR_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
-import { RiCloseLine } from '@remixicon/react';
import { WorktreeSectionContent } from '@/components/sections/openchamber/WorktreeSectionContent';
import { ProjectActionsSection } from '@/components/sections/projects/ProjectActionsSection';
+import { Icon } from "@/components/icon/Icon";
import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useI18n } from '@/lib/i18n';
@@ -298,7 +298,7 @@ export const ProjectsPage: React.FC = () => {
)}
title={t('settings.projects.page.field.none')}
>
-
+
{PROJECT_COLORS.map((c) => (
{
)}
title={t('settings.projects.page.field.none')}
>
-
+
{PROJECT_ICONS.map((i) => {
- const IconComponent = i.Icon;
+ const iconName = i.Icon;
return (
{
)}
title={i.label}
>
-
+
);
})}
@@ -412,7 +412,7 @@ export const ProjectsPage: React.FC = () => {
title={t('settings.projects.page.field.clearBackground')}
disabled={!iconBackground}
>
-
+
)}
diff --git a/packages/ui/src/components/sections/projects/ProjectsSidebar.tsx b/packages/ui/src/components/sections/projects/ProjectsSidebar.tsx
index 35126443..0c0d0b49 100644
--- a/packages/ui/src/components/sections/projects/ProjectsSidebar.tsx
+++ b/packages/ui/src/components/sections/projects/ProjectsSidebar.tsx
@@ -4,9 +4,9 @@ import { useUIStore } from '@/stores/useUIStore';
import { Button } from '@/components/ui/button';
import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout';
import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem';
+import { Icon } from "@/components/icon/Icon";
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { cn } from '@/lib/utils';
-import { RiAddLine, RiFolderLine } from '@remixicon/react';
import { isVSCodeRuntime } from '@/lib/desktop';
import { sessionEvents } from '@/lib/sessionEvents';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -56,7 +56,7 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
onClick={handleAddProject}
aria-label={t('settings.projects.sidebar.actions.addProject')}
>
-
+
)}
@@ -65,7 +65,7 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
>
{projects.map((project) => {
const selected = project.id === selectedId;
- const Icon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
+ const iconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const imageFailureKey = `${project.id}:${project.iconImage?.updatedAt ?? 0}`;
const imageUrl = brokenIconIds.has(imageFailureKey)
? null
@@ -98,12 +98,12 @@ export const ProjectsSidebar: React.FC<{ onItemSelect?: () => void }> = ({ onIte
/>
)
- : Icon
+ : iconName
? (
-
+
)
: (
-
+
);
return (
diff --git a/packages/ui/src/components/sections/providers/ProvidersPage.tsx b/packages/ui/src/components/sections/providers/ProvidersPage.tsx
index b42e9388..2a56dc98 100644
--- a/packages/ui/src/components/sections/providers/ProvidersPage.tsx
+++ b/packages/ui/src/components/sections/providers/ProvidersPage.tsx
@@ -12,8 +12,8 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toast } from '@/components/ui';
-import { RiStackLine, RiToolsLine, RiBrainAi3Line, RiFileImageLine, RiArrowDownSLine, RiCheckLine, RiSearchLine, RiInformationLine, RiEyeLine, RiEyeOffLine } from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import { cn } from '@/lib/utils';
import { copyTextToClipboard } from '@/lib/clipboard';
@@ -511,7 +511,7 @@ export const ProvidersPage: React.FC = () => {
return (
-
+
{t('settings.providers.page.empty.noProvidersDetected')}
{t('settings.providers.page.empty.checkOpenCodeConfiguration')}
@@ -561,7 +561,7 @@ export const ProvidersPage: React.FC = () => {
: t('settings.providers.page.connect.selectProviderPlaceholder')}
-
+
{
className="flex items-center gap-2 border-b border-[var(--surface-subtle)] px-3 py-2"
onKeyDown={(e) => e.stopPropagation()}
>
-
+
{
{provider.name || provider.id}
{candidateProviderId === provider.id && (
-
+
)}
));
@@ -636,7 +636,7 @@ export const ProvidersPage: React.FC = () => {
{t('settings.providers.page.auth.apiKeyLabel')}
-
+
{t('settings.providers.page.auth.apiKeyTooltip')}
@@ -772,7 +772,7 @@ export const ProvidersPage: React.FC = () => {
return (
-
+
{t('settings.providers.page.empty.selectProviderFromSidebar')}
{t('settings.providers.page.empty.reviewDetailsAndConfigureAuth')}
@@ -826,7 +826,7 @@ export const ProvidersPage: React.FC = () => {
{!showAuthPanel ? (
-
+
{t('settings.providers.page.auth.connected')}
{t('settings.providers.page.auth.useReconnectHint')}
@@ -839,7 +839,7 @@ export const ProvidersPage: React.FC = () => {
{t('settings.providers.page.auth.apiKeyLabel')}
-
+
{t('settings.providers.page.auth.apiKeyTooltip')}
@@ -1031,7 +1031,7 @@ export const ProvidersPage: React.FC = () => {
-
+
setModelQuery(event.target.value)}
@@ -1055,10 +1055,10 @@ export const ProvidersPage: React.FC = () => {
const contextTokens = formatTokens(metadata?.limit?.context);
const outputTokens = formatTokens(metadata?.limit?.output);
- const capabilityIcons: Array<{ key: string; icon: typeof RiToolsLine; label: string }> = [];
- if (metadata?.tool_call) capabilityIcons.push({ key: 'tools', icon: RiToolsLine, label: t('settings.providers.page.models.capability.toolCalling') });
- if (metadata?.reasoning) capabilityIcons.push({ key: 'reasoning', icon: RiBrainAi3Line, label: t('settings.providers.page.models.capability.reasoning') });
- if (metadata?.attachment) capabilityIcons.push({ key: 'image', icon: RiFileImageLine, label: t('settings.providers.page.models.capability.imageInput') });
+ const capabilityIcons: Array<{ key: string; icon: string; label: string }> = [];
+ if (metadata?.tool_call) capabilityIcons.push({ key: 'tools', icon: "tools", label: t('settings.providers.page.models.capability.toolCalling') });
+ if (metadata?.reasoning) capabilityIcons.push({ key: 'reasoning', icon: "brain-ai-3", label: t('settings.providers.page.models.capability.reasoning') });
+ if (metadata?.attachment) capabilityIcons.push({ key: 'image', icon: "file-image", label: t('settings.providers.page.models.capability.imageInput') });
return (
@@ -1081,14 +1081,14 @@ export const ProvidersPage: React.FC = () => {
)}
{capabilityIcons.length > 0 && (
- {capabilityIcons.map(({ key, icon: Icon, label }) => (
+ {capabilityIcons.map(({ key, icon: iconName, label }) => (
-
+
))}
@@ -1100,7 +1100,7 @@ export const ProvidersPage: React.FC = () => {
title={isHidden ? t('settings.providers.page.models.actions.showModelInSelectors') : t('settings.providers.page.models.actions.hideModelFromSelectors')}
aria-label={isHidden ? t('settings.providers.page.models.actions.showModel') : t('settings.providers.page.models.actions.hideModel')}
>
- {isHidden ?
:
}
+ {isHidden ?
:
}
diff --git a/packages/ui/src/components/sections/providers/ProvidersSidebar.tsx b/packages/ui/src/components/sections/providers/ProvidersSidebar.tsx
index 219b3a34..59957c7b 100644
--- a/packages/ui/src/components/sections/providers/ProvidersSidebar.tsx
+++ b/packages/ui/src/components/sections/providers/ProvidersSidebar.tsx
@@ -4,9 +4,9 @@ import { ProviderLogo } from '@/components/ui/ProviderLogo';
import { Button } from '@/components/ui/button';
import { useConfigStore } from '@/stores/useConfigStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
-import { RiAddLine, RiStackLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
+import { Icon } from "@/components/icon/Icon";
import { opencodeClient } from '@/lib/opencode/client';
import { useI18n } from '@/lib/i18n';
@@ -122,7 +122,7 @@ export const ProvidersSidebar: React.FC = ({ onItemSelect
aria-label={t('settings.providers.sidebar.actions.connectProviderAria')}
title={t('settings.providers.sidebar.actions.connectProviderTitle')}
>
-
+
@@ -130,7 +130,7 @@ export const ProvidersSidebar: React.FC = ({ onItemSelect
{providers.length === 0 ? (
-
+
{t('settings.providers.sidebar.empty.title')}
{t('settings.providers.sidebar.empty.description')}
diff --git a/packages/ui/src/components/sections/remote-instances/RemoteInstancesPage.tsx b/packages/ui/src/components/sections/remote-instances/RemoteInstancesPage.tsx
index ae0c544b..6a6461d9 100644
--- a/packages/ui/src/components/sections/remote-instances/RemoteInstancesPage.tsx
+++ b/packages/ui/src/components/sections/remote-instances/RemoteInstancesPage.tsx
@@ -10,22 +10,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiArrowRightLine,
- RiComputerLine,
- RiExternalLinkLine,
- RiFileCopyLine,
- RiInformationLine,
- RiPlug2Line,
- RiRefreshLine,
- RiServerLine,
- RiShuffleLine,
- RiTerminalWindowLine,
- RiDeleteBinLine,
- RiStopLine,
-} from '@remixicon/react';
import {
Dialog,
DialogContent,
@@ -39,6 +23,7 @@ import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLay
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { copyTextToClipboard } from '@/lib/clipboard';
import { openExternalUrl } from '@/lib/url';
import { useI18n, type I18nKey } from '@/lib/i18n';
@@ -152,7 +137,7 @@ const HintLabel: React.FC<{ label: string; hint: React.ReactNode }> = ({ label,
{label}
-
+
{hint}
@@ -808,7 +793,7 @@ export const RemoteInstancesPage: React.FC = () => {
onClick={handlePrimaryConnectionAction}
disabled={isPrimaryActionPending || isRetryPending}
>
- {canDisconnect ? : }
+ {canDisconnect ? : }
{primaryButtonLabel}
{
onClick={handleRetryAction}
disabled={!canRetry}
>
-
+
{retryButtonLabel}
{
void handleOpenLogs();
}}
>
-
+
{t('settings.remoteInstances.page.actions.logs')}
{
});
}}
>
-
+
{t('settings.remoteInstances.sidebar.actions.remove')}
@@ -1148,7 +1133,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
-
+
@@ -1265,7 +1250,7 @@ export const RemoteInstancesPage: React.FC = () => {
-
+
{buildForwardLabel(forward)}
{typeLabel}
@@ -1284,7 +1269,7 @@ export const RemoteInstancesPage: React.FC = () => {
}))
}
>
-
+
@@ -1406,27 +1391,27 @@ export const RemoteInstancesPage: React.FC = () => {
{forward.type === 'dynamic' ? (
<>
-
+
{localEndpoint}
{t('settings.remoteInstances.page.preview.localSocks5')}
>
) : forward.type === 'remote' ? (
<>
-
+
{remoteEndpoint}
{t('settings.remoteInstances.page.preview.remote')}
-
-
+
+
{localEndpoint}
{t('settings.remoteInstances.page.preview.local')}
>
) : (
<>
-
+
{localEndpoint}
{t('settings.remoteInstances.page.preview.local')}
-
-
+
+
{remoteEndpoint}
{t('settings.remoteInstances.page.preview.remote')}
>
@@ -1447,7 +1432,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
-
+
{t('settings.remoteInstances.page.actions.openLocal')}
) : null}
@@ -1475,7 +1460,7 @@ export const RemoteInstancesPage: React.FC = () => {
}));
}}
>
-
+
{t('settings.remoteInstances.page.actions.addForward')}
@@ -1540,7 +1525,7 @@ export const RemoteInstancesPage: React.FC = () => {
});
}}
>
-
+
{t('settings.remoteInstances.page.actions.copyLocalUrl')}
{
void handleOpenCurrentInstance();
}}
>
-
+
{t('settings.remoteInstances.page.actions.open')}
>
@@ -1571,11 +1556,11 @@ export const RemoteInstancesPage: React.FC = () => {
-
+
{t('settings.common.actions.copyAll')}
void handleClearLogs()} disabled={logDialogLoading}>
-
+
{t('settings.common.actions.clear')}
diff --git a/packages/ui/src/components/sections/remote-instances/RemoteInstancesSidebar.tsx b/packages/ui/src/components/sections/remote-instances/RemoteInstancesSidebar.tsx
index f0d56766..5abf1034 100644
--- a/packages/ui/src/components/sections/remote-instances/RemoteInstancesSidebar.tsx
+++ b/packages/ui/src/components/sections/remote-instances/RemoteInstancesSidebar.tsx
@@ -1,11 +1,11 @@
import React from 'react';
-import { RiAddLine, RiDeleteBinLine, RiPlug2Line, RiRefreshLine, RiStopLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { SettingsSidebarLayout } from '@/components/sections/shared/SettingsSidebarLayout';
import { SettingsSidebarItem } from '@/components/sections/shared/SettingsSidebarItem';
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import type { DesktopSshInstance } from '@/lib/desktopSsh';
import { useI18n } from '@/lib/i18n';
@@ -145,7 +145,7 @@ export const RemoteInstancesSidebar: React.FC
= ({
onClick={() => void handleAdd()}
aria-label={t('settings.remoteInstances.sidebar.actions.addSshInstance')}
>
-
+
@@ -172,7 +172,7 @@ export const RemoteInstancesSidebar: React.FC = ({
actions={[
{
label: isReady ? t('settings.remoteInstances.sidebar.actions.disconnect') : t('settings.remoteInstances.sidebar.actions.connect'),
- icon: isReady ? RiStopLine : RiPlug2Line,
+ icon: isReady ? 'stop' : 'plug-2',
onClick: () => {
const op = isReady ? disconnect(instance.id) : connectWithPortRecovery(instance);
void op.catch((error) => {
@@ -189,7 +189,7 @@ export const RemoteInstancesSidebar: React.FC = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.retry'),
- icon: RiRefreshLine,
+ icon: "refresh",
onClick: () => {
if (!canRetry) return;
void retry(instance.id).catch((error) => {
@@ -201,7 +201,7 @@ export const RemoteInstancesSidebar: React.FC = ({
},
{
label: t('settings.remoteInstances.sidebar.actions.remove'),
- icon: RiDeleteBinLine,
+ icon: "delete-bin",
destructive: true,
onClick: () => {
void removeInstance(instance.id).then(() => {
diff --git a/packages/ui/src/components/sections/shared/SettingsProjectSelector.tsx b/packages/ui/src/components/sections/shared/SettingsProjectSelector.tsx
index 22c98a16..709d5c50 100644
--- a/packages/ui/src/components/sections/shared/SettingsProjectSelector.tsx
+++ b/packages/ui/src/components/sections/shared/SettingsProjectSelector.tsx
@@ -6,7 +6,7 @@ import {
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { RiArrowDownSLine, RiFolderLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useProjectsStore } from '@/stores/useProjectsStore';
import { isVSCodeRuntime } from '@/lib/desktop';
import { cn } from '@/lib/utils';
@@ -59,9 +59,9 @@ export const SettingsProjectSelector: React.FC<{ className?: string }> = ({ clas
'flex items-center gap-1.5 text-left'
)}
>
-
+
{label}
-
+
diff --git a/packages/ui/src/components/sections/shared/SettingsSidebarHeader.tsx b/packages/ui/src/components/sections/shared/SettingsSidebarHeader.tsx
index 5092ff49..bc4fee1b 100644
--- a/packages/ui/src/components/sections/shared/SettingsSidebarHeader.tsx
+++ b/packages/ui/src/components/sections/shared/SettingsSidebarHeader.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { Button } from '@/components/ui/button';
-import { RiAddLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useDeviceInfo } from '@/lib/device';
import { cn } from '@/lib/utils';
@@ -54,7 +54,7 @@ export const SettingsSidebarHeader: React.FC = ({
onClick={onAdd}
aria-label={addButtonLabel}
>
-
+
)}
diff --git a/packages/ui/src/components/sections/shared/SettingsSidebarItem.tsx b/packages/ui/src/components/sections/shared/SettingsSidebarItem.tsx
index 433a029d..358c16d5 100644
--- a/packages/ui/src/components/sections/shared/SettingsSidebarItem.tsx
+++ b/packages/ui/src/components/sections/shared/SettingsSidebarItem.tsx
@@ -6,14 +6,14 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { RiMore2Line } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
export interface SettingsSidebarItemAction {
/** Label shown in dropdown menu */
label: string;
/** Icon component to show before label */
- icon?: React.ComponentType<{ className?: string }>;
+ icon?: string;
/** Callback when action is clicked */
onClick: () => void;
/** If true, uses destructive styling (red text) */
@@ -47,10 +47,10 @@ interface SettingsSidebarItemProps {
* metadata={agent.description}
* selected={selectedId === agent.id}
* onSelect={() => setSelectedId(agent.id)}
- * icon={ }
+ * icon={<'robot' className="h-4 w-4" />}
* actions={[
- * { label: 'Duplicate', icon: RiFileCopyLine, onClick: handleDuplicate },
- * { label: 'Delete', icon: RiDeleteBinLine, onClick: handleDelete, destructive: true },
+ * { label: 'Duplicate', icon: 'file-copy', onClick: handleDuplicate },
+ * { label: 'Delete', icon: 'delete-bin', onClick: handleDelete, destructive: true },
* ]}
* />
*/
@@ -103,12 +103,12 @@ export const SettingsSidebarItem: React.FC = ({
variant="ghost"
className="h-6 w-6 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
-
+
{actions.map((action) => {
- const Icon = action.icon;
+ const iconName = action.icon;
return (
= ({
action.destructive && 'text-destructive focus:text-destructive'
)}
>
- {Icon && }
+ {iconName && }
{action.label}
);
diff --git a/packages/ui/src/components/sections/shared/SidebarGroup.tsx b/packages/ui/src/components/sections/shared/SidebarGroup.tsx
index 1d14f7db..74409699 100644
--- a/packages/ui/src/components/sections/shared/SidebarGroup.tsx
+++ b/packages/ui/src/components/sections/shared/SidebarGroup.tsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
-import { RiArrowDownSLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
+import { Icon } from "@/components/icon/Icon";
interface SidebarGroupProps {
/** Group display label (e.g. "business", "automation-ai") */
@@ -64,7 +64,7 @@ export const SidebarGroup: React.FC = ({
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
)}
>
- {
return (
-
+
{t('settings.skills.page.empty.title')}
{t('settings.skills.page.empty.description')}
@@ -433,11 +433,11 @@ const SkillsInstalledPage: React.FC = () => {
>
{draftScope === 'user' ? (
-
+
) : (
-
+
)}
- {draftSource === 'agents' ? : null}
+ {draftSource === 'agents' ? : null}
{locationLabelText(locationValueFrom(draftScope, draftSource))}
@@ -445,8 +445,8 @@ const SkillsInstalledPage: React.FC = () => {
- {option.scope === 'user' ? : }
- {option.source === 'agents' ? : null}
+ {option.scope === 'user' ? : }
+ {option.source === 'agents' ? : null}
{locationLabelText(option.value)}
{locationDescriptionText(option.value)}
@@ -501,7 +501,7 @@ const SkillsInstalledPage: React.FC = () => {
{t('settings.skills.page.section.supportingFiles')}
- {t('settings.skills.page.actions.addFile')}
+ {t('settings.skills.page.actions.addFile')}
@@ -525,7 +525,7 @@ const SkillsInstalledPage: React.FC = () => {
className="flex items-center gap-2 py-1.5 cursor-pointer group"
onClick={() => handleEditFile(file.path)}
>
-
+
{file.path}
{isNewSkill && (
@@ -540,7 +540,7 @@ const SkillsInstalledPage: React.FC = () => {
handleDeleteFile(file.path);
}}
>
-
+
))}
diff --git a/packages/ui/src/components/sections/skills/SkillsSidebar.tsx b/packages/ui/src/components/sections/skills/SkillsSidebar.tsx
index b2e334e0..48fd71ce 100644
--- a/packages/ui/src/components/sections/skills/SkillsSidebar.tsx
+++ b/packages/ui/src/components/sections/skills/SkillsSidebar.tsx
@@ -17,13 +17,13 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
-import { RiAddLine, RiDeleteBinLine, RiFileCopyLine, RiMore2Line, RiEditLine, RiBookOpenLine } from '@remixicon/react';
import { useSkillsStore, type DiscoveredSkill } from '@/stores/useSkillsStore';
import { useShallow } from 'zustand/react/shallow';
import { cn } from '@/lib/utils';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SettingsProjectSelector } from '@/components/sections/shared/SettingsProjectSelector';
import { SidebarGroup } from '@/components/sections/shared/SidebarGroup';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface SkillsSidebarProps {
@@ -75,7 +75,6 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) =>
setSelectedSkill(newName);
onItemSelect?.();
-
};
const handleDeleteSkill = async (skill: DiscoveredSkill) => {
@@ -125,7 +124,6 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) =>
});
setSelectedSkill(newName);
-
};
const handleOpenRenameDialog = (skill: DiscoveredSkill) => {
@@ -226,7 +224,7 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) =>
className="h-7 w-7 px-0 -my-1 text-muted-foreground"
onClick={handleCreateNew}
>
-
+
@@ -234,7 +232,7 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) =>
{skills.length === 0 ? (
-
+
{t('settings.skills.sidebar.empty.title')}
{t('settings.skills.sidebar.empty.description')}
@@ -482,7 +480,7 @@ const SkillListItem: React.FC = ({
variant="ghost"
className="h-6 w-6 px-0 flex-shrink-0 -mr-1 opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"
>
-
+
@@ -492,7 +490,7 @@ const SkillListItem: React.FC = ({
onRename();
}}
>
-
+
{t('settings.common.actions.rename')}
@@ -502,7 +500,7 @@ const SkillListItem: React.FC = ({
onDuplicate();
}}
>
-
+
{t('settings.common.actions.duplicate')}
@@ -513,7 +511,7 @@ const SkillListItem: React.FC = ({
}}
className="text-destructive focus:text-destructive"
>
-
+
{t('settings.common.actions.delete')}
diff --git a/packages/ui/src/components/sections/skills/catalog/AddCatalogDialog.tsx b/packages/ui/src/components/sections/skills/catalog/AddCatalogDialog.tsx
index 2c2c0c88..9f8ba939 100644
--- a/packages/ui/src/components/sections/skills/catalog/AddCatalogDialog.tsx
+++ b/packages/ui/src/components/sections/skills/catalog/AddCatalogDialog.tsx
@@ -17,8 +17,7 @@ import {
SelectItem,
SelectTrigger,
} from '@/components/ui/select';
-
-import { RiGitRepositoryLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
import { isVSCodeRuntime } from '@/lib/desktop';
@@ -354,7 +353,7 @@ export const AddCatalogDialog: React.FC = ({ open, onOpen
onClick={() => void handleScan()}
disabled={isScanning || !source.trim()}
>
-
+
{isScanning ? t('settings.skills.catalog.shared.actions.scanning') : t('settings.skills.catalog.shared.actions.scan')}
= ({ op
disabled={isScanning || !source.trim()}
className="gap-2"
>
-
+
{isScanning ? t('settings.skills.catalog.shared.actions.scanning') : t('settings.skills.catalog.shared.actions.scan')}
@@ -401,8 +401,8 @@ export const InstallFromRepoDialog: React.FC = ({ op
}}
>
- {scope === 'user' ? : }
- {targetSource === 'agents' ? : null}
+ {scope === 'user' ? : }
+ {targetSource === 'agents' ? : null}
{locationLabelText(locationValueFrom(scope, targetSource))}
@@ -410,8 +410,8 @@ export const InstallFromRepoDialog: React.FC = ({ op
- {option.scope === 'user' ? : }
- {option.source === 'agents' ? : null}
+ {option.scope === 'user' ? : }
+ {option.source === 'agents' ? : null}
{locationLabelText(option.value)}
{locationDescriptionText(option.value)}
diff --git a/packages/ui/src/components/sections/skills/catalog/InstallSkillDialog.tsx b/packages/ui/src/components/sections/skills/catalog/InstallSkillDialog.tsx
index fbfca117..60b7a0cf 100644
--- a/packages/ui/src/components/sections/skills/catalog/InstallSkillDialog.tsx
+++ b/packages/ui/src/components/sections/skills/catalog/InstallSkillDialog.tsx
@@ -17,7 +17,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
-import { RiFolderLine, RiRobot2Line, RiUser3Line } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import type { SkillsCatalogItem } from '@/lib/api/types';
@@ -208,8 +208,8 @@ export const InstallSkillDialog: React.FC
= ({ open, on
}}
>
- {scope === 'user' ? : }
- {targetSource === 'agents' ? : null}
+ {scope === 'user' ? : }
+ {targetSource === 'agents' ? : null}
{locationLabelText(locationValueFrom(scope, targetSource))}
@@ -217,8 +217,8 @@ export const InstallSkillDialog: React.FC = ({ open, on
- {option.scope === 'user' ? : }
- {option.source === 'agents' ? : null}
+ {option.scope === 'user' ? : }
+ {option.source === 'agents' ? : null}
{locationLabelText(option.value)}
{locationDescriptionText(option.value)}
diff --git a/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx b/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx
index 80dbc207..01ce60fd 100644
--- a/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx
+++ b/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx
@@ -19,8 +19,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
-
-import { RiAddLine, RiDeleteBinLine, RiRefreshLine, RiDownloadLine, RiStarLine, RiSearchLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useSkillsCatalogStore } from '@/stores/useSkillsCatalogStore';
import { useShallow } from 'zustand/react/shallow';
@@ -225,7 +224,7 @@ export const SkillsCatalogPage: React.FC
= ({ mode, onMo
disabled={isLoadingCatalog || isLoadingSource}
title={t('settings.skills.catalog.page.actions.refreshTitle')}
>
-
+
{isCustomSource && (
@@ -237,7 +236,7 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo
disabled={isRemovingCatalog}
title={t('settings.skills.catalog.page.actions.removeCatalogTitle')}
>
-
+
)}
@@ -246,13 +245,13 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo
className="!font-normal gap-1"
onClick={() => setAddCatalogOpen(true)}
>
- {t('settings.skills.catalog.page.actions.addCatalog')}
+ {t('settings.skills.catalog.page.actions.addCatalog')}
-
+
setSearch(e.target.value)}
@@ -287,7 +286,7 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo
) : isLoadingSource ? (
-
+
{t('settings.skills.catalog.page.loading.skills')}
) : (
@@ -326,12 +325,12 @@ export const SkillsCatalogPage: React.FC
= ({ mode, onMo
{t('settings.skills.catalog.page.byOwnerPrefix')} {item.clawdhub.owner}
)}
-
+
{item.clawdhub.downloads?.toLocaleString() ?? 0}
{(item.clawdhub.stars ?? 0) > 0 && (
-
+
{item.clawdhub.stars}
)}
diff --git a/packages/ui/src/components/sections/usage/UsagePage.tsx b/packages/ui/src/components/sections/usage/UsagePage.tsx
index fb0222f6..446cbd92 100644
--- a/packages/ui/src/components/sections/usage/UsagePage.tsx
+++ b/packages/ui/src/components/sections/usage/UsagePage.tsx
@@ -11,10 +11,10 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
-import { RiArrowDownSLine, RiArrowRightSLine, RiInformationLine } from '@remixicon/react';
import type { UsageWindows, QuotaProviderId } from '@/types';
import { getAllModelFamilies, getDisplayModelName, sortModelFamilies, groupModelsByFamilyWithGetter } from '@/lib/quota/model-families';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
const formatTime = (timestamp: number | null) => {
@@ -57,7 +57,6 @@ export const UsagePage: React.FC = () => {
void fetchAllQuotas();
}, [loadSettings, fetchAllQuotas]);
-
React.useEffect(() => {
if (selectedProviderId) {
return;
@@ -194,7 +193,7 @@ export const UsagePage: React.FC = () => {
{t('settings.usage.page.options.showInHeader')}
-
+
{t('settings.usage.page.options.showInHeaderTooltip')}
@@ -269,9 +268,9 @@ export const UsagePage: React.FC = () => {
{isCollapsed ? (
-
+
) : (
-
+
)}
@@ -322,9 +321,9 @@ export const UsagePage: React.FC = () => {
{isCollapsed ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/sections/usage/UsageSidebar.tsx b/packages/ui/src/components/sections/usage/UsageSidebar.tsx
index 7852c85b..f2d9d86b 100644
--- a/packages/ui/src/components/sections/usage/UsageSidebar.tsx
+++ b/packages/ui/src/components/sections/usage/UsageSidebar.tsx
@@ -5,11 +5,11 @@ import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { QUOTA_PROVIDERS, resolveUsageTone } from '@/lib/quota';
import { useQuotaStore } from '@/stores/useQuotaStore';
import { updateDesktopSettings } from '@/lib/persistence';
-import { RiRefreshLine } from '@remixicon/react';
import { useI18n } from '@/lib/i18n';
interface UsageSidebarProps {
@@ -121,7 +121,7 @@ export const UsageSidebar: React.FC = ({ onItemSelect }) => {
title={t('settings.usage.sidebar.actions.refreshTitle')}
disabled={isLoading}
>
-
+
diff --git a/packages/ui/src/components/session/BranchPickerDialog.tsx b/packages/ui/src/components/session/BranchPickerDialog.tsx
index 385350b2..ba9b0684 100644
--- a/packages/ui/src/components/session/BranchPickerDialog.tsx
+++ b/packages/ui/src/components/session/BranchPickerDialog.tsx
@@ -9,16 +9,7 @@ import {
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from '@/components/ui';
-import {
- RiCheckLine,
- RiCloseLine,
- RiDeleteBinLine,
- RiGitBranchLine,
- RiLoader4Line,
- RiPencilLine,
- RiSearchLine,
- RiSplitCellsHorizontal,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { deleteGitBranch, getGitBranches, git, renameBranch } from '@/lib/gitApi';
import type { GitBranch, GitWorktreeInfo } from '@/lib/api/types';
@@ -304,7 +295,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
-
+
{t('branchPickerDialog.title')}
@@ -313,7 +304,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
-
+
-
+
@@ -445,9 +436,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
aria-label={t('branchPickerDialog.actions.createWorktreeAria')}
>
{isCreatingWorktree ? (
-
+
) : (
-
+
)}
@@ -465,7 +456,7 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
className="inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-interactive-hover/40 text-muted-foreground hover:text-foreground transition-colors disabled:opacity-50"
aria-label={t('branchPickerDialog.actions.renameAria')}
>
-
+
@@ -489,9 +480,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
aria-label={hasAttachedWorktree ? t('branchPickerDialog.actions.deleteWorktreeAria') : t('branchPickerDialog.actions.deleteAria')}
>
{isDeleting ? (
-
+
) : (
-
+
)}
@@ -520,9 +511,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
aria-label={t('branchPickerDialog.actions.confirmRenameAria')}
>
{isRenaming ? (
-
+
) : (
-
+
)}
-
+
) : null}
@@ -557,9 +548,9 @@ export function BranchPickerDialog({ open, onOpenChange, project }: BranchPicker
aria-label={t('branchPickerDialog.actions.confirmDeleteAria')}
>
{isDeleting ? (
-
+
) : (
-
+
)}
-
+
) : null}
diff --git a/packages/ui/src/components/session/DirectoryAutocomplete.tsx b/packages/ui/src/components/session/DirectoryAutocomplete.tsx
index 48d146fa..348f09a0 100644
--- a/packages/ui/src/components/session/DirectoryAutocomplete.tsx
+++ b/packages/ui/src/components/session/DirectoryAutocomplete.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiFolderLine, RiRefreshLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { opencodeClient, type FilesystemEntry } from '@/lib/opencode/client';
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
+import { Icon } from "@/components/icon/Icon";
interface DirectoryAutocompleteProps {
inputValue: string;
@@ -280,7 +280,7 @@ export const DirectoryAutocomplete = React.forwardRef
{loading ? (
-
+
) : (
@@ -297,7 +297,7 @@ export const DirectoryAutocomplete = React.forwardRef { handleSelectSuggestion(entry); onClose(); }}
onMouseEnter={() => setSelectedIndex(index)}
>
-
+
{entry.name}
);
diff --git a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx
index 14e04fae..73cf26c1 100644
--- a/packages/ui/src/components/session/DirectoryExplorerDialog.tsx
+++ b/packages/ui/src/components/session/DirectoryExplorerDialog.tsx
@@ -16,18 +16,9 @@ import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
import { cn } from '@/lib/utils';
import { toast } from '@/components/ui';
import { IdentityDropdown } from '@/components/views/git/GitHeader';
-import {
- RiArrowDownSLine,
- RiArrowLeftSLine,
- RiArrowUpSLine,
- RiCheckboxBlankLine,
- RiCheckboxLine,
- RiCornerDownLeftLine,
- RiFolder6Line,
- RiFolderAddLine,
-} from '@remixicon/react';
import { useDeviceInfo } from '@/lib/device';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import { opencodeClient } from '@/lib/opencode/client';
import {
setDirectoryShowHidden,
@@ -523,7 +514,7 @@ export const DirectoryExplorerDialog: React.FC = (
onClick={() => setDirectoryShowHidden(!showHidden)}
className="flex flex-shrink-0 items-center gap-2 rounded-lg px-2 py-1 typography-meta text-muted-foreground transition-colors hover:bg-interactive-hover/40"
>
- {showHidden ? : }
+ {showHidden ? : }
{t('directoryExplorerDialog.toggle.showHidden')}
);
@@ -552,7 +543,7 @@ export const DirectoryExplorerDialog: React.FC = (
) : null}
-
+
= (
)}
>
{row.type === 'up' ? (
-
+
) : (
-
+
)}
{row.name}
@@ -657,17 +648,17 @@ export const DirectoryExplorerDialog: React.FC = (
const footerHints = (
-
-
+
+
{t('directoryExplorerDialog.footer.navigate')}
-
+
{t('directoryExplorerDialog.footer.select')}
{submitModifierLabel}
-
+
{t('directoryExplorerDialog.footer.add')}
diff --git a/packages/ui/src/components/session/DirectoryTree.tsx b/packages/ui/src/components/session/DirectoryTree.tsx
index 19bd1d61..5c78b43e 100644
--- a/packages/ui/src/components/session/DirectoryTree.tsx
+++ b/packages/ui/src/components/session/DirectoryTree.tsx
@@ -8,7 +8,6 @@ import {
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu';
-import { RiAddLine, RiArrowDownSLine, RiArrowRightSLine, RiCheckLine, RiCloseLine, RiFolder6Line, RiPushpin2Line, RiPushpinLine } from '@remixicon/react';
import { cn, formatPathForDisplay } from '@/lib/utils';
import { opencodeClient } from '@/lib/opencode/client';
import { useDeviceInfo } from '@/lib/device';
@@ -16,6 +15,7 @@ import type { DesktopSettings } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence';
import { useFileSystemAccess } from '@/hooks/useFileSystemAccess';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface DirectoryItem {
@@ -695,9 +695,9 @@ export const DirectoryTree: React.FC = ({
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
>
{isExpanded ? (
-
+
) : (
-
+
)}
)}
@@ -728,7 +728,7 @@ export const DirectoryTree: React.FC = ({
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
)}
>
- = ({
)}
title={t('directoryTree.actions.createNewDirectory')}
>
-
+
= ({
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
>
{isPinned ? (
-
+
) : (
-
+
)}
>
@@ -809,7 +809,7 @@ export const DirectoryTree: React.FC = ({
style={{ paddingLeft: `${(level + 1) * 14 + 6}px` }}
>
-
+
= ({
className="p-1 hover:bg-interactive-hover rounded"
title={t('directoryTree.actions.createDirectory')}
>
-
+
{
@@ -849,7 +849,7 @@ export const DirectoryTree: React.FC = ({
className="p-1 hover:bg-interactive-hover rounded"
title={t('directoryTree.actions.cancel')}
>
-
+
)}
@@ -881,9 +881,9 @@ export const DirectoryTree: React.FC = ({
className="p-0.5 hover:bg-interactive-hover rounded"
>
{isExpanded ? (
-
+
) : (
-
+
)}
)}
@@ -898,7 +898,7 @@ export const DirectoryTree: React.FC = ({
style={{ paddingLeft: `${(level + 1) * 12 + 8}px` }}
>
-
+
= ({
className="p-1 hover:bg-interactive-hover rounded"
title={t('directoryTree.actions.createDirectory')}
>
-
+
{
@@ -938,7 +938,7 @@ export const DirectoryTree: React.FC = ({
className="p-1 hover:bg-interactive-hover rounded"
title={t('directoryTree.actions.cancel')}
>
-
+
)}
@@ -968,9 +968,9 @@ export const DirectoryTree: React.FC = ({
className={cn("hover:bg-interactive-hover rounded", isMobile ? "p-0.5" : "p-0.5")}
>
{isExpanded ? (
-
+
) : (
-
+
)}
)}
@@ -996,7 +996,7 @@ export const DirectoryTree: React.FC = ({
isInlineVariant ? (isSelected ? 'text-primary' : 'text-foreground') : 'text-foreground'
)}
>
- = ({
title={isPinned ? t('directoryTree.actions.unpinDirectory') : t('directoryTree.actions.pinDirectory')}
>
{isPinned ? (
-
+
) : (
-
+
)}
>
@@ -1105,9 +1105,9 @@ export const DirectoryTree: React.FC = ({
className={cn("hover:bg-interactive-hover rounded flex-shrink-0", isMobile ? "p-0.5" : "p-0.5")}
>
{isExpanded ? (
-
+
) : (
-
+
)}
@@ -1124,7 +1124,7 @@ export const DirectoryTree: React.FC = ({
isSelected ? 'text-primary' : 'text-foreground'
)}
>
- = ({
)}
title={t('directoryTree.actions.unpinDirectory')}
>
-
+
{isExpanded && children && children.map((child) => renderPinnedTreeItem(child, 1))}
@@ -1183,12 +1183,12 @@ export const DirectoryTree: React.FC = ({
className="p-0.5 hover:bg-interactive-hover rounded flex-shrink-0"
>
{isExpanded ? (
-
+
) : (
-
+
)}
-
+
{name}
@@ -1207,7 +1207,7 @@ export const DirectoryTree: React.FC
= ({
)}
title={t('directoryTree.actions.unpinDirectory')}
>
-
+
{isExpanded && children && (
@@ -1238,9 +1238,9 @@ export const DirectoryTree: React.FC = ({
)}
>
{isPinnedExpanded ? (
-
+
) : (
-
+
)}
{t('directoryTree.section.pinned')}
@@ -1302,12 +1302,12 @@ export const DirectoryTree: React.FC = ({
aria-label={t('directoryTree.actions.selectWorkingDirectoryAria')}
>
-
+
{formatPathForDisplay(currentPath, homeDirectory)}
-
+
diff --git a/packages/ui/src/components/session/GitHubIntegrationDialog.tsx b/packages/ui/src/components/session/GitHubIntegrationDialog.tsx
index e80b537a..8a4e3004 100644
--- a/packages/ui/src/components/session/GitHubIntegrationDialog.tsx
+++ b/packages/ui/src/components/session/GitHubIntegrationDialog.tsx
@@ -9,16 +9,6 @@ import {
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
-import {
- RiGithubLine,
- RiLoader4Line,
- RiSearchLine,
- RiErrorWarningLine,
- RiCheckLine,
- RiGitPullRequestLine,
- RiGitBranchLine,
- RiCloseLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useProjectsStore } from '@/stores/useProjectsStore';
@@ -27,6 +17,7 @@ import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
import { validateWorktreeCreate } from '@/lib/worktrees/worktreeManager';
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import type {
GitHubIssue,
GitHubIssueSummary,
@@ -299,7 +290,7 @@ export function GitHubIntegrationDialog({
<>
{!isGitHubConnected ? (
-
+
{t('session.githubIntegration.connect.title')}
@@ -312,7 +303,7 @@ export function GitHubIntegrationDialog({
<>
{/* Search */}
-
+
setSearchQuery(e.target.value)}
@@ -329,7 +320,7 @@ export function GitHubIntegrationDialog({
{/* Loading */}
{loading && (
-
+
)}
@@ -337,7 +328,7 @@ export function GitHubIntegrationDialog({
{error && (
@@ -391,7 +382,7 @@ export function GitHubIntegrationDialog({
)}
{loadingMore && (
-
+
)}
@@ -463,7 +454,7 @@ export function GitHubIntegrationDialog({
)}
{loadingMore && (
-
+
)}
@@ -489,7 +480,7 @@ export function GitHubIntegrationDialog({
{/* Selected Issue/PR display - hidden on mobile (shown in header instead) */}
{!isMobile && (selectedIssue || selectedPr) && (
-
+
{selectedIssue
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
@@ -499,7 +490,7 @@ export function GitHubIntegrationDialog({
onClick={handleClear}
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
>
-
+
)}
@@ -562,8 +553,8 @@ export function GitHubIntegrationDialog({
},
- { id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon:
},
+ { id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon:
},
+ { id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon:
},
]}
activeId={activeTab}
onSelect={(id) => {
@@ -578,7 +569,7 @@ export function GitHubIntegrationDialog({
{/* Selected Item Inline Display */}
{(selectedIssue || selectedPr) && (
-
+
{selectedIssue
? t('session.githubIntegration.selected.issueNumber', { number: selectedIssue.number })
@@ -588,7 +579,7 @@ export function GitHubIntegrationDialog({
onClick={handleClear}
className="text-muted-foreground hover:text-foreground shrink-0 p-0.5 rounded hover:bg-muted transition-colors"
>
-
+
)}
@@ -603,7 +594,7 @@ export function GitHubIntegrationDialog({
-
+
{t('session.githubIntegration.title')}
@@ -611,8 +602,8 @@ export function GitHubIntegrationDialog({
},
- { id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon:
},
+ { id: 'issues', label: t('session.githubIntegration.tabs.issues'), icon:
},
+ { id: 'prs', label: t('session.githubIntegration.tabs.pullRequests'), icon:
},
]}
activeId={activeTab}
onSelect={(id) => {
diff --git a/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx b/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx
index 4115e50e..e60e76fa 100644
--- a/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx
+++ b/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx
@@ -10,14 +10,7 @@ import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { toast } from '@/components/ui';
-import {
- RiCheckboxBlankLine,
- RiCheckboxLine,
- RiExternalLinkLine,
- RiGithubLine,
- RiLoader4Line,
- RiSearchLine,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useProjectsStore } from '@/stores/useProjectsStore';
@@ -499,7 +492,7 @@ export function GitHubIssuePickerDialog({
const content = (
<>
-
+
-
+
{t('session.githubIssuePicker.loading.issues')}
) : null}
@@ -553,7 +546,7 @@ export function GitHubIssuePickerDialog({
{startingIssueNumber === directNumber ? (
-
+
) : null}
@@ -588,7 +581,7 @@ export function GitHubIssuePickerDialog({
@@ -621,7 +614,7 @@ export function GitHubIssuePickerDialog({
>
{isLoadingMore ? (
-
+
{t('session.githubIssuePicker.loading.more')}
) : (
@@ -660,9 +653,9 @@ export function GitHubIssuePickerDialog({
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
{createInWorktree ? (
-
+
) : (
-
+
)}
{t('session.githubIssuePicker.actions.createInWorktree')}
@@ -673,7 +666,7 @@ export function GitHubIssuePickerDialog({
{repoUrl ? (
-
+
{t('session.githubIssuePicker.actions.openRepo')}
@@ -714,7 +707,7 @@ export function GitHubIssuePickerDialog({
-
+
{title}
diff --git a/packages/ui/src/components/session/GitHubPrPickerDialog.tsx b/packages/ui/src/components/session/GitHubPrPickerDialog.tsx
index f9792709..5bbe57bc 100644
--- a/packages/ui/src/components/session/GitHubPrPickerDialog.tsx
+++ b/packages/ui/src/components/session/GitHubPrPickerDialog.tsx
@@ -11,12 +11,7 @@ import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { toast } from '@/components/ui';
-import {
- RiGithubLine,
- RiLoader4Line,
- RiSearchLine,
- RiExternalLinkLine,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useProjectsStore } from '@/stores/useProjectsStore';
@@ -269,7 +264,7 @@ export function GitHubPrPickerDialog({
<>
-
+
-
+
{t('session.githubPrPicker.loading.pullRequests')}
) : null}
@@ -340,7 +335,7 @@ export function GitHubPrPickerDialog({
{loadingPrNumber === directNumber ? (
-
+
) : null}
@@ -374,7 +369,7 @@ export function GitHubPrPickerDialog({
@@ -407,7 +402,7 @@ export function GitHubPrPickerDialog({
>
{isLoadingMore ? (
-
+
{t('session.githubPrPicker.loading.more')}
) : (
@@ -446,7 +441,7 @@ export function GitHubPrPickerDialog({
-
+
{title}
diff --git a/packages/ui/src/components/session/NewWorktreeDialog.tsx b/packages/ui/src/components/session/NewWorktreeDialog.tsx
index 15bff4f4..7c4a0b88 100644
--- a/packages/ui/src/components/session/NewWorktreeDialog.tsx
+++ b/packages/ui/src/components/session/NewWorktreeDialog.tsx
@@ -23,18 +23,6 @@ import {
CommandList,
CommandSeparator,
} from '@/components/ui/command';
-import {
- RiGitBranchLine,
- RiGitRepositoryLine,
- RiGithubLine,
- RiLoader4Line,
- RiRefreshLine,
- RiErrorWarningLine,
- RiCheckLine,
- RiExternalLinkLine,
- RiCloseLine,
- RiArrowDownSLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
@@ -58,6 +46,7 @@ import { useGitBranches, useGitStore, useGitLoadingBranches } from '@/stores/use
import { GitHubIntegrationDialog } from './GitHubIntegrationDialog';
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import type {
GitHubIssue,
GitHubIssueComment,
@@ -1037,7 +1026,7 @@ export function NewWorktreeDialog({
{validation.touched && (validation.branchError || validation.worktreeError) && (
<>
-
+
{validation.branchError || validation.worktreeError}
@@ -1062,7 +1051,7 @@ export function NewWorktreeDialog({
disabled={!canCreate || isCreating}
className={cn('gap-1.5', isMobile && 'flex-1')}
>
- {isCreating && }
+ {isCreating && }
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
@@ -1082,8 +1071,8 @@ export function NewWorktreeDialog({
},
- { id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: },
+ { id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: },
+ { id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: },
]}
activeId={mode}
onSelect={(id) => handleModeChange(id as Mode)}
@@ -1110,7 +1099,7 @@ export function NewWorktreeDialog({
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
-
+
- {isLoadingBranches ? : }
+ {isLoadingBranches ? : }
@@ -1266,7 +1255,7 @@ export function NewWorktreeDialog({
onClick={() => setGithubDialogOpen(true)}
className="gap-1.5 h-7"
>
-
+
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
)}
@@ -1293,7 +1282,7 @@ export function NewWorktreeDialog({
/>
{newBranchState.linkedPr && (
-
+
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
@@ -1301,7 +1290,7 @@ export function NewWorktreeDialog({
)}
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
-
+
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
@@ -1335,7 +1324,7 @@ export function NewWorktreeDialog({
)}
title={t('session.newWorktree.resetToMatchBranchName')}
>
-
+
{t('session.newWorktree.actions.reset')}
)}
@@ -1380,7 +1369,7 @@ export function NewWorktreeDialog({
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
-
+
{newBranchState.sourceBranch && (
@@ -1509,7 +1498,7 @@ export function NewWorktreeDialog({
{/* Row 1: Type, number, title, actions */}
-
+
{newBranchState.linkedIssue && (
@@ -1533,14 +1522,14 @@ export function NewWorktreeDialog({
className="text-muted-foreground hover:text-foreground shrink-0"
onClick={(e) => e.stopPropagation()}
>
-
+
-
+
@@ -1567,7 +1556,7 @@ export function NewWorktreeDialog({
-
+
{t('session.newWorktree.title')}
@@ -1575,8 +1564,8 @@ export function NewWorktreeDialog({
},
- { id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: },
+ { id: 'new-branch', label: t('session.newWorktree.mode.newBranch'), icon: },
+ { id: 'existing-branch', label: t('session.newWorktree.mode.existingBranch'), icon: },
]}
activeId={mode}
onSelect={(id) => handleModeChange(id as Mode)}
@@ -1602,7 +1591,7 @@ export function NewWorktreeDialog({
{existingBranchState.selectedBranch || t('session.newWorktree.chooseBranch')}
-
+
@@ -1714,7 +1703,7 @@ export function NewWorktreeDialog({
disabled={!canFetchBranches || isLoadingBranches}
title={t('session.newWorktree.fetchBranches')}
>
- {isLoadingBranches ? : }
+ {isLoadingBranches ? : }
@@ -1731,7 +1720,7 @@ export function NewWorktreeDialog({
onClick={() => setGithubDialogOpen(true)}
className="gap-1.5 h-7"
>
-
+
{newBranchState.linkedIssue || newBranchState.linkedPr ? t('session.newWorktree.actions.change') : t('session.newWorktree.actions.startFromGitHubIssuePr')}
)}
@@ -1758,7 +1747,7 @@ export function NewWorktreeDialog({
/>
{newBranchState.linkedPr && (
-
+
{t('session.newWorktree.usingPrBranch', { branch: newBranchState.linkedPr.head })}
@@ -1766,7 +1755,7 @@ export function NewWorktreeDialog({
)}
{newBranchState.linkedIssue && !newBranchState.linkedPr && (
-
+
{t('session.newWorktree.fromIssue', { number: newBranchState.linkedIssue.number, title: newBranchState.linkedIssue.title })}
@@ -1800,7 +1789,7 @@ export function NewWorktreeDialog({
)}
title={t('session.newWorktree.resetToMatchBranchName')}
>
-
+
{t('session.newWorktree.actions.reset')}
)}
@@ -1842,7 +1831,7 @@ export function NewWorktreeDialog({
{newBranchState.sourceBranch || t('session.newWorktree.selectSourceBranchPlaceholder')}
-
+
@@ -1944,7 +1933,7 @@ export function NewWorktreeDialog({
{/* Row 1: Type, number, title, actions */}
-
+
{newBranchState.linkedIssue && (
@@ -1968,14 +1957,14 @@ export function NewWorktreeDialog({
className="text-muted-foreground hover:text-foreground shrink-0"
onClick={(e) => e.stopPropagation()}
>
-
+
-
+
@@ -2002,7 +1991,7 @@ export function NewWorktreeDialog({
{validation.touched && (validation.branchError || validation.worktreeError) && (
<>
-
+
{validation.branchError || validation.worktreeError}
@@ -2025,7 +2014,7 @@ export function NewWorktreeDialog({
disabled={!canCreate || isCreating}
className="gap-1.5"
>
- {isCreating && }
+ {isCreating && }
{isCreating ? t('session.newWorktree.actions.creating') : t('session.newWorktree.actions.createWorktree')}
diff --git a/packages/ui/src/components/session/ProjectNotesTodoPanel.tsx b/packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
index 13ba89a7..7d7f3837 100644
--- a/packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
+++ b/packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiAddLine, RiDeleteBinLine, RiSendPlaneLine } from '@remixicon/react';
import { toast } from '@/components/ui';
import { Checkbox } from '@/components/ui/checkbox';
import {
@@ -10,6 +9,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
+import { Icon } from "@/components/icon/Icon";
import {
deleteProjectPlanFile,
getProjectContextData,
@@ -575,7 +575,7 @@ export const ProjectNotesTodoPanel: React.FC
= ({
aria-label={t('rightSidebar.contextNotesTodo.todo.addAria')}
title={t('rightSidebar.contextNotesTodo.todo.addAria')}
>
-
+
@@ -623,7 +623,7 @@ export const ProjectNotesTodoPanel: React.FC = ({
aria-label={t('rightSidebar.contextNotesTodo.todo.actions.delete', { text: todo.text })}
title={t('rightSidebar.contextNotesTodo.todo.actions.delete', { text: todo.text })}
>
-
+
@@ -634,7 +634,7 @@ export const ProjectNotesTodoPanel: React.FC = ({
aria-label={t('rightSidebar.contextNotesTodo.todo.actions.send', { text: todo.text })}
title={t('rightSidebar.contextNotesTodo.todo.actions.send', { text: todo.text })}
>
-
+
@@ -692,7 +692,7 @@ export const ProjectNotesTodoPanel: React.FC = ({
aria-label={t('rightSidebar.contextNotesTodo.plans.importFromFile')}
title={t('rightSidebar.contextNotesTodo.plans.importFromFile')}
>
-
+
@@ -723,7 +723,7 @@ export const ProjectNotesTodoPanel: React.FC
= ({
title={t('rightSidebar.contextNotesTodo.plans.deletePlan')}
aria-label={t('rightSidebar.contextNotesTodo.plans.deletePlanWithTitle', { title: plan.title })}
>
-
+
))}
diff --git a/packages/ui/src/components/session/ScheduledTaskEditorDialog.tsx b/packages/ui/src/components/session/ScheduledTaskEditorDialog.tsx
index 69aaf2eb..29ea5045 100644
--- a/packages/ui/src/components/session/ScheduledTaskEditorDialog.tsx
+++ b/packages/ui/src/components/session/ScheduledTaskEditorDialog.tsx
@@ -9,12 +9,12 @@ import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { toast } from '@/components/ui';
-import { RiAddLine, RiCloseLine, RiCalendarLine, RiArrowLeftSLine, RiArrowRightSLine, RiArrowDownSLine } from '@remixicon/react';
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
+import { Icon } from "@/components/icon/Icon";
import { useConfigStore } from '@/stores/useConfigStore';
import { useUIStore } from '@/stores/useUIStore';
import type { ScheduledTask } from '@/lib/scheduledTasksApi';
@@ -700,7 +700,6 @@ export function ScheduledTaskEditorDialog(props: {
};
}, [isDatePickerOpen]);
-
const variantOptions = React.useMemo(() => {
const provider = providers.find((item) => item.id === draft.execution.providerID);
const model = provider?.models?.find((item) => item.id === draft.execution.modelID) as { variants?: Record } | undefined;
@@ -1066,10 +1065,10 @@ export function ScheduledTaskEditorDialog(props: {
onClick={() => setIsDatePickerOpen((prev) => !prev)}
>
-
+
{formatDateLabel(draft.schedule.onceDate, t('sessions.scheduledTasks.editor.date.placeholder'), locale)}
-
+
{isDatePickerOpen ? (
@@ -1082,7 +1081,7 @@ export function ScheduledTaskEditorDialog(props: {
aria-label={t('sessions.scheduledTasks.editor.date.previousMonth')}
disabled={isAtCurrentMonth}
>
-
+
{new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(calendarMonth)}
@@ -1093,7 +1092,7 @@ export function ScheduledTaskEditorDialog(props: {
onClick={() => setCalendarMonth((prev) => shiftMonth(prev, 1))}
aria-label={t('sessions.scheduledTasks.editor.date.nextMonth')}
>
-
+
@@ -1255,7 +1254,7 @@ export function ScheduledTaskEditorDialog(props: {
onClick={() => removeTimeAt(index)}
aria-label={t('sessions.scheduledTasks.editor.times.removeAria')}
>
-
+
) : null}
@@ -1263,7 +1262,7 @@ export function ScheduledTaskEditorDialog(props: {
- {t('sessions.scheduledTasks.editor.times.add')}
+ {t('sessions.scheduledTasks.editor.times.add')}
diff --git a/packages/ui/src/components/session/ScheduledTasksDialog.tsx b/packages/ui/src/components/session/ScheduledTasksDialog.tsx
index a4a59fb2..5423137f 100644
--- a/packages/ui/src/components/session/ScheduledTasksDialog.tsx
+++ b/packages/ui/src/components/session/ScheduledTasksDialog.tsx
@@ -11,19 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Checkbox } from '@/components/ui/checkbox';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { toast } from '@/components/ui';
-import {
- RiLoader4Line,
- RiPlayLine,
- RiEdit2Line,
- RiDeleteBinLine,
- RiAddLine,
- RiFolderLine,
- RiTimerLine,
- RiHistoryLine,
- RiCheckboxCircleLine,
- RiErrorWarningLine,
- RiPulseLine,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { useProjectsStore } from '@/stores/useProjectsStore';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -159,14 +147,14 @@ const STATUS_META: Record<
ScheduledTaskStatus,
{
tone: StatusTone;
- Icon: React.ComponentType<{ className?: string }>;
+ Icon: string;
spin?: boolean;
}
> = {
- success: { tone: 'success', Icon: RiCheckboxCircleLine },
- error: { tone: 'error', Icon: RiErrorWarningLine },
- running: { tone: 'warning', Icon: RiLoader4Line, spin: true },
- idle: { tone: 'muted', Icon: RiPulseLine },
+ success: { tone: 'success', Icon: 'checkbox-circle' },
+ error: { tone: 'error', Icon: 'error-warning' },
+ running: { tone: 'warning', Icon: 'loader-4', spin: true },
+ idle: { tone: 'muted', Icon: 'pulse' },
};
const toneStyle = (tone: StatusTone): React.CSSProperties => {
@@ -213,7 +201,7 @@ export function ScheduledTasksDialog() {
iconColor: currentTheme.colors.surface.foreground,
},
);
- const ProjectIcon = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
+ const projectIconName = project.icon ? PROJECT_ICON_MAP[project.icon] : null;
const iconColor = project.color ? PROJECT_COLOR_MAP[project.color] : undefined;
return (
@@ -225,10 +213,10 @@ export function ScheduledTasksDialog() {
>
- ) : ProjectIcon ? (
-
+ ) : projectIconName ? (
+
) : (
-
+
)}
{displayLabel}
@@ -423,7 +411,7 @@ export function ScheduledTasksDialog() {
{projectSelector}
- {t('sessions.scheduledTasks.dialog.actions.newTask')}
+ {t('sessions.scheduledTasks.dialog.actions.newTask')}
) : (
@@ -433,7 +421,7 @@ export function ScheduledTasksDialog() {
{loading ? (
- {t('sessions.scheduledTasks.dialog.loading')}
+ {t('sessions.scheduledTasks.dialog.loading')}
) : tasks.length === 0 ? (
@@ -474,7 +462,7 @@ export function ScheduledTasksDialog() {
-
+
{t('sessions.scheduledTasks.dialog.nextRun.label')}
{nextAt ? (
<>
@@ -487,14 +475,14 @@ export function ScheduledTasksDialog() {
)}
-
+
{t('sessions.scheduledTasks.dialog.lastRun.label')}
{status === 'running' ? (
-
+
{t('sessions.scheduledTasks.dialog.lastRun.runningNow')}
) : lastAt ? (
@@ -504,7 +492,7 @@ export function ScheduledTasksDialog() {
className="inline-flex items-center gap-1"
style={{ color: `var(--status-${meta.tone})` }}
>
-
+
{statusLabel}
) : null}
@@ -522,7 +510,7 @@ export function ScheduledTasksDialog() {
className="mt-3 flex items-start gap-2 rounded-md border p-2 typography-micro"
style={toneStyle('error')}
>
-
+
{task.state.lastError}
) : null}
@@ -553,7 +541,7 @@ export function ScheduledTasksDialog() {
onClick={() => void handleRunNow(task)}
disabled={isBusy}
>
-
{t('sessions.scheduledTasks.dialog.actions.runNow')}
+
{t('sessions.scheduledTasks.dialog.actions.runNow')}
- {t('sessions.scheduledTasks.dialog.actions.edit')}
+ {t('sessions.scheduledTasks.dialog.actions.edit')}
-
+
@@ -612,7 +600,7 @@ export function ScheduledTasksDialog() {
onClick={openNewTaskEditor}
disabled={!selectedProjectID}
>
-
{t('sessions.scheduledTasks.dialog.actions.newTask')}
+
{t('sessions.scheduledTasks.dialog.actions.newTask')}
)}
>
diff --git a/packages/ui/src/components/session/SessionDialogs.tsx b/packages/ui/src/components/session/SessionDialogs.tsx
index f68b2f9a..2f594bf2 100644
--- a/packages/ui/src/components/session/SessionDialogs.tsx
+++ b/packages/ui/src/components/session/SessionDialogs.tsx
@@ -10,8 +10,8 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
-import { RiCheckboxBlankLine, RiCheckboxLine, RiDeleteBinLine, RiGitBranchLine } from '@remixicon/react';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import { DirectoryExplorerDialog } from './DirectoryExplorerDialog';
import { cn, formatPathForDisplay } from '@/lib/utils';
import type { Session } from '@opencode-ai/sdk/v2';
@@ -606,7 +606,7 @@ export const SessionDialogs: React.FC = () => {
{isWorktreeDelete ? (
-
+
{t('sessions.sidebar.sessionDialogs.worktree.label')}
{targetWorktree?.label ? (
{targetWorktree.label}
@@ -651,9 +651,9 @@ export const SessionDialogs: React.FC = () => {
)}
>
{deleteDialogShouldRemoveRemote ? (
-
+
) : (
-
+
)}
{t('sessions.sidebar.sessionDialogs.actions.deleteRemoteBranch')}
@@ -680,9 +680,9 @@ export const SessionDialogs: React.FC = () => {
)}
>
{deleteDialogShouldDeleteLocalBranch ? (
-
+
) : (
-
+
)}
{t('sessions.sidebar.sessionDialogs.actions.deleteLocalBranch')}
@@ -713,7 +713,7 @@ export const SessionDialogs: React.FC = () => {
className="inline-flex items-center gap-1.5 typography-meta text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50"
aria-pressed={!showDeletionDialog}
>
- {!showDeletionDialog ?
:
}
+ {!showDeletionDialog ?
:
}
{t('sessions.sidebar.dialogs.neverAsk')}
@@ -779,7 +779,7 @@ export const SessionDialogs: React.FC = () => {
>
- {isWorktreeDelete && }
+ {isWorktreeDelete && }
{deleteDialogTitle}
{deleteDialogDescription && {deleteDialogDescription} }
diff --git a/packages/ui/src/components/session/SessionFolderItem.tsx b/packages/ui/src/components/session/SessionFolderItem.tsx
index d8bf963a..27c569d0 100644
--- a/packages/ui/src/components/session/SessionFolderItem.tsx
+++ b/packages/ui/src/components/session/SessionFolderItem.tsx
@@ -1,19 +1,8 @@
import React from 'react';
-import {
- RiFolderLine,
- RiFolderOpenLine,
- RiArrowRightSLine,
- RiArrowDownSLine,
- RiPencilAiLine,
- RiDeleteBinLine,
- RiCheckLine,
- RiCloseLine,
- RiAddLine,
- RiFolderAddLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { SessionFolder } from '@/stores/useSessionFoldersStore';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
interface SessionFolderItemProps {
folder: SessionFolder;
@@ -143,7 +132,7 @@ const SessionFolderItemBase = ({
};
}, [isRenaming]);
- const FolderIcon = isCollapsed ? RiFolderLine : RiFolderOpenLine;
+ const folderIconName = isCollapsed ? 'folder' : 'folder-open';
const isSubFolder = depth > 0;
return (
@@ -179,7 +168,7 @@ const SessionFolderItemBase = ({
? (alwaysShowActions ? 'pr-7' : 'group-hover/folder:pr-7 group-focus-within/folder:pr-7')
: '',
)}>
-
+
{renaming ? (
) : (
@@ -244,9 +233,9 @@ const SessionFolderItemBase = ({
• {sessions.length}
{isCollapsed ? (
-
+
) : (
-
+
)}
)}
@@ -274,7 +263,7 @@ const SessionFolderItemBase =
({
aria-label={t('sessions.sidebar.folderItem.newSessionAria', { folderName: folder.name })}
title={t('sessions.sidebar.project.actions.newSession')}
>
-
+
) : null}
{/* Only allow sub-folders at depth 0 (one level deep max) */}
@@ -289,7 +278,7 @@ const SessionFolderItemBase = ({
aria-label={t('sessions.sidebar.folderItem.newSubfolderAria', { folderName: folder.name })}
title={t('sessions.sidebar.folderItem.newSubfolder')}
>
-
+
) : null}
{!archivedBucket ? (
@@ -302,7 +291,7 @@ const SessionFolderItemBase = ({
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('sessions.sidebar.folderItem.renameAria', { folderName: folder.name })}
>
-
+
) : null}
({
? t('sessions.sidebar.folderItem.deleteArchivedInFolderAria', { folderName: folder.name })
: t('sessions.sidebar.folderItem.deleteFolderAria', { folderName: folder.name })}
>
-
+
diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx
index a5c22701..6f6f8420 100644
--- a/packages/ui/src/components/session/SessionSidebar.tsx
+++ b/packages/ui/src/components/session/SessionSidebar.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import type { Session } from '@opencode-ai/sdk/v2';
-import { RiLayoutLeftLine } from '@remixicon/react';
import { toast } from '@/components/ui';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { useI18n } from '@/lib/i18n';
@@ -38,6 +37,7 @@ import { useStickyProjectHeaders } from './sidebar/hooks/useStickyProjectHeaders
import { getGitHubPrStatusKey, usePrVisualSummaryByKeys, useGitHubPrStatusStore } from '@/stores/useGitHubPrStatusStore';
import { ProjectEditDialog } from '@/components/layout/ProjectEditDialog';
import { UpdateDialog } from '@/components/ui/UpdateDialog';
+import { Icon } from "@/components/icon/Icon";
import { SessionGroupSection } from './sidebar/SessionGroupSection';
import { SidebarHeader } from './sidebar/SidebarHeader';
import { SidebarActivitySections } from './sidebar/SidebarActivitySections';
@@ -1648,7 +1648,7 @@ export const SessionSidebar: React.FC
= ({
className={desktopSidebarToggleButtonClass}
aria-label={t('sessions.sidebar.header.actions.closeSessions')}
>
-
+
diff --git a/packages/ui/src/components/session/TodoSendDialog.tsx b/packages/ui/src/components/session/TodoSendDialog.tsx
index 21f87c64..483ec084 100644
--- a/packages/ui/src/components/session/TodoSendDialog.tsx
+++ b/packages/ui/src/components/session/TodoSendDialog.tsx
@@ -17,8 +17,8 @@ import { AgentSelector } from '@/components/sections/commands/AgentSelector';
import { useConfigStore } from '@/stores/useConfigStore';
import { useAgentsStore } from '@/stores/useAgentsStore';
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
-import { RiArrowDownSLine } from '@remixicon/react';
import { useI18n } from '@/lib/i18n';
type TodoSendTarget = 'session' | 'worktree';
@@ -70,7 +70,7 @@ const ThinkingPill = ({ value, options, disabled, onChange }: ThinkingPillProps)
)}
>
{label}
-
+
);
diff --git a/packages/ui/src/components/session/sidebar/BulkActionBar.tsx b/packages/ui/src/components/session/sidebar/BulkActionBar.tsx
index 92450bce..f42e3b82 100644
--- a/packages/ui/src/components/session/sidebar/BulkActionBar.tsx
+++ b/packages/ui/src/components/session/sidebar/BulkActionBar.tsx
@@ -7,12 +7,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import {
- RiAddLine,
- RiCloseLine,
- RiDeleteBinLine,
- RiFolderLine,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import type { SessionFolder } from '@/stores/useSessionFoldersStore';
import { useI18n } from '@/lib/i18n';
@@ -67,7 +62,7 @@ export const BulkActionBar: React.FC
= ({
className={iconButtonClass}
aria-label={t('sessions.sidebar.bulkActions.moveToFolder')}
>
-
+
@@ -87,7 +82,7 @@ export const BulkActionBar: React.FC = ({
)}
-
+
{t('sessions.sidebar.folders.newFolderEllipsis')}
{canRemoveFromFolder ? (
@@ -95,7 +90,7 @@ export const BulkActionBar: React.FC = ({
onClick={onRemoveFromFolder}
className="text-destructive focus:text-destructive"
>
-
+
{t('sessions.sidebar.folders.removeFromFolder')}
) : null}
@@ -111,7 +106,7 @@ export const BulkActionBar: React.FC = ({
className={cn(destructiveIconButtonClass)}
aria-label={destructiveLabel}
>
-
+
{destructiveLabel}
@@ -125,7 +120,7 @@ export const BulkActionBar: React.FC = ({
className={iconButtonClass}
aria-label={t('sessions.sidebar.header.actions.exitSelection')}
>
-
+
{t('sessions.sidebar.header.actions.exitSelection')}
diff --git a/packages/ui/src/components/session/sidebar/ConfirmDialogs.tsx b/packages/ui/src/components/session/sidebar/ConfirmDialogs.tsx
index 6006d343..fb6ee1e8 100644
--- a/packages/ui/src/components/session/sidebar/ConfirmDialogs.tsx
+++ b/packages/ui/src/components/session/sidebar/ConfirmDialogs.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
-import { RiCheckboxBlankLine, RiCheckboxLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import type { Session } from '@opencode-ai/sdk/v2';
import { useI18n } from '@/lib/i18n';
@@ -65,7 +65,7 @@ export function SessionDeleteConfirmDialog(props: {
className="inline-flex items-center gap-1.5 typography-ui-label text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50"
aria-pressed={!showDeletionDialog}
>
- {!showDeletionDialog ? : }
+ {!showDeletionDialog ? : }
{t('sessions.sidebar.dialogs.neverAsk')}
@@ -135,7 +135,7 @@ export function BulkSessionDeleteConfirmDialog(props: {
className="inline-flex items-center gap-1.5 typography-ui-label text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50"
aria-pressed={!showDeletionDialog}
>
- {!showDeletionDialog ?
:
}
+ {!showDeletionDialog ?
:
}
{t('sessions.sidebar.dialogs.neverAsk')}
diff --git a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx
index 501bf950..22293ec6 100644
--- a/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx
+++ b/packages/ui/src/components/session/sidebar/SessionGroupSection.tsx
@@ -1,15 +1,7 @@
import React from 'react';
import type { Session } from '@opencode-ai/sdk/v2';
-import {
- RiAddLine,
- RiArchiveLine,
- RiArrowDownSLine,
- RiArrowLeftLongLine,
- RiArrowRightSLine,
- RiDeleteBinLine,
- RiGitBranchLine,
-} from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { sessionEvents } from '@/lib/sessionEvents';
import type { MainTab } from '@/stores/useUIStore';
@@ -536,7 +528,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
-
@@ -544,7 +536,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
'text-muted-foreground h-3.5 w-3.5 items-center justify-center',
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/gh:inline-flex',
)}>
- {isCollapsed ? : }
+ {isCollapsed ? : }
{prIndicator.url ? (
@@ -568,7 +560,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
{baseBranchLabel && headBranchLabel ? (
<>
{baseBranchLabel}
-
+
{headBranchLabel}
>
) : (
@@ -593,12 +585,12 @@ export function SessionGroupSection(props: Props): React.ReactNode {
) : group.isArchivedBucket ? (
-
+
- {isCollapsed ? : }
+ {isCollapsed ? : }
{renderHighlightedText(group.label, normalizedSessionSearchQuery)}
@@ -606,7 +598,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
) : (!group.isMain || group.worktree) ? (
-
@@ -614,7 +606,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
'text-muted-foreground h-3.5 w-3.5 items-center justify-center',
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/gh:inline-flex',
)}>
- {isCollapsed ? : }
+ {isCollapsed ? : }
{renderHighlightedText(group.label, normalizedSessionSearchQuery)}
@@ -626,16 +618,15 @@ export function SessionGroupSection(props: Props): React.ReactNode {
{showBranchSubtitle && statusLine ? (
{group.isArchivedBucket ? (
-
+
) : (!group.isMain || isGitProject) ? (
showInlinePrTitle && prIndicator ? (
-
+
@@ -645,7 +636,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
{baseBranchLabel && headBranchLabel ? (
<>
{baseBranchLabel}
-
+
{headBranchLabel}
>
) : (
@@ -666,10 +657,9 @@ export function SessionGroupSection(props: Props): React.ReactNode {
) : (
-
+
)
) : null}
-
+
{t('sessions.sidebar.group.actions.deleteArchivedSessions')}
@@ -722,7 +712,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('sessions.sidebar.group.actions.deleteGroupAria', { label: group.label })}
>
-
+
{t('sessions.sidebar.group.actions.deleteWorktree')}
@@ -745,7 +735,7 @@ export function SessionGroupSection(props: Props): React.ReactNode {
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('sessions.sidebar.group.actions.newDraftInGroupAria', { label: group.label })}
>
-
+
{t('sessions.sidebar.project.actions.newDraftSession')}
diff --git a/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx b/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx
index 977da03e..292786ad 100644
--- a/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx
+++ b/packages/ui/src/components/session/sidebar/SessionNodeItem.tsx
@@ -11,34 +11,12 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import {
- RiAddLine,
- RiArchiveLine,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiChat4Line,
- RiCheckLine,
- RiCloseLine,
- RiDeleteBinLine,
- RiDownloadLine,
- RiErrorWarningLine,
- RiFileCopyLine,
- RiFolderLine,
- RiLinkUnlinkM,
- RiMore2Line,
- RiPencilAiLine,
- RiPushpinLine,
- RiShare2Line,
- RiShieldLine,
- RiUnpinLine,
- RiGitBranchLine,
- RiWindowLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { canUseElectronDesktopIPC, invokeDesktop, isVSCodeRuntime } from '@/lib/desktop';
import { toast } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
+import { Icon } from "@/components/icon/Icon";
import { buildExportFilename, downloadAsMarkdown, formatSessionAsMarkdown, getExportRevealLabelKey, revealExportedMarkdown, saveAsMarkdownDesktop } from '@/lib/exportSession';
import type { ChildSessionExport } from '@/lib/exportSession';
import { buildSessionMessageRecordsSnapshot, useDirectoryStore, useGlobalSessionStatus, useSession, useSessionPermissions } from '@/sync/sync-context';
@@ -479,7 +457,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
title={t('sessions.sidebar.session.rename.save')}
className="shrink-0 text-muted-foreground hover:text-foreground"
>
-
+
-
+
{!isMinimalMode ? (
- {hasChildren ? {isExpanded ? : } : null}
+ {hasChildren ? {isExpanded ? : } : null}
{sessionUpdatedLabel}
{sessionDiffStats ? +{sessionDiffStats.additions} /-{sessionDiffStats.deletions} : null}
{hasSecondaryProjectLabel ? {secondaryMeta?.projectLabel} : null}
- {hasSecondaryBranchLabel ? {secondaryMeta?.branchLabel} : null}
+ {hasSecondaryBranchLabel ? {secondaryMeta?.branchLabel} : null}
) : null}
@@ -537,7 +515,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
)}
>
{showStatusMarker ? statusMarkerContent : null}
- {isPinnedSession ?
: null}
+ {isPinnedSession ?
: null}
) : null;
const subsessionChevron = hasChildren ? (
@@ -566,12 +544,12 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
? t('sessions.sidebar.session.subsessions.collapse')
: t('sessions.sidebar.session.subsessions.expand')}
>
- {isExpanded ? : }
+ {isExpanded ? : }
) : null;
const streamingIndicator = isZombie
- ?
+ ?
: null;
const handleMenuOpenChange = (open: boolean) => {
@@ -663,33 +641,33 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
}}
className="[&>svg]:mr-1"
>
-
+
{t('sessions.sidebar.session.menu.rename')}
togglePinnedSession(session.id)} className="[&>svg]:mr-1">
- {isPinnedSession ? : }
+ {isPinnedSession ? : }
{isPinnedSession ? t('sessions.sidebar.session.menu.unpin') : t('sessions.sidebar.session.menu.pin')}
{!resolvedSession.share ? (
handleShareSession(resolvedSession)} className="[&>svg]:mr-1">
-
+
{t('sessions.sidebar.session.menu.share')}
) : (
<>
{ if (resolvedSession.share?.url) handleCopyShareUrl(resolvedSession.share.url, session.id); }} className="[&>svg]:mr-1">
{copiedSessionId === session.id
- ? <> {t('sessions.sidebar.session.menu.copied')}>
- : <> {t('sessions.sidebar.session.menu.copyLink')}>}
+ ? <> {t('sessions.sidebar.session.menu.copied')}>
+ : <> {t('sessions.sidebar.session.menu.copyLink')}>}
handleUnshareSession(session.id)} className="[&>svg]:mr-1">
-
+
{t('sessions.sidebar.session.menu.unshare')}
>
)}
{ void handleExportSession(); }} className="[&>svg]:mr-1">
-
+
{t('sessions.sidebar.session.menu.exportMarkdown')}
@@ -700,7 +678,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
<>
- {t('sessions.sidebar.folders.moveToFolder')}
+ {t('sessions.sidebar.folders.moveToFolder')}
{scopeFolders.length === 0 ? (
{t('sessions.sidebar.folders.none')}
@@ -708,18 +686,18 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
scopeFolders.map((folder) => (
{ if (currentFolderId === folder.id) removeSessionFromFolder(sessionDirectory, session.id); else addSessionToFolder(sessionDirectory, folder.id, session.id); }}>
{folder.name}
- {currentFolderId === folder.id ? : null}
+ {currentFolderId === folder.id ? : null}
))
)}
{ const newFolder = createFolderAndStartRename(sessionDirectory); if (!newFolder) return; addSessionToFolder(sessionDirectory, newFolder.id, session.id); }}>
-
+
{t('sessions.sidebar.folders.newFolderEllipsis')}
{currentFolderId ? (
{ removeSessionFromFolder(sessionDirectory, session.id); }} className="text-destructive focus:text-destructive">
-
+
{t('sessions.sidebar.folders.removeFromFolder')}
) : null}
@@ -742,7 +720,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
}}
className="[&>svg]:mr-1"
>
-
+
{t('sessions.sidebar.session.menu.openInSidePanel')}
{t('sessions.sidebar.session.menu.betaBadge')}
@@ -754,14 +732,14 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
onClick={handleOpenMiniChatWindow}
className="[&>svg]:mr-1"
>
-
+
{t('sessions.sidebar.session.menu.openMiniChatWindow')}
) : null}
handleDeleteSession(session, { archivedBucket })}>
-
+
{archivedBucket ? t('sessions.sidebar.bulkActions.delete') : t('sessions.sidebar.bulkActions.archive')}
@@ -824,7 +802,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
) : null}
{pendingPermissionCount > 0 ? (
-
+
{pendingPermissionCount}
) : null}
@@ -841,7 +819,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
{secondaryMeta?.branchLabel ? (
- {secondaryMeta.branchLabel}
+ {secondaryMeta.branchLabel}
) : null}
{sessionDiffStats ?
+{sessionDiffStats.additions} -{sessionDiffStats.deletions} : null}
@@ -875,7 +853,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
{renderHighlightedText(sessionTitle, normalizedSessionSearchQuery)}
{pendingPermissionCount > 0 ? (
-
+
{pendingPermissionCount}
) : null}
@@ -887,7 +865,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
{sessionUpdatedLabel}
{sessionDiffStats ?
+{sessionDiffStats.additions} / -{sessionDiffStats.deletions} : null}
{hasSecondaryProjectLabel ?
{secondaryMeta?.projectLabel} : null}
- {hasSecondaryBranchLabel ?
{secondaryMeta?.branchLabel} : null}
+ {hasSecondaryBranchLabel ?
{secondaryMeta?.branchLabel} : null}
) : null}
@@ -924,7 +902,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
onClick={handleQuickArchiveClick}
onKeyDown={(event) => event.stopPropagation()}
>
-
+
@@ -950,7 +928,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
onClick={handleMenuTriggerClick}
onKeyDown={(event) => event.stopPropagation()}
>
-
+
{sessionMenuContent}
diff --git a/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx b/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
index 89f6728d..a6a8f343 100644
--- a/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
+++ b/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import type { SessionNode } from './types';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
type ActivityItem = {
node: SessionNode;
@@ -77,7 +77,7 @@ export function SidebarActivitySections({ sections, renderSessionNode }: Props):
aria-expanded={!isCollapsed}
>
- {isCollapsed ? : }
+ {isCollapsed ? : }
{section.title}
diff --git a/packages/ui/src/components/session/sidebar/SidebarFooter.tsx b/packages/ui/src/components/session/sidebar/SidebarFooter.tsx
index a312cbe8..d49fdbac 100644
--- a/packages/ui/src/components/session/sidebar/SidebarFooter.tsx
+++ b/packages/ui/src/components/session/sidebar/SidebarFooter.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiInformationLine, RiQuestionLine, RiSettings3Line } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
type Props = {
@@ -32,7 +32,7 @@ export function SidebarFooter({
-
+
{t('sessions.sidebar.footer.actions.settings')}
@@ -40,7 +40,7 @@ export function SidebarFooter({
-
+
{t('sessions.sidebar.footer.actions.shortcuts')}
@@ -48,7 +48,7 @@ export function SidebarFooter({
-
+
{t('sessions.sidebar.footer.actions.aboutOpenChamber')}
diff --git a/packages/ui/src/components/session/sidebar/SidebarHeader.tsx b/packages/ui/src/components/session/sidebar/SidebarHeader.tsx
index 5f0081c0..db747d05 100644
--- a/packages/ui/src/components/session/sidebar/SidebarHeader.tsx
+++ b/packages/ui/src/components/session/sidebar/SidebarHeader.tsx
@@ -7,21 +7,9 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import {
- RiCheckLine,
- RiCheckboxMultipleLine,
- RiChatNewLine,
- RiEqualizer2Line,
- RiFolderAddLine,
- RiLayoutLeftLine,
- RiSearchLine,
- RiCloseLine,
- RiContractUpDownLine,
- RiExpandUpDownLine,
- RiCalendarScheduleLine,
-} from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ArrowsMerge } from '@/components/icons/ArrowsMerge';
+import { Icon } from "@/components/icon/Icon";
import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
import { useI18n } from '@/lib/i18n';
@@ -118,7 +106,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
className="inline-flex h-8 w-8 items-center justify-center rounded-md typography-ui-label font-medium text-foreground transition-colors hover:bg-interactive-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50"
aria-label={t('sessions.sidebar.header.actions.closeSessions')}
>
-
+
{t('sessions.sidebar.header.actions.closeSessions')}
@@ -132,7 +120,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
className={headerActionButtonClass}
aria-label={t('sessions.sidebar.header.actions.addProject')}
>
-
+
{t('sessions.sidebar.header.actions.addProject')}
@@ -145,7 +133,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
className={headerActionButtonClass}
aria-label={t('sessions.sidebar.header.actions.newSession')}
>
-
+
{t('sessions.sidebar.header.actions.newSession')}
@@ -176,7 +164,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
className={headerActionButtonClass}
aria-label={t('sessions.sidebar.header.actions.scheduledTasks')}
>
-
+
{t('sessions.sidebar.header.actions.scheduledTasks')}
@@ -191,7 +179,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
aria-label={t('sessions.sidebar.header.actions.searchSessions')}
aria-expanded={isSessionSearchOpen}
>
-
+
{t('sessions.sidebar.header.actions.searchSessions')}
@@ -208,7 +196,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
: t('sessions.sidebar.header.actions.selectSessions')}
aria-pressed={selectionModeEnabled}
>
-
+
@@ -227,7 +215,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
className={headerActionButtonClass}
aria-label={t('sessions.sidebar.header.actions.sessionDisplayMode')}
>
-
+
@@ -239,14 +227,14 @@ export function SidebarHeader(props: Props): React.ReactNode {
className="flex items-center justify-between"
>
{t('sessions.sidebar.header.displayMode.default')}
- {displayMode === 'default' ? : null}
+ {displayMode === 'default' ? : null}
setDisplayMode('minimal')}
className="flex items-center justify-between"
>
{t('sessions.sidebar.header.displayMode.minimal')}
- {displayMode === 'minimal' ? : null}
+ {displayMode === 'minimal' ? : null}
{t('sessions.sidebar.header.displayMode.showRecent')}
- {showRecentSection ? : null}
+ {showRecentSection ? : null}
-
+
{t('sessions.sidebar.header.displayMode.collapseAll')}
-
+
{t('sessions.sidebar.header.displayMode.expandAll')}
@@ -281,7 +269,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
{t('sessions.sidebar.header.search.escapeHint')}
-
+
-
+
) : null}
diff --git a/packages/ui/src/components/session/sidebar/sessionFolderDnd.tsx b/packages/ui/src/components/session/sidebar/sessionFolderDnd.tsx
index 5aacba24..bff8d5d8 100644
--- a/packages/ui/src/components/session/sidebar/sessionFolderDnd.tsx
+++ b/packages/ui/src/components/session/sidebar/sessionFolderDnd.tsx
@@ -10,7 +10,7 @@ import {
useDroppable,
type DragEndEvent,
} from '@dnd-kit/core';
-import { RiStickyNoteLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
export const DraggableSessionRow: React.FC<{
sessionId: string;
@@ -122,7 +122,7 @@ export const SessionFolderDndScope: React.FC<{
}}
className="flex items-center rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] px-2.5 py-1 shadow-none pointer-events-none"
>
-
+
{activeDragTitle}
diff --git a/packages/ui/src/components/session/sidebar/sortableItems.tsx b/packages/ui/src/components/session/sidebar/sortableItems.tsx
index 45f0f5bd..0a970c34 100644
--- a/packages/ui/src/components/session/sidebar/sortableItems.tsx
+++ b/packages/ui/src/components/session/sidebar/sortableItems.tsx
@@ -8,16 +8,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiCloseLine,
- RiFolderLine,
- RiMore2Line,
- RiNodeTree,
- RiPencilAiLine,
-} from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, getProjectIconImageUrl } from '@/lib/projectMeta';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -104,7 +95,7 @@ export const SortableProjectItem: React.FC = ({
setImageFailed(false);
}, [id, projectIconImage?.updatedAt]);
- const ProjectIcon = projectIcon ? PROJECT_ICON_MAP[projectIcon] : null;
+ const projectIconName = projectIcon ? PROJECT_ICON_MAP[projectIcon] : null;
const iconColor = projectColor ? (PROJECT_COLOR_MAP[projectColor] ?? null) : null;
const imageUrl = !imageFailed
? getProjectIconImageUrl({ id, iconImage: projectIconImage }, {
@@ -186,7 +177,7 @@ export const SortableProjectItem: React.FC = ({
'h-3.5 w-3.5 items-center justify-center text-muted-foreground',
alwaysShowActions ? 'inline-flex' : 'hidden group-hover/project:inline-flex group-focus-within/project:inline-flex',
)}>
- {isCollapsed ? : }
+ {isCollapsed ? : }
{imageUrl ? (
= ({
onError={() => setImageFailed(true)}
/>
- ) : ProjectIcon ? (
-
+ ) : projectIconName ? (
+
) : (
-
+
)}
= ({
)}
aria-label={t('sessions.sidebar.project.actions.newWorktree')}
>
-
+
@@ -271,25 +262,25 @@ export const SortableProjectItem: React.FC = ({
onMouseDown={handleMenuTriggerMouseDown}
onClick={handleMenuTriggerClick}
>
-
+
{showCreateButtons && !isRepo && !hideDirectoryControls && onNewSession && (
-
+
{t('sessions.sidebar.project.actions.newSession')}
)}
-
+
{t('sessions.sidebar.session.menu.rename')}
-
+
{t('sessions.sidebar.project.actions.closeProject')}
@@ -314,7 +305,7 @@ export const SortableProjectItem: React.FC = ({
? t('sessions.sidebar.project.actions.newDraftSession')
: t('sessions.sidebar.project.actions.newSession')}
>
-
+
diff --git a/packages/ui/src/components/ui/AboutDialog.tsx b/packages/ui/src/components/ui/AboutDialog.tsx
index dc41cd87..4b4a3781 100644
--- a/packages/ui/src/components/ui/AboutDialog.tsx
+++ b/packages/ui/src/components/ui/AboutDialog.tsx
@@ -4,10 +4,10 @@ import {
DialogContent,
} from '@/components/ui/dialog';
import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo';
-import { RiDiscordFill, RiGithubFill, RiTwitterXFill } from '@remixicon/react';
import { debugUtils } from '@/lib/debug';
import { cn } from '@/lib/utils';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { getDesktopAppVersion } from '@/lib/desktopNative';
@@ -153,7 +153,7 @@ export const AboutDialog: React.FC = ({
rel="noopener noreferrer"
className="flex items-center gap-1.5 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
GitHub
= ({
rel="noopener noreferrer"
className="flex items-center gap-1.5 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
Discord
= ({
rel="noopener noreferrer"
className="flex items-center gap-1.5 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
@btriapitsyn
diff --git a/packages/ui/src/components/ui/CommandPalette.tsx b/packages/ui/src/components/ui/CommandPalette.tsx
index 47beb26d..bb6551dc 100644
--- a/packages/ui/src/components/ui/CommandPalette.tsx
+++ b/packages/ui/src/components/ui/CommandPalette.tsx
@@ -28,24 +28,13 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
import { toast } from '@/components/ui';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
-import {
- RiAddLine,
- RiChatAi3Line,
- RiFolderAddLine,
- RiGitBranchLine,
- RiLayoutLeftLine,
- RiLayoutRightLine,
- RiPieChartLine,
- RiWindowLine,
- RiSettings3Line,
- RiTerminalBoxLine,
-} from '@remixicon/react';
import type { Session } from '@opencode-ai/sdk/v2';
import { createWorktreeSession } from '@/lib/worktreeSessionCreator';
import { formatShortcutForDisplay, getEffectiveShortcutCombo } from '@/lib/shortcuts';
import { canUseElectronDesktopIPC, invokeDesktop, isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
import { SETTINGS_PAGE_METADATA, type SettingsRuntimeContext } from '@/lib/settings/metadata';
import { getSettingsNavIcon } from '@/components/views/SettingsView';
+import { Icon } from "@/components/icon/Icon";
import { scoreByFuzzyQuery } from '@/lib/search/fuzzySearch';
import { truncatePathMiddle } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -154,7 +143,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'new-session',
title: t('commandPalette.item.newSession'),
- icon: ,
+ icon: ,
shortcutId: 'new_chat',
searchText: t('commandPalette.item.newSession'),
onSelect: run(() => {
@@ -166,7 +155,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'new-worktree',
title: t('commandPalette.item.newWorktreeDraft'),
- icon: ,
+ icon: ,
shortcutId: 'new_chat_worktree',
searchText: t('commandPalette.item.newWorktreeDraft'),
onSelect: run(() => {
@@ -176,7 +165,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'add-project',
title: t('commandPalette.item.addProject'),
- icon: ,
+ icon: ,
searchText: t('commandPalette.item.addProject'),
onSelect: run(() => {
sessionEvents.requestDirectoryDialog();
@@ -187,7 +176,7 @@ export const CommandPalette: React.FC = () => {
title: isMobile
? t('commandPalette.item.showSessionSwitcher')
: t('commandPalette.item.toggleSidebar'),
- icon: ,
+ icon: ,
shortcutId: 'toggle_sidebar',
searchText: isMobile
? t('commandPalette.item.showSessionSwitcher')
@@ -204,7 +193,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'toggle-right-sidebar',
title: t('commandPalette.item.toggleRightSidebar'),
- icon: ,
+ icon: ,
shortcutId: 'toggle_right_sidebar',
searchText: t('commandPalette.item.toggleRightSidebar'),
onSelect: run(() => toggleRightSidebar()),
@@ -212,7 +201,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'toggle-terminal',
title: t('commandPalette.item.toggleTerminal'),
- icon: ,
+ icon: ,
shortcutId: 'toggle_terminal',
searchText: t('commandPalette.item.toggleTerminal'),
onSelect: run(() => toggleBottomTerminal()),
@@ -220,7 +209,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'context-usage',
title: t('commandPalette.item.showContextUsage'),
- icon: ,
+ icon: ,
searchText: t('commandPalette.item.showContextUsage'),
onSelect: run(() => {
if (currentDirectory) openContextOverview(currentDirectory);
@@ -229,7 +218,7 @@ export const CommandPalette: React.FC = () => {
{
id: 'open-settings',
title: t('commandPalette.item.openSettings'),
- icon: ,
+ icon: ,
shortcutId: 'open_settings',
searchText: t('commandPalette.item.openSettings'),
onSelect: run(() => setSettingsDialogOpen(true)),
@@ -239,7 +228,7 @@ export const CommandPalette: React.FC = () => {
list.splice(1, 0, {
id: 'new-mini-chat',
title: t('commandPalette.item.newMiniChat'),
- icon: ,
+ icon: ,
shortcutId: 'new_mini_chat',
searchText: t('commandPalette.item.newMiniChat'),
onSelect: run(() => {
@@ -283,12 +272,12 @@ export const CommandPalette: React.FC = () => {
.filter((p) => p.slug !== 'home')
.filter((p) => (p.isAvailable ? p.isAvailable(settingsRuntimeCtx) : true))
.map((page) => {
- const Icon = getSettingsNavIcon(page.slug) ?? RiSettings3Line;
+ const iconName = getSettingsNavIcon(page.slug) ?? 'settings-3';
const keywords = (page.keywords ?? []).join(' ');
return {
id: `settings:${page.slug}`,
title: page.title,
- icon: ,
+ icon: ,
searchText: `${page.title} ${page.group} ${keywords}`,
onSelect: run(() => {
setSettingsPage(page.slug);
@@ -507,11 +496,11 @@ export const CommandPalette: React.FC = () => {
value={`session:${session.id}`}
onSelect={() => handleOpenSession(session)}
>
-
+
{title}
{branch ? (
-
+
{branch}
) : null}
diff --git a/packages/ui/src/components/ui/ContextUsageDisplay.tsx b/packages/ui/src/components/ui/ContextUsageDisplay.tsx
index ac4117be..badfcdf2 100644
--- a/packages/ui/src/components/ui/ContextUsageDisplay.tsx
+++ b/packages/ui/src/components/ui/ContextUsageDisplay.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { RiDonutChartFill, RiDonutChartLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface ContextUsageDisplayProps {
@@ -69,11 +69,11 @@ export const ContextUsageDisplay: React.FC = ({
const contextContent = (
<>
- {!isMobile && !hideIcon && }
+ {!isMobile && !hideIcon && }
{showPercentIcon ? (
<>
-
diff --git a/packages/ui/src/components/ui/ErrorBoundary.tsx b/packages/ui/src/components/ui/ErrorBoundary.tsx
index 88f5fd0d..676320f2 100644
--- a/packages/ui/src/components/ui/ErrorBoundary.tsx
+++ b/packages/ui/src/components/ui/ErrorBoundary.tsx
@@ -1,9 +1,9 @@
import React from 'react';
-import { RiErrorWarningLine, RiRestartLine } from '@remixicon/react';
import { Button } from './button';
import { Card, CardContent, CardHeader, CardTitle } from './card';
import { copyTextToClipboard } from '@/lib/clipboard';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
interface ErrorBoundaryState {
hasError: boolean;
@@ -81,7 +81,7 @@ class InnerErrorBoundary extends React.Component
-
+
{strings.title}
@@ -102,7 +102,7 @@ class InnerErrorBoundary extends React.Component
-
+
{strings.tryAgain}
diff --git a/packages/ui/src/components/ui/HelpDialog.tsx b/packages/ui/src/components/ui/HelpDialog.tsx
index 4e0ef85a..8ba275c8 100644
--- a/packages/ui/src/components/ui/HelpDialog.tsx
+++ b/packages/ui/src/components/ui/HelpDialog.tsx
@@ -6,25 +6,8 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
+import { Icon } from "@/components/icon/Icon";
import { useUIStore } from "@/stores/useUIStore";
-import {
- RiAddLine,
- RiAiAgentLine,
- RiAiGenerate2,
- RiBrainAi3Line,
- RiCloseCircleLine,
- RiCommandLine,
- RiGitBranchLine,
- RiLayoutLeftLine,
- RiLayoutRightLine,
- RiPaletteLine,
- RiQuestionLine,
- RiSettings3Line,
- RiStackLine,
- RiText,
- RiTimeLine,
- RiWindowLine,
-} from "@remixicon/react";
import {
getEffectiveShortcutCombo,
getShortcutAction,
@@ -32,14 +15,13 @@ import {
formatShortcutForDisplay,
} from "@/lib/shortcuts";
import { useI18n, type I18nKey } from "@/lib/i18n";
-
-type ShortcutIcon = React.ComponentType<{ className?: string }>;
+import type { IconName } from "@/components/icon/icons";
type ShortcutItem = {
id?: string;
keys: string | string[];
descriptionKey: I18nKey;
- icon: ShortcutIcon | null;
+ icon: IconName | null;
};
type ShortcutSection = {
@@ -66,52 +48,52 @@ export const HelpDialog: React.FC = () => {
{
id: 'open_command_palette',
descriptionKey: "helpDialog.item.openCommandPalette",
- icon: RiCommandLine,
+ icon: "command",
keys: '',
},
{
id: 'open_help',
descriptionKey: "helpDialog.item.showKeyboardShortcuts",
- icon: RiQuestionLine,
+ icon: "question",
keys: '',
},
{
id: 'toggle_sidebar',
descriptionKey: "helpDialog.item.toggleSessionSidebar",
- icon: RiLayoutLeftLine,
+ icon: "layout-left",
keys: '',
},
{
keys: ["Tab"],
descriptionKey: "helpDialog.item.cycleAgent",
- icon: RiAiAgentLine,
+ icon: "ai-agent",
},
{
id: 'open_model_selector',
descriptionKey: "helpDialog.item.openModelSelector",
- icon: RiAiGenerate2,
+ icon: "ai-generate-2",
keys: '',
},
{
keys: ["↑↓"],
descriptionKey: "helpDialog.item.navigateModels",
- icon: RiAiGenerate2,
+ icon: "ai-generate-2",
},
{
keys: ["←→"],
descriptionKey: "helpDialog.item.adjustThinkingMode",
- icon: RiBrainAi3Line,
+ icon: "brain-ai-3",
},
{
id: 'cycle_thinking_variant',
descriptionKey: "helpDialog.item.cycleThinkingVariant",
- icon: RiBrainAi3Line,
+ icon: "brain-ai-3",
keys: '',
},
{
keys: [`Shift + Alt + ${mod} + N`],
descriptionKey: "helpDialog.item.newWindow",
- icon: RiWindowLine,
+ icon: "window",
},
],
},
@@ -121,20 +103,20 @@ export const HelpDialog: React.FC = () => {
{
id: 'new_chat',
descriptionKey: "helpDialog.item.createNewSession",
- icon: RiAddLine,
+ icon: "add",
keys: '',
},
{
id: 'new_chat_worktree',
descriptionKey: "helpDialog.item.createNewWorktreeDraft",
- icon: RiGitBranchLine,
+ icon: "git-branch",
keys: '',
},
- { id: 'focus_input', descriptionKey: "helpDialog.item.focusChatInput", icon: RiText, keys: '' },
+ { id: 'focus_input', descriptionKey: "helpDialog.item.focusChatInput", icon: "text", keys: '' },
{
id: 'abort_run',
descriptionKey: "helpDialog.item.abortActiveRun",
- icon: RiCloseCircleLine,
+ icon: "close-circle",
keys: '',
},
],
@@ -145,43 +127,43 @@ export const HelpDialog: React.FC = () => {
{
id: 'toggle_right_sidebar',
descriptionKey: 'helpDialog.item.toggleRightSidebar',
- icon: RiLayoutRightLine,
+ icon: "layout-right",
keys: '',
},
{
id: 'open_right_sidebar_git',
descriptionKey: 'helpDialog.item.openRightSidebarGitTab',
- icon: RiGitBranchLine,
+ icon: "git-branch",
keys: '',
},
{
id: 'open_right_sidebar_files',
descriptionKey: 'helpDialog.item.openRightSidebarFilesTab',
- icon: RiLayoutRightLine,
+ icon: "layout-right",
keys: '',
},
{
id: 'cycle_right_sidebar_tab',
descriptionKey: 'helpDialog.item.cycleRightSidebarTab',
- icon: RiLayoutRightLine,
+ icon: "layout-right",
keys: '',
},
{
id: 'toggle_terminal',
descriptionKey: 'helpDialog.item.toggleTerminalDock',
- icon: RiWindowLine,
+ icon: "window",
keys: '',
},
{
id: 'toggle_terminal_expanded',
descriptionKey: 'helpDialog.item.toggleTerminalExpanded',
- icon: RiWindowLine,
+ icon: "window",
keys: '',
},
{
id: 'toggle_context_plan',
descriptionKey: 'helpDialog.item.togglePlanContextPanel',
- icon: RiTimeLine,
+ icon: "time",
keys: '',
},
],
@@ -192,30 +174,30 @@ export const HelpDialog: React.FC = () => {
{
id: 'cycle_theme',
descriptionKey: "helpDialog.item.cycleTheme",
- icon: RiPaletteLine,
+ icon: "palette",
keys: '',
},
{
keys: [`${mod} + 1...9`],
descriptionKey: "helpDialog.item.switchProject",
- icon: RiLayoutLeftLine,
+ icon: "layout-left",
},
{
id: 'toggle_services_menu',
descriptionKey: 'helpDialog.item.toggleServicesMenu',
- icon: RiStackLine,
+ icon: "stack",
keys: '',
},
{
id: 'cycle_services_tab',
descriptionKey: 'helpDialog.item.cycleServicesTab',
- icon: RiStackLine,
+ icon: "stack",
keys: '',
},
{
id: 'open_settings',
descriptionKey: "helpDialog.item.openSettings",
- icon: RiSettings3Line,
+ icon: "settings-3",
keys: '',
},
],
@@ -227,7 +209,7 @@ export const HelpDialog: React.FC = () => {
-
+
{t('helpDialog.title')}
@@ -255,7 +237,7 @@ export const HelpDialog: React.FC = () => {
>
{shortcut.icon && (
-
+
)}
{t(shortcut.descriptionKey)}
@@ -285,7 +267,7 @@ export const HelpDialog: React.FC = () => {
-
+
{t('helpDialog.proTips.title')}
diff --git a/packages/ui/src/components/ui/JsonTreeView.tsx b/packages/ui/src/components/ui/JsonTreeView.tsx
index ce3040d2..78d33a77 100644
--- a/packages/ui/src/components/ui/JsonTreeView.tsx
+++ b/packages/ui/src/components/ui/JsonTreeView.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { JsonTreeViewer } from './JsonTreeViewer';
import { useI18n } from '@/lib/i18n';
@@ -70,7 +70,7 @@ const JsonTreeView = React.memo(function JsonTreeView({
onClick={handleExpandAll}
className="gap-1 text-xs text-muted-foreground"
>
-
+
{t('jsonTreeView.actions.expandAll')}
-
+
{t('jsonTreeView.actions.collapseAll')}
diff --git a/packages/ui/src/components/ui/JsonTreeViewer.tsx b/packages/ui/src/components/ui/JsonTreeViewer.tsx
index e67f2c20..6e094a9d 100644
--- a/packages/ui/src/components/ui/JsonTreeViewer.tsx
+++ b/packages/ui/src/components/ui/JsonTreeViewer.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react';
import { useVirtualizer } from '@tanstack/react-virtual';
import {
@@ -10,6 +9,7 @@ import {
type JsonTreeNode,
type FlatJsonNode,
} from '@/lib/jsonTreeUtils';
+import { Icon } from "@/components/icon/Icon";
interface JsonTreeViewerProps {
data: unknown;
@@ -129,9 +129,9 @@ const JsonRow = React.memo(
className="mr-1 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm hover:bg-[var(--interactive-hover)] text-muted-foreground"
>
{isExpanded ? (
-
+
) : (
-
+
)}
) : (
diff --git a/packages/ui/src/components/ui/MemoryDebugPanel.tsx b/packages/ui/src/components/ui/MemoryDebugPanel.tsx
index dbfe5422..89e2c449 100644
--- a/packages/ui/src/components/ui/MemoryDebugPanel.tsx
+++ b/packages/ui/src/components/ui/MemoryDebugPanel.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiBarChartBoxLine, RiCloseLine, RiDatabase2Line, RiFileCopyLine, RiPulseLine, RiRefreshLine } from '@remixicon/react';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useViewportStore } from '@/sync/viewport-store';
@@ -12,6 +11,7 @@ import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface DebugPanelProps {
@@ -198,9 +198,9 @@ export const DebugPanel: React.FC
= ({ onClose }) => {
{activeTab === 'memory' ? (
-
+
) : (
-
+
)}
{t('memoryDebugPanel.title')}
@@ -208,7 +208,7 @@ export const DebugPanel: React.FC
= ({ onClose }) => {
{activeTab === 'streaming' ? (
<>
-
+
= ({ onClose }) => {
setVsCodeStreamSnapshot(getVsCodeStreamPerfSnapshot());
}}
>
-
+
>
) : null}
{onClose ? (
-
+
) : null}
@@ -290,7 +290,7 @@ export const DebugPanel: React.FC = ({ onClose }) => {
>
{stat.title}
- {stat.isStreaming ? : null}
+ {stat.isStreaming ? : null}
{stat.isZombie ? ! : null}
diff --git a/packages/ui/src/components/ui/MobileOverlayPanel.tsx b/packages/ui/src/components/ui/MobileOverlayPanel.tsx
index bb75ea10..66cdc649 100644
--- a/packages/ui/src/components/ui/MobileOverlayPanel.tsx
+++ b/packages/ui/src/components/ui/MobileOverlayPanel.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { createPortal } from 'react-dom';
-import { RiCloseLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { ScrollableOverlay } from './ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
interface MobileOverlayPanelProps {
open: boolean;
@@ -90,7 +90,7 @@ export const MobileOverlayPanel: React.FC
= ({
onClick={onClose}
className="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-interactive-hover"
>
-
+
);
diff --git a/packages/ui/src/components/ui/UpdateDialog.tsx b/packages/ui/src/components/ui/UpdateDialog.tsx
index c2a19400..44fe6ba6 100644
--- a/packages/ui/src/components/ui/UpdateDialog.tsx
+++ b/packages/ui/src/components/ui/UpdateDialog.tsx
@@ -6,7 +6,7 @@ import {
} from '@/components/ui/dialog';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
-import { RiCheckLine, RiClipboardLine, RiDownloadCloudLine, RiDownloadLine, RiExternalLinkLine, RiLoaderLine, RiRestartLine, RiTerminalLine } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import type { UpdateInfo, UpdateProgress } from '@/lib/desktop';
import { copyTextToClipboard } from '@/lib/clipboard';
@@ -109,7 +109,6 @@ function parseChangelogSections(body: string): ChangelogSection[] {
});
}
-
type InstallWebUpdateResult = {
success: boolean;
error?: string;
@@ -305,7 +304,7 @@ export const UpdateDialog: React.FC = ({
{/* Header Section */}
-
+
{webUpdateState === 'restarting' || webUpdateState === 'reconnecting'
? t('updateDialog.header.updating')
@@ -336,7 +335,7 @@ export const UpdateDialog: React.FC = ({
{isWebRuntime && isWebUpdating && (
-
+
{webUpdateState === 'updating' && t('updateDialog.status.installingUpdate')}
{webUpdateState === 'restarting' && t('updateDialog.status.serverRestarting')}
@@ -409,7 +408,7 @@ export const UpdateDialog: React.FC
= ({
{isWebRuntime && webUpdateState === 'error' && (
-
+
{t('updateDialog.fallback.updateViaTerminal')}
@@ -427,9 +426,9 @@ export const UpdateDialog: React.FC = ({
title={copied ? t('updateDialog.actions.copied') : t('updateDialog.actions.copyCommand')}
>
{copied ? (
-
+
) : (
-
+
)}
@@ -468,7 +467,7 @@ export const UpdateDialog: React.FC
= ({
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors shrink-0"
>
-
+
GitHub
@@ -479,7 +478,7 @@ export const UpdateDialog: React.FC = ({
onClick={onDownload}
className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)] text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
-
+
{t('updateDialog.actions.downloadUpdate')}
)}
@@ -489,7 +488,7 @@ export const UpdateDialog: React.FC = ({
disabled
className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)]/50 text-[var(--primary-foreground)] cursor-not-allowed"
>
-
+
{t('updateDialog.status.downloading')}
)}
@@ -499,7 +498,7 @@ export const UpdateDialog: React.FC = ({
onClick={onRestart}
className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--status-success)] text-white hover:opacity-90 transition-opacity"
>
-
+
{t('updateDialog.actions.restartToUpdate')}
)}
@@ -510,7 +509,7 @@ export const UpdateDialog: React.FC = ({
onClick={handleWebUpdate}
className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)] text-[var(--primary-foreground)] hover:opacity-90 transition-opacity"
>
-
+
{t('updateDialog.actions.updateNow')}
)}
@@ -520,7 +519,7 @@ export const UpdateDialog: React.FC = ({
disabled
className="flex items-center justify-center gap-2 px-5 py-2 rounded-md text-sm font-medium bg-[var(--primary-base)]/50 text-[var(--primary-foreground)] cursor-not-allowed"
>
-
+
{t('updateDialog.status.updating')}
)}
diff --git a/packages/ui/src/components/ui/checkbox.tsx b/packages/ui/src/components/ui/checkbox.tsx
index 4e207bab..8b849868 100644
--- a/packages/ui/src/components/ui/checkbox.tsx
+++ b/packages/ui/src/components/ui/checkbox.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Checkbox as BaseCheckbox } from '@base-ui/react/checkbox';
-import { RiCheckLine, RiSubtractLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
+import { Icon } from "@/components/icon/Icon";
interface CheckboxProps {
checked: boolean;
@@ -15,7 +15,6 @@ interface CheckboxProps {
size?: 'sm' | 'default';
}
-
export const Checkbox = React.memo(function Checkbox({
checked,
onChange,
@@ -62,9 +61,9 @@ export const Checkbox = React.memo(function Checkbox({
)}
>
{indeterminate ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/ui/command.tsx b/packages/ui/src/components/ui/command.tsx
index 1ede73e0..ba3c63ff 100644
--- a/packages/ui/src/components/ui/command.tsx
+++ b/packages/ui/src/components/ui/command.tsx
@@ -2,10 +2,10 @@
import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
-import { RiCommandLine, RiArrowUpLine, RiSearchLine } from "@remixicon/react";
import { cn } from "@/lib/utils"
import { ScrollableOverlay } from "@/components/ui/ScrollableOverlay";
+import { Icon } from "@/components/icon/Icon";
import {
Dialog,
DialogContent,
@@ -79,7 +79,7 @@ const CommandInput = React.forwardRef<
data-slot="command-input-wrapper"
className="flex h-8 items-center gap-2 border-b px-3"
>
-
+
;
+ return ;
}
if (normalized === 'shift' || normalized === '⇧') {
- return ;
+ return ;
}
return (
diff --git a/packages/ui/src/components/ui/dialog.tsx b/packages/ui/src/components/ui/dialog.tsx
index 906783c9..06bb166a 100644
--- a/packages/ui/src/components/ui/dialog.tsx
+++ b/packages/ui/src/components/ui/dialog.tsx
@@ -1,9 +1,9 @@
import * as React from "react"
import { Dialog as BaseDialog } from "@base-ui/react/dialog"
-import { RiCloseLine } from '@remixicon/react';
import { cn } from "@/lib/utils"
import { useI18n } from '@/lib/i18n'
+import { Icon } from "@/components/icon/Icon";
let openDialogCount = 0;
@@ -122,7 +122,7 @@ function DialogContent({
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[open]:bg-interactive-active data-[open]:text-foreground absolute top-2 right-2 rounded-lg opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none text-muted-foreground hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
-
+
{t('dialog.common.actions.close')}
)}
diff --git a/packages/ui/src/components/ui/dropdown-menu.tsx b/packages/ui/src/components/ui/dropdown-menu.tsx
index 25d7e15a..0125e0cc 100644
--- a/packages/ui/src/components/ui/dropdown-menu.tsx
+++ b/packages/ui/src/components/ui/dropdown-menu.tsx
@@ -1,8 +1,8 @@
import * as React from "react"
import { Menu as BaseMenu } from "@base-ui/react/menu"
-import { RiArrowRightSLine, RiCheckLine } from '@remixicon/react';
import { cn } from "@/lib/utils"
+import { Icon } from "@/components/icon/Icon";
type AsChildProps = { asChild?: boolean };
type AsChildRenderProps = {
@@ -203,7 +203,7 @@ function DropdownMenuCheckboxItem({
>
-
+
{children}
@@ -233,7 +233,7 @@ function DropdownMenuRadioItem({
>
-
+
{children}
@@ -315,7 +315,7 @@ function DropdownMenuSubTrigger({
{...props}
>
{children}
-
+
)
}
diff --git a/packages/ui/src/components/ui/number-input.tsx b/packages/ui/src/components/ui/number-input.tsx
index c10955bd..07b2a8d3 100644
--- a/packages/ui/src/components/ui/number-input.tsx
+++ b/packages/ui/src/components/ui/number-input.tsx
@@ -1,9 +1,9 @@
import * as React from "react"
-import { RiAddLine, RiSubtractLine } from "@remixicon/react"
import { useDeviceInfo } from "@/lib/device"
import { useI18n } from "@/lib/i18n"
import { cn } from "@/lib/utils"
+import { Icon } from "@/components/icon/Icon";
export interface NumberInputProps
extends Omit, "value" | "onChange" | "type"> {
@@ -224,7 +224,7 @@ const NumberInput = React.forwardRef(
!decrementDisabled && "active:bg-interactive-hover"
)}
>
-
+
(
!incrementDisabled && "active:bg-interactive-hover"
)}
>
-
+
)
@@ -276,7 +276,7 @@ const NumberInput = React.forwardRef(
"disabled:pointer-events-none disabled:opacity-50"
)}
>
-
+
(
"disabled:pointer-events-none disabled:opacity-50"
)}
>
-
+
)
diff --git a/packages/ui/src/components/ui/select.tsx b/packages/ui/src/components/ui/select.tsx
index cfcfaa40..fb8eec4e 100644
--- a/packages/ui/src/components/ui/select.tsx
+++ b/packages/ui/src/components/ui/select.tsx
@@ -3,10 +3,10 @@
import * as React from "react"
import { Select as BaseSelect } from "@base-ui/react/select"
import type { SelectRootChangeEventDetails } from "@base-ui/react/select";
-import { RiArrowDownSLine, RiArrowUpSLine, RiCheckLine } from '@remixicon/react';
import { cn } from "@/lib/utils"
import { ScrollableOverlay } from "@/components/ui/ScrollableOverlay";
+import { Icon } from "@/components/icon/Icon";
type AsChildProps = { asChild?: boolean };
type AsChildRenderProps = {
@@ -145,7 +145,7 @@ function SelectTrigger({
{asChildRender ? undefined : (<>
{children}
-
+
>)}
@@ -249,7 +249,7 @@ function SelectItem({
>
-
+
{children}
@@ -283,7 +283,7 @@ function SelectScrollUpButton({
)}
{...props}
>
-
+
)
}
@@ -301,7 +301,7 @@ function SelectScrollDownButton({
)}
{...props}
>
-
+
)
}
diff --git a/packages/ui/src/components/ui/sortable-tabs-strip.tsx b/packages/ui/src/components/ui/sortable-tabs-strip.tsx
index 091da0cc..1ee67178 100644
--- a/packages/ui/src/components/ui/sortable-tabs-strip.tsx
+++ b/packages/ui/src/components/ui/sortable-tabs-strip.tsx
@@ -15,12 +15,12 @@ import {
useSortable,
} from '@dnd-kit/sortable';
import { CSS as DndCSS } from '@dnd-kit/utilities';
-import { RiCloseLine } from '@remixicon/react';
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/useUIStore';
import { useDeviceInfo } from '@/lib/device';
+import { Icon } from "@/components/icon/Icon";
export type SortableTabsStripItem = {
id: string;
@@ -122,7 +122,6 @@ export const SortableTabsStrip: React.FC = ({
const tabRefs = React.useRef>(new Map());
const [pillRect, setPillRect] = React.useState<{ left: number; top: number; width: number; height: number } | null>(null);
-
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
);
@@ -256,8 +255,6 @@ export const SortableTabsStrip: React.FC = ({
updateActivePillRect();
});
-
-
React.useEffect(() => {
if (!isScrollable || !activeId) {
return;
@@ -457,7 +454,7 @@ export const SortableTabsStrip: React.FC = ({
aria-label={item.closeLabel ?? `Close ${item.label} tab`}
title={item.closeLabel ?? `Close ${item.label} tab`}
>
-
+
) : null}
@@ -489,7 +486,7 @@ export const SortableTabsStrip: React.FC = ({
aria-label={item.closeLabel ?? `Close ${item.label} tab`}
title={item.closeLabel ?? `Close ${item.label} tab`}
>
-
+
) : null}
@@ -524,7 +521,7 @@ export const SortableTabsStrip: React.FC = ({
aria-label={item.closeLabel ?? `Close ${item.label} tab`}
title={item.closeLabel ?? `Close ${item.label} tab`}
>
-
+
) : null}
diff --git a/packages/ui/src/components/views/DiffView.tsx b/packages/ui/src/components/views/DiffView.tsx
index a606f571..96e05bc6 100644
--- a/packages/ui/src/components/views/DiffView.tsx
+++ b/packages/ui/src/components/views/DiffView.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiArrowDownSLine, RiArrowRightSLine, RiEditLine, RiGitCommitLine, RiLoader4Line, RiTextWrap } from '@remixicon/react';
import { useUIStore } from '@/stores/useUIStore';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
@@ -26,6 +25,7 @@ import type { DiffViewMode } from '@/components/chat/message/types';
import { PierreDiffViewer } from './PierreDiffViewer';
import { useDeviceInfo } from '@/lib/device';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
+import { Icon } from "@/components/icon/Icon";
import { getContextFileOpenFailureMessage, validateContextFileOpen } from '@/lib/contextFileOpenGuard';
import { sessionEvents } from '@/lib/sessionEvents';
import { useI18n } from '@/lib/i18n';
@@ -232,7 +232,7 @@ const FileSelector = React.memo
(({
) : (
{t('diffView.selector.selectFile')}
)}
-
+
@@ -297,7 +297,7 @@ const DiffViewModeSelector = React.memo(({ mode, onMo
{t(currentOption.labelKey)}
-
+
@@ -788,9 +788,9 @@ const MultiFileDiffEntry = React.memo(({
{isExpanded ? (
-
+
) : (
-
+
)}
(({
disabled={isOpeningInEditor}
>
{isOpeningInEditor ? (
-
+
) : (
-
+
)}
) : null}
@@ -895,7 +895,7 @@ const MultiFileDiffEntry = React.memo(({
) : null}
{isLoading && !diffData && !diffLoadError ? (
-
+
Loading diff…
) : null}
@@ -987,7 +987,6 @@ export const DiffView: React.FC = ({
const pendingScrollFrameRef = React.useRef(null);
const shouldPinAfterAlignRef = React.useRef(false);
-
React.useEffect(() => {
if (!pinSelectedFileHeaderToTopOnNavigate || !isStackedView || !pinnedStackedTarget) {
return;
@@ -1630,7 +1629,7 @@ export const DiffView: React.FC = ({
if (isLoadingStatus && !status) {
return (
-
+
{t('diffView.state.loadingRepositoryStatus')}
);
@@ -1682,7 +1681,7 @@ export const DiffView: React.FC = ({
) : (
<>
-
+
{t('diffView.state.loadingDiff')}
>
)}
@@ -1697,7 +1696,7 @@ export const DiffView: React.FC = ({
{!isMobile && (
-
+
{isLoadingStatus && !status
? t('diffView.state.loadingChanges')
@@ -1734,7 +1733,7 @@ export const DiffView: React.FC = ({
)}
title={diffWrapLines ? t('diffView.actions.disableLineWrap') : t('diffView.actions.enableLineWrap')}
>
-
+
)}
{showOpenInEditorAction && selectedFileEntry && !isStackedView && (
@@ -1749,9 +1748,9 @@ export const DiffView: React.FC = ({
title={t('diffView.actions.openFileAtFirstChangedLine')}
>
{isOpeningSelectedInEditor ? (
-
+
) : (
-
+
)}
)}
diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx
index 2ce512e9..8d153bc1 100644
--- a/packages/ui/src/components/views/FilesView.tsx
+++ b/packages/ui/src/components/views/FilesView.tsx
@@ -1,36 +1,5 @@
import React from 'react';
-import {
- RiArrowLeftSLine,
- RiArrowDownSLine,
- RiClipboardLine,
- RiCloseLine,
- RiFileCopy2Line,
- RiCheckLine,
- RiFolder3Fill,
- RiFolderOpenFill,
- RiFolderReceivedLine,
- RiFileCheckFill,
- RiFileCheckLine,
- RiFullscreenExitLine,
- RiFullscreenLine,
- RiLoader4Line,
- RiRefreshLine,
- RiSearchLine,
- RiSave3Line,
- RiTextWrap,
- RiMore2Fill,
- RiFileAddLine,
- RiFolderAddLine,
- RiDeleteBinLine,
- RiEditLine,
- RiFileCopyLine,
- RiFileTransferLine,
- RiCodeSSlashLine,
- RiNodeTree,
- RiDownloadLine,
- RiMenuFold2Line,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { copyTextToClipboard } from '@/lib/clipboard';
@@ -81,6 +50,7 @@ import { useFilesViewShowGitignored } from '@/lib/filesViewShowGitignored';
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
+import { Icon } from "@/components/icon/Icon";
import { ensurePierreThemeRegistered } from '@/lib/shiki/appThemeRegistry';
import { getDefaultTheme } from '@/lib/theme/themes';
import { openDesktopFileInApp, openDesktopPath } from '@/lib/desktop';
@@ -388,9 +358,9 @@ const FileRow: React.FC = ({
>
{isDir ? (
isExpanded ? (
-
+
) : (
-
+
)
) : (
getFileIcon(node.path, node.extension)
@@ -425,13 +395,13 @@ const FileRow: React.FC = ({
className="h-6 w-6"
onClick={handleMenuButtonClick}
>
-
+
setContextMenuPath(null)}>
{canRename && (
{ e.stopPropagation(); onOpenDialog('rename', node); }}>
- {t('sidebarFilesTree.menu.rename')}
+ {t('sidebarFilesTree.menu.rename')}
)}
{
@@ -444,7 +414,7 @@ const FileRow: React.FC = ({
toast.error(t('sidebarFilesTree.toast.copyFailed'));
});
}}>
- {t('sidebarFilesTree.menu.copyPath')}
+ {t('sidebarFilesTree.menu.copyPath')}
{
e.stopPropagation();
@@ -457,19 +427,19 @@ const FileRow: React.FC = ({
toast.error(t('sidebarFilesTree.toast.copyFailed'));
});
}}>
- {t('filesView.tree.menu.copyRelativePath')}
+ {t('filesView.tree.menu.copyRelativePath')}
{!isDir && downloadFile && (
{
e.stopPropagation();
void downloadFile(node.path);
}}>
- {t('sidebarFilesTree.menu.save')}
+ {t('sidebarFilesTree.menu.save')}
)}
{canReveal && (
{ e.stopPropagation(); onRevealPath(node.path); }}>
- {t(getRevealLabelKey())}
+ {t(getRevealLabelKey())}
)}
{isDir && (canCreateFile || canCreateFolder) && (
@@ -477,12 +447,12 @@ const FileRow: React.FC = ({
{canCreateFile && (
{ e.stopPropagation(); onOpenDialog('createFile', node); }}>
- {t('sidebarFilesTree.menu.newFile')}
+ {t('sidebarFilesTree.menu.newFile')}
)}
{canCreateFolder && (
{ e.stopPropagation(); onOpenDialog('createFolder', node); }}>
- {t('sidebarFilesTree.menu.newFolder')}
+ {t('sidebarFilesTree.menu.newFolder')}
)}
>
@@ -494,7 +464,7 @@ const FileRow: React.FC = ({
onClick={(e) => { e.stopPropagation(); onOpenDialog('delete', node); }}
className="text-destructive focus:text-destructive"
>
- {t('sidebarFilesTree.menu.delete')}
+ {t('sidebarFilesTree.menu.delete')}
>
)}
@@ -2418,9 +2388,6 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
}).toString()}`))
: '';
-
-
-
React.useEffect(() => {
let cancelled = false;
@@ -2507,7 +2474,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
onClick={() => void handleDialogSubmit()}
disabled={isDialogSubmitting || (activeDialog !== 'delete' && !dialogInputValue.trim())}
>
- {isDialogSubmitting ? : (
+ {isDialogSubmitting ? : (
activeDialog === 'delete' ? t('filesView.dialog.delete.confirm') : t('filesView.dialog.confirm')
)}
@@ -2572,12 +2539,12 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
<>
{isSaving ? (
-
+
{t('filesView.editor.saving')}
) : autoSaveEnabled && autoSaveStatus === 'saved' && !isDirty ? (
-
+
{t('filesView.editor.saved')}
) : isDirty ? (
@@ -2589,7 +2556,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
title={t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: `${getModifierLabel()}+S` })}
aria-label={t('filesView.editor.saveAria', { shortcut: `${getModifierLabel()}+S` })}
>
-
+
) : null}
= ({ mode = 'full' }) => {
title={autoSaveEnabled ? t('filesView.editor.autoSaveOn') : t('filesView.editor.manualSave')}
aria-label={autoSaveEnabled ? t('filesView.editor.autoSaveOn') : t('filesView.editor.manualSave')}
>
- {autoSaveEnabled ? : }
+ {autoSaveEnabled ? : }
>
)}
@@ -2617,7 +2584,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
title={t('filesView.editor.openInDesktopApp')}
aria-label={t('filesView.editor.openInDesktopApp')}
>
-
+
@@ -2636,7 +2603,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
className="flex items-center gap-2"
onClick={() => void loadOpenInApps(true)}
>
-
+
{t('filesView.editor.refreshApps')}
) : null}
@@ -2655,7 +2622,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
)}
title={wrapLines ? t('filesView.editor.disableLineWrap') : t('filesView.editor.enableLineWrap')}
>
-
+
{textViewMode === 'edit' && (
<>
@@ -2669,7 +2636,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
className="h-6 w-6 p-0 text-foreground opacity-100 transition-opacity hover:bg-transparent focus-visible:bg-transparent active:bg-transparent"
title={t('filesView.editor.findInFile')}
>
-
+
= ({ mode = 'full' }) => {
className="h-6 w-6 p-0 text-foreground opacity-100 transition-opacity hover:bg-transparent focus-visible:bg-transparent active:bg-transparent"
title={t('filesView.editor.goToLine')}
>
-
+
= ({ mode = 'full' }) => {
title={jsonViewMode === 'tree' ? t('filesView.editor.switchToTextView') : t('filesView.editor.switchToTreeView')}
>
{jsonViewMode === 'tree' ? (
-
+
) : (
-
+
)}
)}
@@ -2755,9 +2722,9 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
aria-label={t('filesView.editor.copyFileContents')}
>
{copiedContent ? (
-
+
) : (
-
+
)}
)}
@@ -2785,9 +2752,9 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
aria-label={t('filesView.editor.copyFilePathTitle', { path: displaySelectedPath })}
>
{copiedPath ? (
-
+
) : (
-
+
)}
)}
@@ -2804,7 +2771,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
title={t('filesView.editor.saveFile')}
aria-label={t('filesView.editor.saveFile')}
>
-
+
)}
@@ -2817,7 +2784,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
title={t('filesView.editor.exitFullscreen')}
aria-label={t('filesView.editor.exitFullscreen')}
>
-
+
) : (!isMobile && mode === 'full' && (
= ({ mode = 'full' }) => {
aria-label={isFullscreen ? t('filesView.editor.exitFullscreen') : t('filesView.editor.fullscreen')}
>
{isFullscreen ? (
-
+
) : (
-
+
)}
))}
@@ -2880,7 +2847,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
aria-label={t('filesView.editor.back')}
className="inline-flex h-7 w-7 flex-shrink-0 items-center justify-center mr-1 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
)}
@@ -2895,7 +2862,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
>
{selectedFile.name}
-
+
@@ -2938,7 +2905,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-[var(--surface-muted-foreground)] hover:text-[var(--surface-foreground)]"
aria-label={t('filesView.editor.closeFileAria', { name: file.name })}
>
-
+
);
@@ -2999,7 +2966,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
)}
aria-label={t('filesView.editor.closeFileAria', { name: file.name })}
>
-
+
);
@@ -3037,7 +3004,7 @@ export const FilesView: React.FC
= ({ mode = 'full' }) => {
aria-label={t('filesView.editor.showControlsAria')}
title={t('filesView.editor.controlsTitle')}
>
-
+
)}
@@ -3050,7 +3017,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
?
: (
-
+
{t('filesView.state.loading')}
)
@@ -3228,7 +3195,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
{shouldMaskEditorForPendingNavigation && (
-
+
{t('filesView.state.openingFileAtChange')}
@@ -3250,7 +3217,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
@@ -3300,7 +3267,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
{searching ? (
-
+
{t('filesView.tree.search.searching')}
) : searchResults.length > 0 ? (
@@ -3352,7 +3319,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
?
: (
-
+
Loading…
)
@@ -3417,7 +3384,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
{shouldMaskEditorForPendingNavigation && (
-
+
{t('filesView.state.openingFileAtChange')}
diff --git a/packages/ui/src/components/views/GitView.tsx b/packages/ui/src/components/views/GitView.tsx
index 71458b4d..1a368b6a 100644
--- a/packages/ui/src/components/views/GitView.tsx
+++ b/packages/ui/src/components/views/GitView.tsx
@@ -19,13 +19,6 @@ import {
} from '@/stores/useGitStore';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
import { ScrollShadow } from '@/components/ui/ScrollShadow';
-import {
- RiGitBranchLine,
- RiGitMergeLine,
- RiGitCommitLine,
- RiGitPullRequestLine,
- RiLoader4Line,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import {
Dialog,
@@ -43,6 +36,7 @@ import {
CommandItem,
CommandList,
} from '@/components/ui/command';
+import { Icon } from "@/components/icon/Icon";
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useUIStore } from '@/stores/useUIStore';
@@ -84,7 +78,6 @@ const GIT_ACTION_TAB_STORAGE_KEY = 'oc.git.actionTab';
const isActionTab = (value: unknown): value is ActionTab =>
value === 'commit' || value === 'branch' || value === 'pr';
-
type GitViewSnapshot = {
directory?: string;
selectedPaths: string[];
@@ -521,9 +514,9 @@ export const GitView: React.FC = () => {
const [isHistoryDialogOpen, setIsHistoryDialogOpen] = React.useState(false);
const actionTabItems = React.useMemo(() => [
- { id: 'commit', label: t('gitView.tabs.commit'), icon: },
- { id: 'branch', label: t('gitView.tabs.update'), icon: },
- { id: 'pr', label: t('gitView.tabs.pr'), icon: },
+ { id: 'commit', label: t('gitView.tabs.commit'), icon: },
+ { id: 'branch', label: t('gitView.tabs.update'), icon: },
+ { id: 'pr', label: t('gitView.tabs.pr'), icon: },
], [t]);
const [actionTab, setActionTab] = React.useState(() => {
if (typeof window === 'undefined') {
@@ -884,7 +877,6 @@ export const GitView: React.FC = () => {
};
}, [changeEntries, currentDirectory, git, prefetchDiffs, selectedPaths, visibleChangePaths]);
-
React.useEffect(() => {
if (!status || changeEntries.length === 0) {
setSelectedPaths(new Set());
@@ -2101,7 +2093,7 @@ export const GitView: React.FC = () => {
return (
-
+
{t('gitView.loading.checkingRepository')}
@@ -2112,7 +2104,7 @@ export const GitView: React.FC = () => {
if (shouldHideNotGitState) {
return (
-
+
{t('gitView.empty.worktreeSetupInProgress')}
@@ -2125,7 +2117,7 @@ export const GitView: React.FC = () => {
return (
-
+
{t('gitView.empty.notGitRepository')}
diff --git a/packages/ui/src/components/views/MultiRunWindow.tsx b/packages/ui/src/components/views/MultiRunWindow.tsx
index 898c6894..47042cd7 100644
--- a/packages/ui/src/components/views/MultiRunWindow.tsx
+++ b/packages/ui/src/components/views/MultiRunWindow.tsx
@@ -1,8 +1,8 @@
import React from 'react';
import { Dialog } from '@base-ui/react/dialog';
-import { RiCloseLine } from '@remixicon/react';
import { cn } from '@/lib/utils';
import { MultiRunLauncher } from '@/components/multirun';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface MultiRunWindowProps {
@@ -65,7 +65,7 @@ export const MultiRunWindow: React.FC
= ({
aria-label={t('multiRun.window.actions.closeAria')}
className="inline-flex h-7 w-7 items-center justify-center rounded-md p-0.5 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
diff --git a/packages/ui/src/components/views/PlanView.tsx b/packages/ui/src/components/views/PlanView.tsx
index 0dd0a3ce..0b3071e8 100644
--- a/packages/ui/src/components/views/PlanView.tsx
+++ b/packages/ui/src/components/views/PlanView.tsx
@@ -20,7 +20,6 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator';
import { createFlexokiCodeMirrorTheme } from '@/lib/codemirror/flexokiTheme';
import { languageByExtension } from '@/lib/codemirror/languageByExtension';
-import { RiCheckLine, RiClipboardLine, RiCodeAiLine, RiLoopRightAiLine } from '@remixicon/react';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSessions } from '@/sync/sync-context';
import { useDirectoryStore } from '@/stores/useDirectoryStore';
@@ -39,6 +38,7 @@ import { generateBranchName } from '@/lib/git/branchNameGenerator';
import { parseProjectPlanMarkdown } from '@/lib/openchamberConfig';
import { createWorktreeSessionForNewBranch } from '@/lib/worktreeSessionCreator';
import { TodoSendDialog, type TodoSendExecution } from '@/components/session/TodoSendDialog';
+import { Icon } from "@/components/icon/Icon";
import { renderMagicPrompt } from '@/lib/magicPrompts';
import { useI18n } from '@/lib/i18n';
@@ -338,7 +338,6 @@ export const PlanView: React.FC = ({ targetPath = null }) => {
};
}, [cancel, editingDraftId, isMobile, lineSelection]);
-
const editorExtensions = React.useMemo(() => {
const extensions = [createFlexokiCodeMirrorTheme(currentTheme)];
const language = languageByExtension(resolvedPath || 'plan.md');
@@ -638,7 +637,7 @@ export const PlanView: React.FC = ({ targetPath = null }) => {
aria-label={t('planView.actions.improvePlanAria')}
disabled={!content.trim()}
>
-
+
@@ -667,7 +666,7 @@ export const PlanView: React.FC = ({ targetPath = null }) => {
aria-label={t('planView.actions.implementPlanAria')}
disabled={!content.trim()}
>
-
+
@@ -711,9 +710,9 @@ export const PlanView: React.FC = ({ targetPath = null }) => {
aria-label={t('planView.actions.copyPlanContents')}
>
{copiedContent ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/views/PreviewToggleButton.tsx b/packages/ui/src/components/views/PreviewToggleButton.tsx
index e42451da..77404afc 100644
--- a/packages/ui/src/components/views/PreviewToggleButton.tsx
+++ b/packages/ui/src/components/views/PreviewToggleButton.tsx
@@ -1,9 +1,8 @@
import React from 'react';
-import { RiEyeLine, RiEyeOffLine } from '@remixicon/react';
-
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
export type PreviewToggleButtonProps = {
/** Current mode - determines which icon is displayed */
@@ -37,9 +36,9 @@ export const PreviewToggleButton: React.FC = ({
className="h-5 w-5 p-0"
>
{isPreview ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/views/SettingsView.tsx b/packages/ui/src/components/views/SettingsView.tsx
index 58206bac..d7912bc9 100644
--- a/packages/ui/src/components/views/SettingsView.tsx
+++ b/packages/ui/src/components/views/SettingsView.tsx
@@ -7,31 +7,6 @@ import { useCommandsStore } from '@/stores/useCommandsStore';
import { useMcpConfigStore } from '@/stores/useMcpConfigStore';
import { useSkillsStore } from '@/stores/useSkillsStore';
import { useSkillsCatalogStore } from '@/stores/useSkillsCatalogStore';
-import {
- RiAiAgentLine,
- RiAiGenerate2,
- RiArrowLeftSLine,
- RiBarChart2Line,
- RiBookLine,
- RiBookOpenLine,
- RiChatAi3Line,
- RiChatHistoryLine,
- RiCloseLine,
- RiCommandLine,
- RiCloudLine,
- RiFoldersLine,
- RiGitBranchLine,
- RiGlobalLine,
- RiMicLine,
- RiListUnordered,
- RiNotification3Line,
- RiPaletteLine,
- RiRobot2Line,
- RiRestartLine,
- RiServerLine,
- RiSlashCommands2,
- RiBrainLine,
-} from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
import { AgentsSidebar } from '@/components/sections/agents/AgentsSidebar';
@@ -56,10 +31,11 @@ import { MagicPromptsPage } from '@/components/sections/magic-prompts/MagicPromp
import { GitPage } from '@/components/sections/git-identities/GitPage';
import type { OpenChamberSection } from '@/components/sections/openchamber/types';
import { OpenChamberPage } from '@/components/sections/openchamber/OpenChamberPage';
-import { McpIcon } from '@/components/icons/McpIcon';
import { useDeviceInfo } from '@/lib/device';
import { isDesktopShell, isVSCodeRuntime, isWebRuntime } from '@/lib/desktop';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
+import type { IconName } from "@/components/icon/icons";
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import {
SETTINGS_PAGE_METADATA,
@@ -125,54 +101,54 @@ function isPageAvailable(page: SettingsPageMeta, ctx: SettingsRuntimeContext): b
}
// eslint-disable-next-line react-refresh/only-export-components
-export function getSettingsNavIcon(slug: SettingsPageSlug): React.ComponentType<{ className?: string }> | null {
+export function getSettingsNavIcon(slug: SettingsPageSlug): IconName | null {
switch (slug) {
case 'projects':
- return RiFoldersLine;
+ return 'folders';
case 'remote-instances':
- return RiServerLine;
+ return 'server';
case 'appearance':
- return RiPaletteLine;
+ return 'palette';
case 'chat':
- return RiChatAi3Line;
+ return 'chat-ai-3';
case 'magic-prompts':
- return RiAiGenerate2;
+ return 'ai-generate-2';
case 'notifications':
- return RiNotification3Line;
+ return 'notification-3';
case 'shortcuts':
- return RiCommandLine;
+ return 'command';
case 'sessions':
- return RiChatHistoryLine;
+ return 'chat-history';
case 'providers':
- return RiCloudLine;
+ return 'cloud';
case 'agents':
- return RiAiAgentLine;
+ return 'ai-agent';
case 'behavior':
- return RiBrainLine;
+ return 'brain';
case 'commands':
- return RiSlashCommands2;
+ return 'slash-commands-2';
case 'mcp':
- return McpIcon;
+ return 'plug-2';
case 'skills.installed':
- return RiBookOpenLine;
+ return 'book-open';
case 'skills.catalog':
- return RiBookLine;
+ return 'book';
case 'git':
- return RiGitBranchLine;
+ return 'git-branch';
case 'usage':
- return RiBarChart2Line;
+ return 'bar-chart-2';
case 'voice':
- return RiMicLine;
+ return 'mic';
case 'tunnel':
- return RiGlobalLine;
+ return 'global';
case 'home':
return null;
default:
- return RiRobot2Line;
+ return 'robot-2';
}
}
@@ -584,8 +560,8 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
{sortedFilteredPages.map((page) => {
const selected = settingsSlug === page.slug;
- const Icon = getSettingsNavIcon(page.slug);
- if (!Icon) return null;
+ const iconName = getSettingsNavIcon(page.slug);
+ if (!iconName) return null;
return (
@@ -601,7 +577,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
: 'text-foreground hover:bg-interactive-hover'
)}
>
-
+
{getPageTitle(page.slug)}
{(page.slug === 'voice' || page.slug === 'tunnel') && (
@@ -633,7 +609,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
)}
onClick={() => void reloadOpenCodeConfiguration({ message: 'Restarting OpenCode…', mode: 'projects', scopes: ['all'] })}
>
-
+
{t('settings.view.actions.reloadOpenCode')}
@@ -734,7 +710,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
aria-label={showBackButton ? t('settings.view.actions.backToSettings') : t('settings.view.actions.closeSettings')}
className="inline-flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
@@ -750,7 +726,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
aria-label={t('settings.view.actions.openSectionList')}
className="inline-flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
)}
@@ -762,7 +738,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
title={t('settings.view.actions.closeSettingsWithShortcut', { shortcut: shortcutKey })}
className="inline-flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
)}
@@ -776,7 +752,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
aria-label={t('settings.view.actions.back')}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
)}
@@ -790,7 +766,7 @@ export const SettingsView: React.FC = ({ onClose, forceMobile
title={t('settings.view.actions.closeSettingsWithShortcut', { shortcut: shortcutKey })}
className="inline-flex h-7 w-7 items-center justify-center rounded-md p-0.5 text-muted-foreground hover:text-foreground hover:bg-interactive-hover/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary"
>
-
+
)}
diff --git a/packages/ui/src/components/views/TerminalView.tsx b/packages/ui/src/components/views/TerminalView.tsx
index 8ddb99c0..a9490f97 100644
--- a/packages/ui/src/components/views/TerminalView.tsx
+++ b/packages/ui/src/components/views/TerminalView.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiAddLine, RiArrowDownLine, RiArrowGoBackLine, RiArrowLeftLine, RiArrowRightLine, RiArrowUpLine, RiCloseLine, RiCommandLine, RiFullscreenExitLine, RiFullscreenLine, RiGlobalLine, RiTerminalLine } from '@remixicon/react';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useTerminalStore } from '@/stores/useTerminalStore';
@@ -14,6 +13,7 @@ import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/useUIStore';
import { Button } from '@/components/ui/button';
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
+import { Icon } from "@/components/icon/Icon";
import { useDeviceInfo } from '@/lib/device';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { primeTerminalInputTransport } from '@/lib/terminalApi';
@@ -45,7 +45,6 @@ const MODIFIER_ARROW_SUFFIX: Record
= {
cmd: '3',
};
-
const STREAM_OPTIONS = {
retry: {
maxRetries: 3,
@@ -142,8 +141,8 @@ export const TerminalView: React.FC = () => {
const terminalTabItems = React.useMemo(() => {
return (directoryTerminalState?.tabs ?? []).map((tab) => ({
icon: (() => {
- const Icon = tab.iconKey ? PROJECT_ACTION_ICON_MAP[tab.iconKey as ProjectActionIconKey] ?? RiTerminalLine : RiTerminalLine;
- return ;
+ const tabIconName = tab.iconKey ? PROJECT_ACTION_ICON_MAP[tab.iconKey as ProjectActionIconKey] ?? 'terminal' : 'terminal';
+ return ;
})(),
id: tab.id,
label: tab.label,
@@ -649,7 +648,6 @@ export const TerminalView: React.FC = () => {
[activeTabId, closeTab, disconnectStream, effectiveDirectory]
);
-
const handleViewportInput = React.useCallback(
(data: string) => {
if (!data || isReconnectPending) {
@@ -958,7 +956,7 @@ export const TerminalView: React.FC = () => {
onClick={() => handleMobileKeyPress('tab')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.tabAria')}
{
onClick={() => handleModifierToggle('cmd')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.commandModifierAria')}
{
onClick={() => handleMobileKeyPress('arrow-up')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.arrowUpAria')}
{
onClick={() => handleMobileKeyPress('arrow-left')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.arrowLeftAria')}
{
onClick={() => handleMobileKeyPress('arrow-down')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.arrowDownAria')}
{
onClick={() => handleMobileKeyPress('arrow-right')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.arrowRightAria')}
{
onClick={() => handleMobileKeyPress('enter')}
disabled={quickKeysDisabled}
>
-
+
{t('terminalView.quickKeys.enterAria')}
>
@@ -1068,7 +1066,7 @@ export const TerminalView: React.FC = () => {
onClick={handleCreateTab}
title={t('terminalView.tabs.newTabTitle')}
>
-
+
@@ -1084,7 +1082,7 @@ export const TerminalView: React.FC = () => {
}}
title={t('terminalView.preview.openTitle')}
>
-
+
{t('terminalView.preview.open')}
) : null}
@@ -1099,7 +1097,7 @@ export const TerminalView: React.FC = () => {
title={isBottomTerminalExpanded ? t('terminalView.bottomDock.restoreTitle') : t('terminalView.bottomDock.expandTitle')}
aria-label={isBottomTerminalExpanded ? t('terminalView.bottomDock.restoreAria') : t('terminalView.bottomDock.expandAria')}
>
- {isBottomTerminalExpanded ?
:
}
+ {isBottomTerminalExpanded ?
:
}
{
title={t('terminalView.bottomDock.closeTitle')}
aria-label={t('terminalView.bottomDock.closeAria')}
>
-
+
>
) : null}
diff --git a/packages/ui/src/components/views/agent-manager/AgentGroupDetail.tsx b/packages/ui/src/components/views/agent-manager/AgentGroupDetail.tsx
index d9e03e30..807da526 100644
--- a/packages/ui/src/components/views/agent-manager/AgentGroupDetail.tsx
+++ b/packages/ui/src/components/views/agent-manager/AgentGroupDetail.tsx
@@ -1,12 +1,4 @@
import React from 'react';
-import {
- RiGitBranchLine,
- RiArrowDownSLine,
- RiCheckLine,
- RiMore2Line,
- RiFileCopyLine,
- RiLoader4Line,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { Button } from '@/components/ui/button';
import { copyTextToClipboard } from '@/lib/clipboard';
@@ -31,6 +23,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface AgentGroupDetailProps {
@@ -163,7 +156,7 @@ export const AgentGroupDetail: React.FC
= ({
{group.name}
- {groupBusy && }
+ {groupBusy && }
@@ -173,7 +166,7 @@ export const AgentGroupDetail: React.FC = ({
·
-
+
{selectedSession?.worktreeMetadata?.label || selectedSession?.branch || t('agentManager.detail.header.noBranch')}
@@ -209,7 +202,7 @@ export const AgentGroupDetail: React.FC
= ({
>
)}
-
+
@@ -237,13 +230,13 @@ export const AgentGroupDetail: React.FC = ({
{session.branch && (
-
+
{session.worktreeMetadata?.label || session.branch}
)}
{selectedSession?.id === session.id && (
-
+
)}
))}
@@ -254,7 +247,7 @@ export const AgentGroupDetail: React.FC = ({
-
+
@@ -278,7 +271,7 @@ export const AgentGroupDetail: React.FC = ({
}}
disabled={!selectedSession?.path}
>
-
+
{t('agentManager.detail.actions.copyWorktreePath')}
diff --git a/packages/ui/src/components/views/agent-manager/AgentManagerEmptyState.tsx b/packages/ui/src/components/views/agent-manager/AgentManagerEmptyState.tsx
index e08758ba..31b766b8 100644
--- a/packages/ui/src/components/views/agent-manager/AgentManagerEmptyState.tsx
+++ b/packages/ui/src/components/views/agent-manager/AgentManagerEmptyState.tsx
@@ -1,15 +1,4 @@
import React from 'react';
-import {
- RiAddCircleLine,
- RiAddLine,
- RiArrowDownSLine,
- RiCloseLine,
- RiFileImageLine,
- RiFileLine,
- RiGitBranchLine,
- RiHourglassFill,
- RiSendPlane2Line,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { cn } from '@/lib/utils';
import { Input } from '@/components/ui/input';
@@ -22,6 +11,7 @@ import { BranchSelector, useBranchOptions } from '@/components/multirun/BranchSe
import { AgentSelector } from '@/components/multirun/AgentSelector';
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
+import { Icon } from "@/components/icon/Icon";
import { isIMECompositionEvent } from '@/lib/ime';
import { getWorktreeSetupCommands } from '@/lib/openchamberConfig';
import { useThemeSystem } from '@/contexts/useThemeSystem';
@@ -438,7 +428,7 @@ export const AgentManagerEmptyState: React.FC = ({
{/* Branch Selection */}
-
+
{t('agentManager.empty.baseBranch.label')}
= ({
) : null;
})()}
-
@@ -500,7 +490,7 @@ export const AgentManagerEmptyState: React.FC = ({
className="flex-shrink-0 flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
aria-label={t('agentManager.empty.setupCommands.removeCommandAria')}
>
-
+
))}
@@ -509,7 +499,7 @@ export const AgentManagerEmptyState: React.FC = ({
onClick={() => setSetupCommands([...setupCommands, ''])}
className="flex items-center gap-1.5 typography-meta text-muted-foreground hover:text-foreground transition-colors"
>
-
+
{t('agentManager.empty.setupCommands.addCommand')}
@@ -583,9 +573,9 @@ export const AgentManagerEmptyState: React.FC = ({
className="inline-flex items-center gap-1.5 px-2 py-1 bg-muted/30 border border-border/30 rounded-md typography-meta"
>
{file.mimeType.startsWith('image/') ? (
-
+
) : (
-
+
)}
{file.filename}
@@ -595,7 +585,7 @@ export const AgentManagerEmptyState: React.FC = ({
onClick={() => handleRemoveFile(file.id)}
className="text-muted-foreground hover:text-destructive ml-0.5"
>
-
+
))}
@@ -620,7 +610,7 @@ export const AgentManagerEmptyState: React.FC = ({
className="inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:text-foreground transition-colors"
aria-label={t('agentManager.empty.prompt.addAttachmentAria')}
>
-
+
@@ -645,9 +635,9 @@ export const AgentManagerEmptyState: React.FC
= ({
aria-label={t('agentManager.empty.actions.startAgentGroupAria')}
>
{isSubmittingOrCreating ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/views/agent-manager/AgentManagerSidebar.tsx b/packages/ui/src/components/views/agent-manager/AgentManagerSidebar.tsx
index d5c12d29..5ed17d8b 100644
--- a/packages/ui/src/components/views/agent-manager/AgentManagerSidebar.tsx
+++ b/packages/ui/src/components/views/agent-manager/AgentManagerSidebar.tsx
@@ -1,12 +1,4 @@
import React from 'react';
-import {
- RiAddLine,
- RiArrowDownSLine,
- RiMore2Line,
- RiSearchLine,
- RiGitBranchLine,
- RiLoader4Line,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
@@ -25,6 +17,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore';
import { useAllSessionStatuses } from '@/sync/sync-context';
@@ -92,11 +85,11 @@ const AgentGroupItem: React.FC = ({ group, isSelected, isBu
{group.name}
- {isBusy && }
+ {isBusy && }
-
+
{group.sessionCount === 1
? t('agentManager.sidebar.item.modelCountSingle', { count: group.sessionCount })
: t('agentManager.sidebar.item.modelCountPlural', { count: group.sessionCount })}
@@ -126,7 +119,7 @@ const AgentGroupItem: React.FC = ({ group, isSelected, isBu
aria-label={t('agentManager.sidebar.item.groupMenuAria')}
onClick={(e) => e.stopPropagation()}
>
-
+
@@ -218,7 +211,7 @@ export const AgentManagerSidebar: React.FC = ({
{/* Search Input */}
-
+
setSearchQuery(e.target.value)}
@@ -235,14 +228,14 @@ export const AgentManagerSidebar: React.FC
= ({
className="w-full justify-start gap-2 h-8"
onClick={onNewAgent}
>
-
+
{t('agentManager.sidebar.actions.newAgentGroup')}
{/* Agent Groups Section Header */}
-
+
{t('agentManager.sidebar.section.agentGroups')}
diff --git a/packages/ui/src/components/views/git/AIHighlightsBox.tsx b/packages/ui/src/components/views/git/AIHighlightsBox.tsx
index d7c7da99..4e23646a 100644
--- a/packages/ui/src/components/views/git/AIHighlightsBox.tsx
+++ b/packages/ui/src/components/views/git/AIHighlightsBox.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiArrowDownLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface AIHighlightsBoxProps {
@@ -35,7 +35,7 @@ export const AIHighlightsBox: React.FC
= ({
onClick={handleInsert}
aria-label={t('gitView.commit.aiHighlights.insertAria')}
>
-
+
diff --git a/packages/ui/src/components/views/git/BranchIntegrationSection.tsx b/packages/ui/src/components/views/git/BranchIntegrationSection.tsx
index 7f9943b1..dfe753be 100644
--- a/packages/ui/src/components/views/git/BranchIntegrationSection.tsx
+++ b/packages/ui/src/components/views/git/BranchIntegrationSection.tsx
@@ -1,12 +1,4 @@
import React from 'react';
-import {
- RiGitMergeLine,
- RiGitBranchLine,
- RiLoader4Line,
- RiArrowDownSLine,
- RiCheckLine,
- RiCloseLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import {
Dialog,
@@ -31,6 +23,7 @@ import {
CommandSeparator,
} from '@/components/ui/command';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
@@ -188,13 +181,13 @@ export const BranchIntegrationSection: React.FC =
{log.status === 'running' && (
-
+
)}
{log.status === 'done' && (
-
+
)}
{log.status === 'error' && (
-
+
)}
{log.status === 'pending' && (
@@ -251,7 +244,7 @@ export const BranchIntegrationSection: React.FC
=
)}
>
-
=
)}
>
-
=
{selectedBranch || t('gitView.branch.selectBranch')}
-
+
=
>
{operation === 'merge' ? (
<>
-
+
{t('gitView.operation.merge')}
>
) : (
<>
-
+
{t('gitView.operation.rebase')}
>
)}
@@ -444,9 +437,9 @@ export const BranchIntegrationSection: React.FC =
disabled={isDisabled}
>
{isOperating ? (
-
+
) : (
-
+
)}
{t('gitView.branch.mergeRebase')}
diff --git a/packages/ui/src/components/views/git/BranchSelector.tsx b/packages/ui/src/components/views/git/BranchSelector.tsx
index 60bd4b12..fa5e3b64 100644
--- a/packages/ui/src/components/views/git/BranchSelector.tsx
+++ b/packages/ui/src/components/views/git/BranchSelector.tsx
@@ -1,12 +1,4 @@
import React from 'react';
-import {
- RiGitBranchLine,
- RiArrowDownSLine,
- RiAddLine,
- RiCloseLine,
- RiLoader4Line,
- RiArrowLeftLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
@@ -23,6 +15,7 @@ import {
CommandSeparator,
} from '@/components/ui/command';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import type { GitRemote } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -177,11 +170,11 @@ export const BranchSelector: React.FC = ({
className="h-8 min-w-0 max-w-full justify-start gap-1.5 px-2 py-1"
disabled={disabled}
>
-
+
{currentBranch || t('gitView.branch.detachedHead')}
-
+
@@ -214,7 +207,7 @@ export const BranchSelector: React.FC = ({
disabled={isCreating}
className="shrink-0 text-muted-foreground hover:text-foreground disabled:opacity-50"
>
-
+
{t('gitView.branch.pushToPrefix')} {sanitizedNewBranch} {t('gitView.branch.pushToSuffix')}
@@ -231,7 +224,7 @@ export const BranchSelector: React.FC = ({
>
{isCreating ? (
-
+
) : null}
{remote.name}
@@ -244,7 +237,7 @@ export const BranchSelector: React.FC = ({
) : !showCreate ? (
-
+
{t('gitView.branch.create')}
) : (
@@ -274,9 +267,9 @@ export const BranchSelector: React.FC = ({
className="shrink-0 text-muted-foreground hover:text-foreground disabled:opacity-50"
>
{isCreating ? (
-
+
) : (
-
+
)}
= ({
disabled={isCreating}
className="shrink-0 text-muted-foreground hover:text-foreground disabled:opacity-50"
>
-
+
)}
diff --git a/packages/ui/src/components/views/git/ChangeRow.tsx b/packages/ui/src/components/views/git/ChangeRow.tsx
index 0b41f16b..f10fb9d5 100644
--- a/packages/ui/src/components/views/git/ChangeRow.tsx
+++ b/packages/ui/src/components/views/git/ChangeRow.tsx
@@ -1,11 +1,8 @@
import React, { useCallback, useMemo } from 'react';
-import {
- RiArrowGoBackLine,
- RiLoader4Line,
-} from '@remixicon/react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Checkbox } from '@/components/ui/checkbox';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
+import { Icon } from "@/components/icon/Icon";
import type { GitStatus } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -160,9 +157,9 @@ export const ChangeRow = React.memo(function ChangeRow({
aria-label={t('gitView.changes.revertFileAria', { path: file.path })}
>
{isReverting ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/views/git/ChangesSection.tsx b/packages/ui/src/components/views/git/ChangesSection.tsx
index afc0484b..dc8f67f2 100644
--- a/packages/ui/src/components/views/git/ChangesSection.tsx
+++ b/packages/ui/src/components/views/git/ChangesSection.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
-import { RiArchiveStackLine, RiFolder3Fill, RiFolderOpenFill } from '@remixicon/react';
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
import {
@@ -13,6 +12,7 @@ import {
} from '@/components/ui/dialog';
import { ScrollShadow } from '@/components/ui/ScrollShadow';
import { OverlayScrollbar } from '@/components/ui/OverlayScrollbar';
+import { Icon } from "@/components/icon/Icon";
import { ChangeRow } from './ChangeRow';
import type { GitStatus } from '@/lib/api/types';
import { cn } from '@/lib/utils';
@@ -405,9 +405,9 @@ export const ChangesSection: React.FC = ({
: t('gitView.changes.expandDirectoryAria', { path: directory.path })}
>
{isExpanded ? (
-
+
) : (
-
+
)}
{directory.name}
@@ -475,7 +475,7 @@ export const ChangesSection: React.FC = ({
aria-label={t('gitView.stashes.title')}
title={t('gitView.stashes.title')}
>
-
+
) : null}
diff --git a/packages/ui/src/components/views/git/CommitSection.tsx b/packages/ui/src/components/views/git/CommitSection.tsx
index ca298838..b7a58f29 100644
--- a/packages/ui/src/components/views/git/CommitSection.tsx
+++ b/packages/ui/src/components/views/git/CommitSection.tsx
@@ -1,15 +1,9 @@
-import {
- RiGitCommitLine,
- RiArrowUpLine,
- RiAiGenerate2,
- RiLoader4Line,
- RiEmotionHappyLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { CommitInput } from './CommitInput';
import { AIHighlightsBox } from './AIHighlightsBox';
import { useDeviceInfo } from '@/lib/device';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
type CommitAction = 'commit' | 'commitAndPush' | null;
@@ -87,7 +81,7 @@ export const CommitSection: React.FC
= ({
className="w-fit"
type="button"
>
-
+
{t('gitView.commit.addGitmoji')}
)}
@@ -107,9 +101,9 @@ export const CommitSection: React.FC = ({
className="commit-actions__btn"
>
{isGeneratingMessage ? (
-
+
) : (
-
+
)}
{t('gitView.commit.generate')}
@@ -126,12 +120,12 @@ export const CommitSection: React.FC = ({
>
{commitAction === 'commit' ? (
<>
-
+
{t('gitView.commit.committing')}
>
) : (
<>
-
+
{t('gitView.commit.commit')}
>
)}
@@ -149,9 +143,9 @@ export const CommitSection: React.FC = ({
aria-label={t('gitView.commit.pushAria')}
>
{commitAction === 'commitAndPush' ? (
-
+
) : (
-
+
)}
@@ -170,12 +164,12 @@ export const CommitSection: React.FC = ({
>
{commitAction === 'commitAndPush' ? (
<>
-
+
{t('gitView.commit.pushing')}
>
) : (
<>
-
+
{t('gitView.commit.push')}
>
)}
diff --git a/packages/ui/src/components/views/git/ConflictDialog.tsx b/packages/ui/src/components/views/git/ConflictDialog.tsx
index 28d1168d..bd75bc4e 100644
--- a/packages/ui/src/components/views/git/ConflictDialog.tsx
+++ b/packages/ui/src/components/views/git/ConflictDialog.tsx
@@ -8,11 +8,11 @@ import {
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
-import { RiAlertLine, RiLoader4Line, RiChat1Line, RiAddLine } from '@remixicon/react';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { getConflictDetails, type MergeConflictDetails } from '@/lib/gitApi';
import { renderMagicPrompt } from '@/lib/magicPrompts';
import { useI18n } from '@/lib/i18n';
@@ -173,7 +173,7 @@ export const ConflictDialog: React.FC = ({
-
+
{t('gitView.conflict.detectedTitle', { operation: operationLabel })}
@@ -183,7 +183,7 @@ export const ConflictDialog: React.FC = ({
{isLoading && (
-
+
{t('gitView.conflict.loading')}
)}
@@ -237,9 +237,9 @@ export const ConflictDialog: React.FC = ({
className="w-full gap-2"
>
{isLoading ? (
-
+
) : (
-
+
)}
{t('gitView.conflict.resolveNewSession')}
@@ -250,9 +250,9 @@ export const ConflictDialog: React.FC = ({
className="w-full gap-2"
>
{isLoading ? (
-
+
) : (
-
+
)}
{t('gitView.conflict.resolveCurrentSession')}
diff --git a/packages/ui/src/components/views/git/GitEmptyState.tsx b/packages/ui/src/components/views/git/GitEmptyState.tsx
index 40ce2d19..daab1914 100644
--- a/packages/ui/src/components/views/git/GitEmptyState.tsx
+++ b/packages/ui/src/components/views/git/GitEmptyState.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { RiArchiveStackLine, RiGitCommitLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface GitEmptyStateProps {
@@ -11,7 +11,7 @@ export const GitEmptyState: React.FC = ({ onOpenStashes }) =
const { t } = useI18n();
return (
-
+
{t('gitView.empty.cleanTitle')}
@@ -27,7 +27,7 @@ export const GitEmptyState: React.FC
= ({ onOpenStashes }) =
onClick={onOpenStashes}
className="gap-1.5"
>
-
+
{t('gitView.stashes.title')}
) : null}
diff --git a/packages/ui/src/components/views/git/GitHeader.tsx b/packages/ui/src/components/views/git/GitHeader.tsx
index 6d1485e3..40eefa35 100644
--- a/packages/ui/src/components/views/git/GitHeader.tsx
+++ b/packages/ui/src/components/views/git/GitHeader.tsx
@@ -1,17 +1,4 @@
import React from 'react';
-import {
- RiArrowDownSLine,
- RiCheckLine,
- RiLoader4Line,
- RiGitBranchLine,
- RiBriefcaseLine,
- RiHomeLine,
- RiGraduationCapLine,
- RiCodeLine,
- RiHeartLine,
- RiHistoryLine,
- RiUser3Line,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { SortableTabsStrip, type SortableTabsStripItem } from '@/components/ui/sortable-tabs-strip';
import {
@@ -21,6 +8,8 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
+import type { IconName } from "@/components/icon/icons";
import { BranchSelector } from './BranchSelector';
import { WorktreeBranchDisplay } from './WorktreeBranchDisplay';
import { SyncActions } from './SyncActions';
@@ -54,17 +43,14 @@ interface GitHeaderProps {
onSelectActionTab?: (tabID: string) => void;
}
-const IDENTITY_ICON_MAP: Record<
- string,
- React.ComponentType>
-> = {
- branch: RiGitBranchLine,
- briefcase: RiBriefcaseLine,
- house: RiHomeLine,
- graduation: RiGraduationCapLine,
- code: RiCodeLine,
- heart: RiHeartLine,
- user: RiUser3Line,
+const IDENTITY_ICON_MAP: Record = {
+ branch: 'git-branch',
+ briefcase: 'briefcase',
+ house: 'home',
+ graduation: 'graduation-cap',
+ code: 'code',
+ heart: 'heart',
+ user: 'user-3',
};
const IDENTITY_COLOR_MAP: Record = {
@@ -92,9 +78,10 @@ interface IdentityIconProps {
}
const IdentityIcon: React.FC = ({ icon, className, colorToken }) => {
- const IconComponent = IDENTITY_ICON_MAP[icon ?? 'branch'] ?? RiUser3Line;
+ const iconName = IDENTITY_ICON_MAP[icon ?? 'branch'] ?? 'user-3';
return (
-
@@ -132,7 +119,7 @@ export const IdentityDropdown: React.FC = ({
disabled={isDisabled}
>
{isApplying ? (
-
+
) : (
= ({
{activeProfile?.name || t('gitView.header.noIdentity')}
)}
-
+
@@ -178,7 +165,7 @@ export const IdentityDropdown: React.FC = ({
{isSelected ? (
-
+
) : null}
@@ -230,7 +217,7 @@ export const GitHeader: React.FC = ({
className="h-8 w-8 px-0"
onClick={onOpenHistory}
>
-
+
{t('gitView.history.title')}
diff --git a/packages/ui/src/components/views/git/HistoryCommitRow.tsx b/packages/ui/src/components/views/git/HistoryCommitRow.tsx
index 24fe26de..dac47328 100644
--- a/packages/ui/src/components/views/git/HistoryCommitRow.tsx
+++ b/packages/ui/src/components/views/git/HistoryCommitRow.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { RiLoader4Line, RiFileCopyLine } from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { Icon } from "@/components/icon/Icon";
import { cn } from '@/lib/utils';
import type { GitLogEntry, CommitFileEntry } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -99,7 +99,7 @@ export const HistoryCommitRow = React.memo(({
onCopyHash(entry.hash);
}}
>
-
+
{t('gitView.history.copySha')}
@@ -112,7 +112,7 @@ export const HistoryCommitRow = React.memo(({
{isLoadingFiles ? (
-
+
{t('gitView.history.loadingFiles')}
) : files.length === 0 ? (
diff --git a/packages/ui/src/components/views/git/HistorySection.tsx b/packages/ui/src/components/views/git/HistorySection.tsx
index fa61a9ac..c73d7281 100644
--- a/packages/ui/src/components/views/git/HistorySection.tsx
+++ b/packages/ui/src/components/views/git/HistorySection.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-import { RiArrowDownSLine, RiArrowUpLine, RiArrowUpSLine } from '@remixicon/react';
import {
Collapsible,
CollapsibleContent,
@@ -13,6 +12,7 @@ import {
SelectValue,
} from '@/components/ui/select';
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
+import { Icon } from "@/components/icon/Icon";
import { HistoryCommitRow } from './HistoryCommitRow';
import type { GitLogEntry, CommitFileEntry } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -78,8 +78,8 @@ export const HistorySection: React.FC
= ({
const bottomEntries = hasDivider ? log.all.slice(branchDivider.insertBeforeIndex) : [];
const dividerIcon = branchDivider?.direction === 'down'
- ?
- : ;
+ ?
+ : ;
const renderCommitList = (entries: GitLogEntry[]) => (
@@ -182,9 +182,9 @@ export const HistorySection: React.FC = ({
)}
{isOpen ? (
-
+
) : (
-
+
)}
diff --git a/packages/ui/src/components/views/git/InProgressOperationBanner.tsx b/packages/ui/src/components/views/git/InProgressOperationBanner.tsx
index 09de85a4..64d461ab 100644
--- a/packages/ui/src/components/views/git/InProgressOperationBanner.tsx
+++ b/packages/ui/src/components/views/git/InProgressOperationBanner.tsx
@@ -1,13 +1,6 @@
import React from 'react';
-import {
- RiGitMergeLine,
- RiGitBranchLine,
- RiLoader4Line,
- RiCheckLine,
- RiCloseLine,
- RiSparklingLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import type { GitMergeInProgress, GitRebaseInProgress } from '@/lib/api/types';
import { useI18n } from '@/lib/i18n';
@@ -63,7 +56,7 @@ export const InProgressOperationBanner: React.FC
const isProcessing = processingAction !== null;
const operationLabel = operation === 'merge' ? t('gitView.operation.merge') : t('gitView.operation.rebase');
- const OperationIcon = operation === 'merge' ? RiGitMergeLine : RiGitBranchLine;
+ const operationIconName = operation === 'merge' ? 'git-merge' : 'git-branch';
// Build description
let description = '';
@@ -81,7 +74,7 @@ export const InProgressOperationBanner: React.FC
-
+
{t('gitView.operation.inProgressTitle', { operation: operationLabel })}
@@ -103,7 +96,7 @@ export const InProgressOperationBanner: React.FC
disabled={isProcessing || isLoading}
className="gap-1.5"
>
-
+
{t('gitView.operation.resolveWithAi')}
)}
@@ -117,9 +110,9 @@ export const InProgressOperationBanner: React.FC
className="gap-1.5"
>
{processingAction === 'abort' ? (
-
+
) : (
-
+
)}
{t('gitView.operation.abort')}
@@ -134,9 +127,9 @@ export const InProgressOperationBanner: React.FC
className="gap-1.5"
>
{processingAction === 'continue' ? (
-
+
) : (
-
+
)}
{t('gitView.operation.continue')}
diff --git a/packages/ui/src/components/views/git/IntegrateCommitsSection.tsx b/packages/ui/src/components/views/git/IntegrateCommitsSection.tsx
index e8be153c..5b8704df 100644
--- a/packages/ui/src/components/views/git/IntegrateCommitsSection.tsx
+++ b/packages/ui/src/components/views/git/IntegrateCommitsSection.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-import { RiArrowDownSLine, RiLoader4Line, RiSplitCellsHorizontal, RiSparklingLine } from '@remixicon/react';
import {
DropdownMenu,
DropdownMenuContent,
@@ -15,6 +14,7 @@ import {
CommandList,
} from '@/components/ui/command';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
@@ -344,7 +344,7 @@ export const IntegrateCommitsSection: React.FC<{
-
+
{t('gitView.integrate.title')}
{ui.kind === 'ready' && ui.plan.commits.length > 0 ? (
@@ -354,7 +354,7 @@ export const IntegrateCommitsSection: React.FC<{
{ui.kind === 'loading' || ui.kind === 'running' ? (
-
+
) : null}
@@ -375,7 +375,7 @@ export const IntegrateCommitsSection: React.FC<{
{t('gitView.integrate.target')}
{targetBranch}
-
+
void handleResolveWithAi({ state: ui.state, details: ui.details }, false)}
>
-
+
{t('gitView.integrate.currentSession')}
void handleResolveWithAi({ state: ui.state, details: ui.details }, true)}
>
-
+
{t('gitView.integrate.newSession')}
void handleContinue()}>
diff --git a/packages/ui/src/components/views/git/PullRequestSection.tsx b/packages/ui/src/components/views/git/PullRequestSection.tsx
index 3ef40104..09183068 100644
--- a/packages/ui/src/components/views/git/PullRequestSection.tsx
+++ b/packages/ui/src/components/views/git/PullRequestSection.tsx
@@ -1,23 +1,4 @@
import React from 'react';
-import {
- RiChat4Line,
- RiCheckLine,
- RiCheckboxCircleLine,
- RiAiGenerate2,
- RiArrowDownSLine,
- RiArrowRightSLine,
- RiCloseLine,
- RiEditLine,
- RiErrorWarningLine,
- RiExternalLinkLine,
- RiGitClosePullRequestLine,
- RiGitMergeLine,
- RiGitPrDraftLine,
- RiGitPullRequestLine,
- RiInformationLine,
- RiLoader4Line,
- RiRefreshLine,
-} from '@remixicon/react';
import { toast } from '@/components/ui';
import { Checkbox } from '@/components/ui/checkbox';
import { Button } from '@/components/ui/button';
@@ -45,6 +26,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useDeviceInfo } from '@/lib/device';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { SimpleMarkdownRenderer } from '@/components/chat/MarkdownRenderer';
+import { Icon } from "@/components/icon/Icon";
import { useUIStore } from '@/stores/useUIStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useSelectionStore } from '@/sync/selection-store';
@@ -770,7 +752,7 @@ export const PullRequestSection: React.FC<{
{run.detailsUrl ? (
-
+
Open
@@ -859,7 +841,7 @@ export const PullRequestSection: React.FC<{
(isFail ? 'bg-destructive/10 text-destructive' : 'text-muted-foreground')
}
>
- {stepExpanded ? : }
+ {stepExpanded ? : }
{step.name}
{step.conclusion ? {step.conclusion} : null}
@@ -1385,13 +1367,13 @@ export const PullRequestSection: React.FC<{
const shouldShowConnectionNotice = githubAuthChecked && status?.connected === false;
const prVisualState = getPrVisualState(status);
const prColorVar = prVisualState ? `var(--pr-${prVisualState})` : 'var(--status-info)';
- const PrStateIcon = prVisualState === 'draft'
- ? RiGitPrDraftLine
+ const prStateIconName = prVisualState === 'draft'
+ ? 'git-pr-draft'
: prVisualState === 'merged'
- ? RiGitMergeLine
+ ? 'git-merge'
: prVisualState === 'closed'
- ? RiGitClosePullRequestLine
- : RiGitPullRequestLine;
+ ? 'git-close-pull-request'
+ : 'git-pull-request';
const prStatusText = pr
? [
`${pr.state}${pr.draft ? ' (draft)' : ''}`,
@@ -1424,13 +1406,13 @@ export const PullRequestSection: React.FC<{
onClick={() => void openExternal(pr.url)}
aria-label={t('gitView.pr.actions.openOnGitHubAria')}
>
-
+
{t('gitView.pr.actions.openOnGitHub')}
) : (
-
+
)}
{t('gitView.pullRequest.title')}
{pr ? (
@@ -1438,7 +1420,7 @@ export const PullRequestSection: React.FC<{
) : null}
- {isLoading ?
: null}
+ {isLoading ?
: null}
void refresh({ force: true })}
aria-label={t('gitView.pr.actions.refreshAria')}
>
-
+
{t('gitView.pr.actions.refresh')}
@@ -1493,7 +1475,7 @@ export const PullRequestSection: React.FC<{
{repoUrl ? (
-
+
Open Repo
@@ -1503,7 +1485,7 @@ export const PullRequestSection: React.FC<{
{!pr && !isInitialStatusResolved && !error && !shouldShowConnectionNotice ? (
-
+
{t('gitView.pr.checkingStatus')}
) : pr ? (
@@ -1574,7 +1556,7 @@ export const PullRequestSection: React.FC<{
disabled={isUpdating}
aria-label={t('gitView.pr.actions.cancelEditingAria')}
>
-
+
{t('gitView.pr.actions.cancelEditing')}
@@ -1588,7 +1570,7 @@ export const PullRequestSection: React.FC<{
disabled={isUpdating || !editTitle.trim()}
aria-label={t('gitView.pr.actions.savePrAria')}
>
- {isUpdating ? : }
+ {isUpdating ? : }
{t('gitView.pr.actions.savePr')}
@@ -1604,7 +1586,7 @@ export const PullRequestSection: React.FC<{
onClick={() => setIsEditingPr(true)}
aria-label={t('gitView.pr.actions.editPrAria')}
>
-
+
{t('gitView.pr.actions.editPr')}
@@ -1623,7 +1605,7 @@ export const PullRequestSection: React.FC<{
disabled={isLoadingCheckDetails}
aria-label={t('gitView.pr.actions.openChecksAria')}
>
- {isLoadingCheckDetails ? : }
+ {isLoadingCheckDetails ? : }
{t('gitView.pr.actions.openChecks')}
@@ -1640,7 +1622,7 @@ export const PullRequestSection: React.FC<{
onClick={sendFailedChecksToChat}
aria-label={t('gitView.pr.actions.resolveFailedChecksAria')}
>
-
+
{t('gitView.pr.actions.resolveFailedChecks')}
@@ -1656,7 +1638,7 @@ export const PullRequestSection: React.FC<{
onClick={openCommentsDialog}
aria-label={t('gitView.pr.actions.openCommentsAria')}
>
-
+
{t('gitView.pr.actions.openComments')}
@@ -1671,7 +1653,7 @@ export const PullRequestSection: React.FC<{
onClick={sendCommentsToChat}
aria-label={t('gitView.pr.actions.shareCommentsAria')}
>
-
+
{t('gitView.pr.actions.shareComments')}
@@ -1688,7 +1670,7 @@ export const PullRequestSection: React.FC<{
disabled={isMarkingReady || isMerging || isUpdating || isEditingPr}
aria-label={t('gitView.pr.actions.markReadyAria')}
>
- {isMarkingReady ? : }
+ {isMarkingReady ? : }
{t('gitView.pr.actions.markReady')}
@@ -1722,7 +1704,7 @@ export const PullRequestSection: React.FC<{
disabled={isMerging || isMarkingReady || pr.state !== 'open' || pr.draft || isUpdating || isEditingPr}
aria-label={t('gitView.pr.actions.mergePrAria')}
>
- {isMerging ? : }
+ {isMerging ? : }
{t('gitView.pr.actions.mergePr')}
@@ -1745,7 +1727,7 @@ export const PullRequestSection: React.FC<{
{repoUrl ? (
-
+
{t('gitView.pr.actions.repo')}
@@ -1906,7 +1888,7 @@ export const PullRequestSection: React.FC<{
onClick={generateDescription}
disabled={isGenerating || isCreating}
>
- {isGenerating ? : }
+ {isGenerating ? : }
{t('gitView.commit.generate')}
@@ -1917,7 +1899,7 @@ export const PullRequestSection: React.FC<{
disabled={isCreating || !isConnected || !targetBaseBranch.trim() || (!useDetectedUpstream && targetBaseBranch.trim() === branch)}
>
- {isCreating ? : }
+ {isCreating ? : }
{t('gitView.pr.actions.createPr')}
@@ -1930,7 +1912,7 @@ export const PullRequestSection: React.FC<{
-
+
{t('gitView.pr.checkDetails.title')}
@@ -1941,7 +1923,7 @@ export const PullRequestSection: React.FC<{
{isLoadingCheckDetails ? (
-
+
{t('gitView.loading.loading')}
) : null}
@@ -1971,7 +1953,7 @@ export const PullRequestSection: React.FC<{
-
+
{t('gitView.pr.comments.title')}
{pr ? (
{t('gitView.pr.numberLabel', { number: pr.number })}
@@ -1982,7 +1964,7 @@ export const PullRequestSection: React.FC<{
{isLoadingCommentsDetails ? (
-
+
{t('gitView.loading.loading')}
) : null}
@@ -2023,7 +2005,7 @@ export const PullRequestSection: React.FC<{
}}
aria-label={t('gitView.pr.actions.sendCommentToAgentAria')}
>
-
+
{t('gitView.pr.actions.sendToAgent')}
diff --git a/packages/ui/src/components/views/git/StashDialog.tsx b/packages/ui/src/components/views/git/StashDialog.tsx
index e4cddccb..a55e8fb5 100644
--- a/packages/ui/src/components/views/git/StashDialog.tsx
+++ b/packages/ui/src/components/views/git/StashDialog.tsx
@@ -10,7 +10,7 @@ import {
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { toast } from '@/components/ui';
-import { RiAlertLine, RiLoader4Line } from '@remixicon/react';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface StashDialogProps {
@@ -59,7 +59,7 @@ export const StashDialog: React.FC = ({
-
+
{t('gitView.stash.title')}
@@ -115,7 +115,7 @@ export const StashDialog: React.FC = ({
>
{isProcessing ? (
<>
-
+
{t('gitView.common.processing')}
>
) : (
diff --git a/packages/ui/src/components/views/git/StashesDialog.tsx b/packages/ui/src/components/views/git/StashesDialog.tsx
index 4b7f5ab0..d4fef84e 100644
--- a/packages/ui/src/components/views/git/StashesDialog.tsx
+++ b/packages/ui/src/components/views/git/StashesDialog.tsx
@@ -1,10 +1,10 @@
import React from 'react';
-import { RiArchiveStackLine, RiDeleteBinLine, RiInboxArchiveLine, RiInboxUnarchiveFill, RiInboxUnarchiveLine, RiLoader4Line, RiSearchLine } from '@remixicon/react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from '@/components/ui';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import type { GitStashEntry } from '@/lib/api/types';
@@ -124,7 +124,7 @@ export const StashesDialog: React.FC = ({
-
+
{t('gitView.stashes.title')}
{t('gitView.stashes.description')}
@@ -140,7 +140,7 @@ export const StashesDialog: React.FC = ({
className="flex-1"
/>
- {operation === 'create' ? : }
+ {operation === 'create' ? : }
{t('gitView.stashes.actions.stashCurrentWithCount', { count: uncommittedFileCount })}
@@ -148,13 +148,13 @@ export const StashesDialog: React.FC = ({
-
+
setQuery(event.target.value)} placeholder={t('gitView.stashes.searchPlaceholder')} className="pl-9" />
{isLoading ? (
-
+
) : filtered.length === 0 ? (
{query ? t('gitView.stashes.empty.search') : t('gitView.stashes.empty.list')}
) : filtered.map((stash, index) => (
@@ -172,9 +172,9 @@ export const StashesDialog: React.FC
= ({
- runStashAction(stash, 'apply')} disabled={isOperating}>
- runStashAction(stash, 'pop')} disabled={isOperating}>
- runStashAction(stash, 'drop')} disabled={isOperating} destructive>
+ runStashAction(stash, 'apply')} disabled={isOperating}>
+ runStashAction(stash, 'pop')} disabled={isOperating}>
+ runStashAction(stash, 'drop')} disabled={isOperating} destructive>
))}
@@ -188,7 +188,7 @@ const StashIconButton: React.FC<{ label: string; loading: boolean; disabled: boo
- {loading ? : children}
+ {loading ? : children}
{label}
diff --git a/packages/ui/src/components/views/git/SyncActions.tsx b/packages/ui/src/components/views/git/SyncActions.tsx
index 5e287988..710b2ee2 100644
--- a/packages/ui/src/components/views/git/SyncActions.tsx
+++ b/packages/ui/src/components/views/git/SyncActions.tsx
@@ -1,10 +1,4 @@
import React from 'react';
-import {
- RiArrowDownSLine,
- RiCloseLine,
- RiLoader4Line,
- RiRefreshLine,
-} from '@remixicon/react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import {
@@ -13,6 +7,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
+import { Icon } from "@/components/icon/Icon";
import type { GitRemote } from '@/lib/gitApi';
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
@@ -90,9 +85,9 @@ export const SyncActions: React.FC = ({
aria-label={t('gitView.sync.syncChanges')}
>
{syncAction === 'sync' ? (
-
+
) : (
-
+
)}
{primaryLabel}
@@ -111,7 +106,7 @@ export const SyncActions: React.FC = ({
disabled={isDropdownDisabled}
aria-label={t('gitView.sync.moreActionsAria')}
>
-
+
@@ -128,7 +123,7 @@ export const SyncActions: React.FC = ({
}}
>
-
+
@@ -161,9 +156,9 @@ export const SyncActions: React.FC = ({
title={t('gitView.header.removeRemoteTitle', { name: remote.name })}
>
{removingRemoteName === remote.name ? (
-
+
) : (
-
+
)}
) : null}
diff --git a/packages/ui/src/components/views/git/WorktreeBranchDisplay.tsx b/packages/ui/src/components/views/git/WorktreeBranchDisplay.tsx
index ca4205a9..4676eba7 100644
--- a/packages/ui/src/components/views/git/WorktreeBranchDisplay.tsx
+++ b/packages/ui/src/components/views/git/WorktreeBranchDisplay.tsx
@@ -1,6 +1,6 @@
import React from 'react';
-import { RiGitBranchLine, RiEditLine, RiCheckLine, RiCloseLine, RiLoader4Line } from '@remixicon/react';
import { Button } from '@/components/ui/button';
+import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
interface WorktreeBranchDisplayProps {
@@ -86,7 +86,7 @@ export const WorktreeBranchDisplay: React.FC = ({
handleSaveEdit();
}}
>
-
+
= ({
className="shrink-0 text-muted-foreground hover:text-foreground disabled:opacity-50"
>
{isRenaming ? (
-
+
) : (
-
+
)}
= ({
title={t('gitView.branch.renameCancel')}
className="shrink-0 text-muted-foreground hover:text-foreground disabled:opacity-50"
>
-
+
@@ -126,7 +126,7 @@ export const WorktreeBranchDisplay: React.FC
= ({
return (
-
+
{currentBranch || t('gitView.branch.detachedHead')}
@@ -139,7 +139,7 @@ export const WorktreeBranchDisplay: React.FC = ({
onClick={handleStartEdit}
title={t('gitView.branch.renameTitle')}
>
-
+
)}
diff --git a/packages/ui/src/components/voice/BrowserVoiceButton.tsx b/packages/ui/src/components/voice/BrowserVoiceButton.tsx
index f385c0a2..70b17431 100644
--- a/packages/ui/src/components/voice/BrowserVoiceButton.tsx
+++ b/packages/ui/src/components/voice/BrowserVoiceButton.tsx
@@ -22,14 +22,9 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
-import {
- RiMicOffLine,
- RiStopCircleLine,
- RiVoiceRecognitionLine,
- RiVolumeUpLine,
-} from '@remixicon/react';
import { VoiceStatusIndicator } from './VoiceStatusIndicator';
import { toast } from '@/components/ui/toast';
+import { Icon } from "@/components/icon/Icon";
// Status text for accessibility and labels
const statusLabels: Record
= {
@@ -260,8 +255,6 @@ export function BrowserVoiceButton() {
toggleConversationMode();
}, [toggleConversationMode]);
-
-
// If voice mode is disabled, don't render anything
if (!voiceModeEnabled) {
return null;
@@ -289,7 +282,7 @@ export function BrowserVoiceButton() {
aria-label={tooltipMessage}
className={`${buttonSizeClass} p-0 ${clearHoverBackgroundClass}`}
>
-
+
@@ -341,10 +334,10 @@ export function BrowserVoiceButton() {
{isActive ? (
isSpeaking ? (
// Green speaker icon when AI is speaking
-
+
) : (
// Red stop icon for listening/processing (both mobile and desktop)
-
+
)
) : (
-
+
)}
diff --git a/packages/ui/src/components/voice/VoiceStatusIndicator.tsx b/packages/ui/src/components/voice/VoiceStatusIndicator.tsx
index 0c0718aa..9be499b0 100644
--- a/packages/ui/src/components/voice/VoiceStatusIndicator.tsx
+++ b/packages/ui/src/components/voice/VoiceStatusIndicator.tsx
@@ -18,15 +18,9 @@
*/
import React from 'react';
-import {
- RiMicLine,
- RiMicOffLine,
- RiLoader4Line,
- RiVolumeUpLine,
- RiAlertLine,
-} from '@remixicon/react';
import type { BrowserVoiceStatus } from '@/hooks/useBrowserVoice';
import { useI18n } from '@/lib/i18n';
+import { Icon } from "@/components/icon/Icon";
export interface VoiceStatusIndicatorProps {
/** Current voice status */
@@ -59,7 +53,7 @@ const sizeClasses = {
const statusConfig: Record<
BrowserVoiceStatus,
{
- icon: typeof RiMicLine;
+ icon: string;
color: string;
labelKey:
| 'voice.status.idle'
@@ -71,29 +65,29 @@ const statusConfig: Record<
}
> = {
idle: {
- icon: RiMicOffLine,
+ icon: "mic-off",
color: 'text-muted-foreground',
labelKey: 'voice.status.idle',
},
listening: {
- icon: RiMicLine,
+ icon: "mic",
color: 'text-primary',
labelKey: 'voice.status.listening',
animation: 'animate-pulse',
},
processing: {
- icon: RiLoader4Line,
+ icon: "loader-4",
color: 'text-primary',
labelKey: 'voice.status.processing',
animation: 'animate-spin',
},
speaking: {
- icon: RiVolumeUpLine,
+ icon: "volume-up",
color: 'text-green-500',
labelKey: 'voice.status.speaking',
},
error: {
- icon: RiAlertLine,
+ icon: "alert",
color: 'text-destructive',
labelKey: 'voice.status.error',
},
@@ -111,14 +105,14 @@ export function VoiceStatusIndicator({
}: VoiceStatusIndicatorProps) {
const { t } = useI18n();
const config = statusConfig[status];
- const Icon = config.icon;
+ const statusIconName = config.icon;
const sizeClass = sizeClasses[size];
const containerClass = showLabel ? sizeClass.container : '';
return (
-
;
+export type IconComponent = IconName;
export interface SidebarSectionConfig {
id: SidebarSection;
@@ -18,55 +16,55 @@ export const SIDEBAR_SECTIONS: SidebarSectionConfig[] = [
id: 'sessions',
label: 'Sessions',
description: 'Browse and manage chat sessions scoped to the current directory.',
- icon: RiChatAi3Line,
+ icon: "chat-ai-3",
},
{
id: 'agents',
label: 'Agents',
description: 'Configure OpenCode agents, prompts, and permissions.',
- icon: RiBrainAi3Line,
+ icon: "brain-ai-3",
},
{
id: 'commands',
label: 'Commands',
description: 'Create and maintain custom slash commands for OpenCode.',
- icon: RiCommandLine,
+ icon: "command",
},
{
id: 'skills',
label: 'Skills',
description: 'Create reusable instruction files for agents to load on-demand.',
- icon: RiBookLine,
+ icon: "book",
},
{
id: 'mcp',
label: 'MCP',
description: 'Manage Model Context Protocol servers and their configurations.',
- icon: RiPlugLine,
+ icon: "plug-line",
},
{
id: 'providers',
label: 'Providers',
description: 'Configure AI model providers and API credentials.',
- icon: RiStackLine,
+ icon: "stack",
},
{
id: 'usage',
label: 'Usage',
description: 'Monitor API quota and usage across providers.',
- icon: RiBarChart2Line,
+ icon: "bar-chart-2",
},
{
id: 'git-identities',
label: 'Git Identities',
description: 'Manage Git profiles with different credentials and SSH keys.',
- icon: RiGitBranchLine,
+ icon: "git-branch",
},
{
id: 'settings',
label: 'OpenChamber',
description: 'OpenChamber app settings: themes, fonts, and preferences.',
- icon: RiSettings3Line,
+ icon: "settings-3",
},
];
diff --git a/packages/ui/src/lib/projectActions.ts b/packages/ui/src/lib/projectActions.ts
index abdeb96f..a6ee9726 100644
--- a/packages/ui/src/lib/projectActions.ts
+++ b/packages/ui/src/lib/projectActions.ts
@@ -1,24 +1,3 @@
-import {
- RiBrainAi3Line,
- RiCheckboxCircleLine,
- RiBugLine,
- RiCodeLine,
- RiCommandLine,
- RiFileTextLine,
- RiFlaskLine,
- RiGitBranchLine,
- RiHammerLine,
- RiPlayLine,
- RiRocketLine,
- RiRobot2Line,
- RiSearchLine,
- RiServerLine,
- RiSettings3Line,
- RiStackLine,
- RiTerminalBoxLine,
- RiToolsLine,
-} from '@remixicon/react';
-import type { ComponentType } from 'react';
import type {
OpenChamberProjectAction,
OpenChamberProjectActionPlatform,
@@ -27,6 +6,7 @@ import type {
DesktopSshInstance,
DesktopSshPortForward,
} from '@/lib/desktopSsh';
+import type { IconName } from "@/components/icon/icons";
export type ProjectActionIconKey =
| 'play'
@@ -51,31 +31,31 @@ export type ProjectActionIconKey =
export const PROJECT_ACTION_ICONS: Array<{
key: ProjectActionIconKey;
label: string;
- Icon: ComponentType<{ className?: string }>;
+ Icon: IconName;
}> = [
- { key: 'play', label: 'Play', Icon: RiPlayLine },
- { key: 'build', label: 'Build', Icon: RiHammerLine },
- { key: 'lint', label: 'Lint', Icon: RiCheckboxCircleLine },
- { key: 'terminal', label: 'Terminal', Icon: RiTerminalBoxLine },
- { key: 'tools', label: 'Tools', Icon: RiToolsLine },
- { key: 'bug', label: 'Bug', Icon: RiBugLine },
- { key: 'flask', label: 'Flask', Icon: RiFlaskLine },
- { key: 'rocket', label: 'Rocket', Icon: RiRocketLine },
- { key: 'code', label: 'Code', Icon: RiCodeLine },
- { key: 'server', label: 'Server', Icon: RiServerLine },
- { key: 'branch', label: 'Branch', Icon: RiGitBranchLine },
- { key: 'search', label: 'Search', Icon: RiSearchLine },
- { key: 'settings', label: 'Settings', Icon: RiSettings3Line },
- { key: 'brain', label: 'Brain', Icon: RiBrainAi3Line },
- { key: 'stack', label: 'Stack', Icon: RiStackLine },
- { key: 'robot', label: 'Robot', Icon: RiRobot2Line },
- { key: 'command', label: 'Command', Icon: RiCommandLine },
- { key: 'file', label: 'File', Icon: RiFileTextLine },
+ { key: 'play', label: 'Play', Icon: 'play' },
+ { key: 'build', label: 'Build', Icon: 'hammer' },
+ { key: 'lint', label: 'Lint', Icon: 'checkbox-circle' },
+ { key: 'terminal', label: 'Terminal', Icon: 'terminal-box' },
+ { key: 'tools', label: 'Tools', Icon: 'tools' },
+ { key: 'bug', label: 'Bug', Icon: 'bug' },
+ { key: 'flask', label: 'Flask', Icon: 'flask' },
+ { key: 'rocket', label: 'Rocket', Icon: 'rocket' },
+ { key: 'code', label: 'Code', Icon: 'code' },
+ { key: 'server', label: 'Server', Icon: 'server' },
+ { key: 'branch', label: 'Branch', Icon: 'git-branch' },
+ { key: 'search', label: 'Search', Icon: 'search' },
+ { key: 'settings', label: 'Settings', Icon: 'settings-3' },
+ { key: 'brain', label: 'Brain', Icon: 'brain-ai-3' },
+ { key: 'stack', label: 'Stack', Icon: 'stack' },
+ { key: 'robot', label: 'Robot', Icon: 'robot-2' },
+ { key: 'command', label: 'Command', Icon: 'command' },
+ { key: 'file', label: 'File', Icon: 'file-text' },
];
export const PROJECT_ACTION_ICON_MAP = Object.fromEntries(
PROJECT_ACTION_ICONS.map((entry) => [entry.key, entry.Icon])
-) as Record>;
+) as Record;
export const PROJECT_ACTIONS_UPDATED_EVENT = 'openchamber:project-actions-updated';
diff --git a/packages/ui/src/lib/projectMeta.ts b/packages/ui/src/lib/projectMeta.ts
index 1116f3ae..770e568b 100644
--- a/packages/ui/src/lib/projectMeta.ts
+++ b/packages/ui/src/lib/projectMeta.ts
@@ -1,52 +1,31 @@
-import {
- RiCodeBoxLine,
- RiTerminalBoxLine,
- RiRocketLine,
- RiFlaskLine,
- RiGamepadLine,
- RiBriefcaseLine,
- RiHomeLine,
- RiGlobalLine,
- RiLeafLine,
- RiShieldLine,
- RiPaletteLine,
- RiServerLine,
- RiSmartphoneLine,
- RiDatabase2Line,
- RiLightbulbLine,
- RiMusicLine,
- RiCameraLine,
- RiBookOpenLine,
- RiHeartLine,
- type RemixiconComponentType,
-} from '@remixicon/react';
import type { ProjectEntry } from '@/lib/api/types';
+import type { IconName } from "@/components/icon/icons";
type ThemeVariant = 'light' | 'dark';
-export const PROJECT_ICONS: Array<{ key: string; Icon: RemixiconComponentType; label: string }> = [
- { key: 'code', Icon: RiCodeBoxLine, label: 'Code' },
- { key: 'terminal', Icon: RiTerminalBoxLine, label: 'Terminal' },
- { key: 'rocket', Icon: RiRocketLine, label: 'Rocket' },
- { key: 'flask', Icon: RiFlaskLine, label: 'Lab' },
- { key: 'gamepad', Icon: RiGamepadLine, label: 'Game' },
- { key: 'briefcase', Icon: RiBriefcaseLine, label: 'Work' },
- { key: 'home', Icon: RiHomeLine, label: 'Home' },
- { key: 'globe', Icon: RiGlobalLine, label: 'Web' },
- { key: 'leaf', Icon: RiLeafLine, label: 'Nature' },
- { key: 'shield', Icon: RiShieldLine, label: 'Security' },
- { key: 'palette', Icon: RiPaletteLine, label: 'Design' },
- { key: 'server', Icon: RiServerLine, label: 'Server' },
- { key: 'phone', Icon: RiSmartphoneLine, label: 'Mobile' },
- { key: 'database', Icon: RiDatabase2Line, label: 'Data' },
- { key: 'lightbulb', Icon: RiLightbulbLine, label: 'Idea' },
- { key: 'music', Icon: RiMusicLine, label: 'Music' },
- { key: 'camera', Icon: RiCameraLine, label: 'Media' },
- { key: 'book', Icon: RiBookOpenLine, label: 'Docs' },
- { key: 'heart', Icon: RiHeartLine, label: 'Favorite' },
+export const PROJECT_ICONS: Array<{ key: string; Icon: IconName; label: string }> = [
+ { key: 'code', Icon: 'code-box', label: 'Code' },
+ { key: 'terminal', Icon: 'terminal-box', label: 'Terminal' },
+ { key: 'rocket', Icon: 'rocket', label: 'Rocket' },
+ { key: 'flask', Icon: 'flask', label: 'Lab' },
+ { key: 'gamepad', Icon: 'gamepad', label: 'Game' },
+ { key: 'briefcase', Icon: 'briefcase', label: 'Work' },
+ { key: 'home', Icon: 'home', label: 'Home' },
+ { key: 'globe', Icon: 'global', label: 'Web' },
+ { key: 'leaf', Icon: 'leaf', label: 'Nature' },
+ { key: 'shield', Icon: 'shield', label: 'Security' },
+ { key: 'palette', Icon: 'palette', label: 'Design' },
+ { key: 'server', Icon: 'server', label: 'Server' },
+ { key: 'phone', Icon: 'smartphone', label: 'Mobile' },
+ { key: 'database', Icon: 'database-2', label: 'Data' },
+ { key: 'lightbulb', Icon: 'lightbulb', label: 'Idea' },
+ { key: 'music', Icon: 'music', label: 'Music' },
+ { key: 'camera', Icon: 'camera', label: 'Media' },
+ { key: 'book', Icon: 'book-open', label: 'Docs' },
+ { key: 'heart', Icon: 'heart', label: 'Favorite' },
];
-export const PROJECT_ICON_MAP: Record = Object.fromEntries(
+export const PROJECT_ICON_MAP: Record = Object.fromEntries(
PROJECT_ICONS.map((i) => [i.key, i.Icon])
);
diff --git a/scripts/generate-icon-sprite.mjs b/scripts/generate-icon-sprite.mjs
new file mode 100644
index 00000000..1839c3d1
--- /dev/null
+++ b/scripts/generate-icon-sprite.mjs
@@ -0,0 +1,250 @@
+/**
+ * Generates the SVG icon sprite file from @remixicon/react bundle.
+ *
+ * Usage: bun run scripts/generate-icon-sprite.mjs
+ *
+ * Reads the minified @remixicon/react bundle, extracts SVG path data
+ * for all Ri* icons used in packages/ui/src, and writes
+ * packages/ui/src/components/icon/sprite.ts.
+ */
+
+import { readFileSync, writeFileSync } from "node:fs"
+import { resolve, dirname } from "node:path"
+import { fileURLToPath } from "node:url"
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const repoRoot = resolve(__dirname, "..")
+const remixPath = resolve(repoRoot, "node_modules/@remixicon/react/index.mjs")
+
+const source = readFileSync(remixPath, "utf-8")
+
+// --- Step 1: extract variable → path mapping ---
+// Pattern: const VARNAME=({color:...})=>...createElement("path",{d:"PATH_DATA"})...,
+// Each icon is defined as `const X=...` where X is 1-4 chars.
+const varPathMap = new Map()
+const varRegex = /(?:[,;]const |\),)([A-Za-z0-9_$]{1,4})=\([{]color:/g
+// Find all variable definitions and their boundaries
+const varPositions = []
+let m
+while ((m = varRegex.exec(source)) !== null) {
+ varPositions.push({
+ varName: m[1],
+ start: m.index + m[0].length - 1, // first `{` after `=({color:`
+ })
+}
+
+for (let i = 0; i < varPositions.length; i++) {
+ const current = varPositions[i]
+ const next = varPositions[i + 1]
+ // End at the )), just before the next variable definition
+ const end = next
+ ? source.indexOf("))," + next.varName + "=(", current.start)
+ : source.length
+ if (end < 0 || end < current.start) continue
+ const segment = source.slice(current.start, end)
+ const pathRegex = /\w+\.createElement\("path",[{]d:"([^"]*)"/g
+ let pm
+ const paths = []
+ while ((pm = pathRegex.exec(segment)) !== null) {
+ paths.push(pm[1])
+ }
+ if (paths.length > 0) {
+ varPathMap.set(current.varName, paths)
+ }
+}
+
+// --- Step 2: extract export mapping ---
+// The export map is near the end of the file:
+// export{V1 as Ri...Z2 as RiLast};
+const exportRegex = /export[{]([^}]+)[}]/
+const exportMatch = exportRegex.exec(source)
+if (!exportMatch) {
+ console.error("Could not find export mapping in remixicon bundle")
+ process.exit(1)
+}
+
+const nameToVar = new Map()
+const entries = exportMatch[1].split(",")
+for (const entry of entries) {
+ // Pattern: VAR as RiIconName
+ const parts = entry.trim().split(" as ")
+ if (parts.length === 2) {
+ nameToVar.set(parts[1].trim(), parts[0].trim())
+ }
+}
+
+// --- Step 3: find which icons we actually use ---
+const srcDir = resolve(repoRoot, "packages/ui/src")
+
+// Helper: convert kebab-case name back to RiName
+function nameToRi(kebab) {
+ // "arrow-down-sline" → RiArrowDownSline
+ const parts = kebab.split("-")
+ let result = "Ri"
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i]
+ if (i > 0 && /^\d/.test(part)) {
+ result += part[0].toUpperCase() + part.slice(1)
+ } else {
+ result += part.charAt(0).toUpperCase() + part.slice(1)
+ }
+ }
+ return result
+}
+
+const srcFiles = []
+function walk(dir) {
+ const { readdirSync, statSync } = require("node:fs")
+ for (const entry of readdirSync(dir)) {
+ const full = resolve(dir, entry)
+ if (statSync(full).isDirectory()) {
+ if (entry === "node_modules") continue
+ walk(full)
+ } else if (/\.(tsx?|jsx?)$/.test(entry)) {
+ srcFiles.push(full)
+ }
+ }
+}
+import("node:fs").then(({ readdirSync, statSync: st }) => {
+ // Already imported above, use recursive function
+ function localWalk(dir) {
+ const { readdirSync: rd, statSync: s } = require("node:fs")
+ for (const entry of rd(dir)) {
+ const full = resolve(dir, entry)
+ try {
+ if (s(full).isDirectory()) {
+ if (entry === "node_modules") continue
+ localWalk(full)
+ } else if (/\.(tsx?)$/.test(entry)) {
+ srcFiles.push(full)
+ }
+ } catch {}
+ }
+ }
+ localWalk(srcDir)
+})
+
+// Finish step 3 synchronously with simpler approach
+import { readdirSync, statSync } from "node:fs"
+function findAllSourceFiles(dir) {
+ const results = []
+ for (const entry of readdirSync(dir)) {
+ const full = resolve(dir, entry)
+ try {
+ const st = statSync(full)
+ if (st.isDirectory()) {
+ if (entry === "node_modules") continue
+ results.push(...findAllSourceFiles(full))
+ } else if (/\.(tsx?)$/.test(entry)) {
+ results.push(full)
+ }
+ } catch { /* skip */ }
+ }
+ return results
+}
+
+// Helper: convert kebab-case name back to RiX name
+function nameToRi(kebab) {
+ const parts = kebab.split("-")
+ let result = "Ri"
+ for (let i = 0; i < parts.length; i++) {
+ result += parts[i].charAt(0).toUpperCase() + parts[i].slice(1)
+ }
+ return result
+}
+
+const allSrcFiles = findAllSourceFiles(srcDir)
+const usedIcons = new Set()
+for (const file of allSrcFiles) {
+ const content = readFileSync(file, "utf-8")
+ // Match RiIcons from @remixicon/react imports
+ const iconRegex = /Ri[A-Z][A-Za-z0-9]+/g
+ let im
+ while ((im = iconRegex.exec(content)) !== null) {
+ if (nameToVar.has(im[0])) {
+ usedIcons.add(im[0])
+ }
+ }
+
+ // Also scan for patterns (already-migrated icons)
+ const iconNameRegex = /Icon\s+name="([^"]+)"/g
+ let nm
+ while ((nm = iconNameRegex.exec(content)) !== null) {
+ const kebab = nm[1]
+ for (const suffix of ["Line", "Fill", ""]) {
+ const riName = nameToRi(kebab) + suffix
+ if (nameToVar.has(riName)) {
+ usedIcons.add(riName)
+ break
+ }
+ }
+ }
+
+ // Also scan for icon: 'kebab-name' in object literals (e.g. MODALITY_ICON_MAP)
+ const iconPropRegex = /icon:\s*'([a-z][a-z0-9-]*)'/g
+ let ip
+ while ((ip = iconPropRegex.exec(content)) !== null) {
+ const kebab = ip[1]
+ for (const suffix of ["Line", "Fill", ""]) {
+ const riName = nameToRi(kebab) + suffix
+ if (nameToVar.has(riName)) {
+ usedIcons.add(riName)
+ break
+ }
+ }
+ }
+}
+
+console.log(`Found ${usedIcons.size} unique remixicon names used in source`)
+
+// --- Step 4: build sprite data ---
+const iconEntries = []
+for (const iconName of [...usedIcons].sort()) {
+ const varName = nameToVar.get(iconName)
+ if (!varName) {
+ console.warn(` ⚠ Unknown icon: ${iconName}`)
+ continue
+ }
+ const paths = varPathMap.get(varName)
+ if (!paths || paths.length === 0) {
+ console.warn(` ⚠ No path data for: ${iconName} (var: ${varName})`)
+ continue
+ }
+
+ // Build SVG content from paths
+ const svgContent = paths
+ .map((d) => ` `)
+ .join("")
+
+ iconEntries.push({ name: iconName, content: svgContent })
+}
+
+// --- Step 5: write sprite.ts ---
+const remixToSpriteName = (name) => {
+ // RiArrowDownSLine → arrow-down-s
+ // RiGithubFill → github-fill (keep Fill for fill variants)
+ return name
+ .replace(/^Ri/, "")
+ .replace(/Line$/, "")
+ .replace(/([a-z])([A-Z0-9])/g, "$1-$2")
+ .replace(/([0-9])([A-Z])/g, "$1-$2")
+ .toLowerCase()
+}
+
+const spriteLines = iconEntries.map(({ name, content }) => {
+ const spriteName = remixToSpriteName(name)
+ return ` "${spriteName}": \`${content}\`,`
+})
+
+const spriteContent = `// This file is auto-generated by scripts/generate-icon-sprite.mjs
+// Do not edit manually. Run the script to update.
+
+export const iconSpriteData: Record = {
+${spriteLines.join("\n")}
+};
+`
+
+const outPath = resolve(repoRoot, "packages/ui/src/components/icon/sprite.ts")
+writeFileSync(outPath, spriteContent, "utf-8")
+console.log(`\n✅ Generated sprite data for ${iconEntries.length} icons → ${outPath}`)
+console.log(` Total sprite size: ${Buffer.byteLength(spriteContent).toLocaleString()} bytes`)