diff --git a/apps/desktop/bun.lock b/apps/desktop/bun.lock index 554bc7d2..5bb5fad8 100644 --- a/apps/desktop/bun.lock +++ b/apps/desktop/bun.lock @@ -11,10 +11,9 @@ "@blocknote/react": "^0.25.0", "@dnd-kit/react": "0.5.0", "@excalidraw/excalidraw": "0.18.1", - "@hugeicons/core-free-icons": "^4.3.0", - "@hugeicons/react": "^1.1.10", "@noble/hashes": "^2.2.0", "@petx/react": "0.2.0", + "@phosphor-icons/react": "^2.1.10", "@shikijs/monaco": "^4.4.1", "@xterm/addon-fit": "^0.10.0", "@xterm/addon-search": "^0.16.0", @@ -244,10 +243,6 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], - "@hugeicons/core-free-icons": ["@hugeicons/core-free-icons@4.3.0", "", {}, "sha512-5DC5gq2psNDKBQThOjpuFCtMco8nl2FkrBzZgG15cFGM0kSwUoych/pw0DmjBygF9qRDITdBPtmzJqLsOeImMg=="], - - "@hugeicons/react": ["@hugeicons/react@1.1.10", "", { "peerDependencies": { "react": ">=16.0.0" } }, "sha512-9tAsd92yMVVGP+E+d8cM+wc0VnXuYZHNbWQi7TOe8k2neawmhiaFmVFbzyruejhOMa197/LbgvSED32ayUo3ZA=="], - "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], @@ -298,6 +293,8 @@ "@petx/react": ["@petx/react@0.2.0", "", { "dependencies": { "@petx/core": "0.2.0" }, "peerDependencies": { "react": ">=18" } }, "sha512-qzUwBUHxRFMgrxBUbChalkJRFhEnpMi8jVta4Mx/SqNHk0xQQbBHE39FMxvuXawEPYdFaIft4c500lRJse1S0g=="], + "@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="], + "@pkgr/core": ["@pkgr/core@0.3.6", "", {}, "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA=="], "@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="], diff --git a/apps/desktop/components.json b/apps/desktop/components.json index 598446ea..78d06117 100644 --- a/apps/desktop/components.json +++ b/apps/desktop/components.json @@ -10,7 +10,7 @@ "cssVariables": true, "prefix": "" }, - "iconLibrary": "hugeicons", + "iconLibrary": "phosphor", "aliases": { "components": "@/components", "utils": "@/lib/utils", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7a56b2c0..a01488de 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -29,9 +29,8 @@ "@blocknote/react": "^0.25.0", "@dnd-kit/react": "0.5.0", "@excalidraw/excalidraw": "0.18.1", - "@hugeicons/core-free-icons": "^4.3.0", - "@hugeicons/react": "^1.1.10", "@noble/hashes": "^2.2.0", + "@phosphor-icons/react": "^2.1.10", "@petx/react": "0.2.0", "@shikijs/monaco": "^4.4.1", "@xterm/addon-fit": "^0.10.0", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 90b65606..74d2ef13 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -5480,7 +5480,7 @@ export default function App() { id: slug(skillDraft.name), name: skillDraft.name.trim(), description: "", - icon: "✦", + icon: null, payload: { kind: "fragment", text: skillDraft.text }, }); setSkillDraft(null); diff --git a/apps/desktop/src/bridge.ts b/apps/desktop/src/bridge.ts index 12fc2449..338ad91c 100644 --- a/apps/desktop/src/bridge.ts +++ b/apps/desktop/src/bridge.ts @@ -1594,11 +1594,11 @@ export function providerDisplayName(providerId: string): string { } const FALLBACK_SKILLS: SkillInfo[] = [ - { id: "reviewer", name: "Code Reviewer", description: "Meticulous reviewer", icon: "🔍", kind: "fragment", source: null }, - { id: "test-writer", name: "Test Writer", description: "Thorough tests", icon: "🧪", kind: "fragment", source: null }, - { id: "security-audit", name: "Security Audit", description: "Find vulns", icon: "🛡️", kind: "fragment", source: null }, + { id: "reviewer", name: "Code Reviewer", description: "Meticulous reviewer", icon: null, kind: "fragment", source: null }, + { id: "test-writer", name: "Test Writer", description: "Thorough tests", icon: null, kind: "fragment", source: null }, + { id: "security-audit", name: "Security Audit", description: "Find vulns", icon: null, kind: "fragment", source: null }, { - id: "commit-macro", name: "Commit Message", description: "Commit macro", icon: "📝", kind: "macro", source: null, + id: "commit-macro", name: "Commit Message", description: "Commit macro", icon: null, kind: "macro", source: null, macro_template: "Write a {{style}} commit message for changes to {{scope}}.", macro_slots: [ { id: "style", label: "Style", kind: "select", options: ["conventional", "descriptive"], required: true }, @@ -2167,7 +2167,7 @@ export async function readVisualization(path: string): Promise {

Renderer build

Passed

- + Updated just now
`; @@ -2967,9 +2967,9 @@ export interface MarketItem { } const FALLBACK_MARKET: MarketItem[] = [ - { id: "architect", name: "System Architect", description: "Design before coding.", author: "codetwo", tags: ["design"], icon: "🏛️", kind: "fragment", installed: false }, - { id: "test-suite", name: "Test Suite Author", description: "Thorough deterministic tests.", author: "codetwo", tags: ["testing"], icon: "🧪", kind: "fragment", installed: false }, - { id: "browser-tool", name: "Browser Tool (MCP)", description: "Give the agent a browser.", author: "codetwo", tags: ["mcp", "browser"], icon: "🌐", kind: "mcp", installed: false }, + { id: "architect", name: "System Architect", description: "Design before coding.", author: "codetwo", tags: ["design"], icon: null, kind: "fragment", installed: false }, + { id: "test-suite", name: "Test Suite Author", description: "Thorough deterministic tests.", author: "codetwo", tags: ["testing"], icon: null, kind: "fragment", installed: false }, + { id: "browser-tool", name: "Browser Tool (MCP)", description: "Give the agent a browser.", author: "codetwo", tags: ["mcp", "browser"], icon: null, kind: "mcp", installed: false }, ]; export async function marketCatalog(): Promise { @@ -4380,18 +4380,18 @@ export async function onAutoSceneChanged( /** Browser-preview stand-ins (same convention as FALLBACK_SKILLS): the five builtin scenes. */ const FALLBACK_SCENES: SceneInfo[] = ( [ - ["research", "Research", "调研", "🔎", "read_only", "Survey the problem space read-only and produce a cited research report."], - ["develop", "Develop", "开发", "🛠️", "auto_edit", "Plan-first implementation in an isolated worktree."], - ["test", "Test", "测试", "🧪", "auto_edit", "Exercise the change against its acceptance criteria."], - ["fix", "Fix", "修复", "🩹", "auto_edit", "Resolve reported failures one by one."], - ["acceptance", "Acceptance", "验收", "✅", "read_only", "Read-only verification against the original acceptance criteria."], + ["research", "Research", "调研", "read_only", "Survey the problem space read-only and produce a cited research report."], + ["develop", "Develop", "开发", "auto_edit", "Plan-first implementation in an isolated worktree."], + ["test", "Test", "测试", "auto_edit", "Exercise the change against its acceptance criteria."], + ["fix", "Fix", "修复", "auto_edit", "Resolve reported failures one by one."], + ["acceptance", "Acceptance", "验收", "read_only", "Read-only verification against the original acceptance criteria."], ] as const -).map(([name, title, zh, icon, mode, description]) => ({ +).map(([name, title, zh, mode, description]) => ({ reference: `builtin:${name}`, name, title, description, - icon, + icon: null, source: "builtin" as const, keywords: [], has_brief: true, diff --git a/apps/desktop/src/components/ui/icons.test.tsx b/apps/desktop/src/components/ui/icons.test.tsx new file mode 100644 index 00000000..03e3168b --- /dev/null +++ b/apps/desktop/src/components/ui/icons.test.tsx @@ -0,0 +1,94 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { createRef } from "react"; +import { activateDom, dom, mount } from "../../../tests/domTestHarness"; +import * as iconExports from "./icons"; +import type { AppIcon } from "./icons"; + +activateDom(); + +const icons = Object.entries(iconExports) as Array<[string, AppIcon]>; + +afterEach(() => { + dom.document.body.replaceChildren(); +}); + +describe("rounded icon adapter", () => { + test("renders every public icon as a configurable Phosphor SVG", () => { + const view = mount( +
+ {icons.map(([name, Icon]) => ( + + ))} +
, + ); + + const rendered = [...view.container.querySelectorAll("svg")]; + expect(icons.length).toBeGreaterThan(100); + expect(rendered).toHaveLength(icons.length); + + for (const svg of rendered) { + expect(svg.getAttribute("viewBox")).toBe("0 0 256 256"); + expect(svg.getAttribute("fill")).toBe("currentColor"); + expect(svg.getAttribute("width")).toBe("1em"); + expect(svg.getAttribute("height")).toBe("1em"); + expect(svg.classList.contains("adapter-icon")).toBe(true); + expect(svg.getAttribute("aria-label")).toBe(svg.getAttribute("data-icon-name")); + } + + view.unmount(); + }); + + test("defaults to regular weight, forwards refs, and preserves directional defaults", () => { + const ref = createRef(); + const view = mount( +
+ + + + +
, + ); + + const defaultSearch = view.container.querySelector('[data-search="default"]'); + const regularSearch = view.container.querySelector('[data-search="regular"]'); + const fallbackCaret = view.container.querySelector('[data-caret="fallback"]'); + const rightPanel = view.container.querySelector('[data-panel="right"]'); + + expect(ref.current).toBe(defaultSearch); + expect(defaultSearch?.querySelector("path")?.getAttribute("d")) + .toBe(regularSearch?.querySelector("path")?.getAttribute("d")); + expect(defaultSearch?.querySelector("title")?.textContent).toBe("Search"); + expect(fallbackCaret?.textContent).toBe(""); + expect(rightPanel?.getAttribute("transform")).toBe("scale(-1, 1)"); + + view.unmount(); + }); + + test("keeps action-oriented exports visually distinct from their base concepts", () => { + const view = mount( +
+ + + + + + + +
, + ); + const markup = (name: string) => + view.container.querySelector(`[data-icon="${name}"]`)?.innerHTML; + + expect(markup("file-clock")).not.toBe(markup("file")); + expect(markup("folder-down")).not.toBe(markup("folder-open")); + expect(markup("package-check")).not.toBe(markup("package")); + expect(markup("package-plus")).not.toBe(markup("package")); + + view.unmount(); + }); +}); diff --git a/apps/desktop/src/components/ui/icons.tsx b/apps/desktop/src/components/ui/icons.tsx index e5d9318a..ac8a4b2d 100644 --- a/apps/desktop/src/components/ui/icons.tsx +++ b/apps/desktop/src/components/ui/icons.tsx @@ -1,347 +1,332 @@ import { forwardRef, type ForwardRefExoticComponent, type RefAttributes } from "react"; -import { HugeiconsIcon, type HugeiconsIconProps, type IconSvgElement } from "@hugeicons/react"; -import ActivityData from "@hugeicons/core-free-icons/Activity01Icon"; -import AddSquareIconData from "@hugeicons/core-free-icons/AddSquareIcon"; -import AlertCircleData from "@hugeicons/core-free-icons/AlertCircleIcon"; -import AlertTriangleData from "@hugeicons/core-free-icons/Alert01Icon"; -import ArchiveData from "@hugeicons/core-free-icons/ArchiveIcon"; -import ArchiveRestoreData from "@hugeicons/core-free-icons/ArchiveArrowUpIcon"; -import ArrowDownData from "@hugeicons/core-free-icons/ArrowDown01Icon"; -import ArrowLeftData from "@hugeicons/core-free-icons/ArrowLeft01Icon"; -import ArrowRightData from "@hugeicons/core-free-icons/ArrowRight01Icon"; -import ArrowUpData from "@hugeicons/core-free-icons/ArrowUp01Icon"; -import ArrowUpRightData from "@hugeicons/core-free-icons/ArrowUpRight01Icon"; -import AtSignData from "@hugeicons/core-free-icons/AtIcon"; -import BarChartIconData from "@hugeicons/core-free-icons/BarChartIcon"; -import BlocksIconData from "@hugeicons/core-free-icons/BlocksIcon"; -import BotData from "@hugeicons/core-free-icons/BotIcon"; -import BookOpenData from "@hugeicons/core-free-icons/BookOpen01Icon"; -import BoxData from "@hugeicons/core-free-icons/PackageIcon"; -import BoxesData from "@hugeicons/core-free-icons/Package01Icon"; -import BrainData from "@hugeicons/core-free-icons/BrainIcon"; -import BrainCircuitData from "@hugeicons/core-free-icons/AiBrain01Icon"; -import BugData from "@hugeicons/core-free-icons/Bug01Icon"; -import CalendarClockData from "@hugeicons/core-free-icons/TimeScheduleIcon"; -import ChartNoAxesColumnIconData from "@hugeicons/core-free-icons/ChartNoAxesColumnIcon"; -import CheckData from "@hugeicons/core-free-icons/Tick01Icon"; -import CheckIconData from "@hugeicons/core-free-icons/CheckIcon"; -import CheckmarkCircle02IconData from "@hugeicons/core-free-icons/CheckmarkCircle02Icon"; -import ChevronDownData from "@hugeicons/core-free-icons/ArrowDown01Icon"; -import ChevronDownIconData from "@hugeicons/core-free-icons/ChevronDownIcon"; -import ChevronLeftData from "@hugeicons/core-free-icons/ArrowLeft01Icon"; -import ChevronRightData from "@hugeicons/core-free-icons/ArrowRight01Icon"; -import ChevronUpData from "@hugeicons/core-free-icons/ArrowUp01Icon"; -import ChevronUpIconData from "@hugeicons/core-free-icons/ChevronUpIcon"; -import CircleData from "@hugeicons/core-free-icons/CircleIcon"; -import CircleAlertData from "@hugeicons/core-free-icons/AlertCircleIcon"; -import CircleCheckData from "@hugeicons/core-free-icons/CheckmarkCircle01Icon"; -import CircleDotData from "@hugeicons/core-free-icons/CircleIcon"; -import ClapperboardData from "@hugeicons/core-free-icons/FilmRoll01Icon"; -import Clock3Data from "@hugeicons/core-free-icons/Clock03Icon"; -import CodeIconData from "@hugeicons/core-free-icons/CodeIcon"; -import ContainerData from "@hugeicons/core-free-icons/ContainerIcon"; -import CopyData from "@hugeicons/core-free-icons/Copy01Icon"; -import CornerUpLeftData from "@hugeicons/core-free-icons/ArrowTurnUpIcon"; -import CornerUpRightData from "@hugeicons/core-free-icons/ArrowTurnUpIcon"; -import DownloadData from "@hugeicons/core-free-icons/Download01Icon"; -import EllipsisData from "@hugeicons/core-free-icons/MoreHorizontalIcon"; -import EraserData from "@hugeicons/core-free-icons/EraserIcon"; -import ExternalLinkData from "@hugeicons/core-free-icons/ArrowUpRight01Icon"; -import EyeData from "@hugeicons/core-free-icons/ViewIcon"; -import EyeOffData from "@hugeicons/core-free-icons/ViewOffIcon"; -import FileData from "@hugeicons/core-free-icons/File01Icon"; -import FileBracesIconData from "@hugeicons/core-free-icons/FileBracesIcon"; -import FileClockIconData from "@hugeicons/core-free-icons/FileClockIcon"; -import FileCodeData from "@hugeicons/core-free-icons/FileScriptIcon"; -import FileCodeIconData from "@hugeicons/core-free-icons/FileCodeIcon"; -import FileImageData from "@hugeicons/core-free-icons/Image01Icon"; -import FilePlusData from "@hugeicons/core-free-icons/FileAddIcon"; -import FileTextData from "@hugeicons/core-free-icons/File02Icon"; -import FileTextIconData from "@hugeicons/core-free-icons/FileTextIcon"; -import FilterData from "@hugeicons/core-free-icons/FilterIcon"; -import FlagData from "@hugeicons/core-free-icons/Flag01Icon"; -import FolderData from "@hugeicons/core-free-icons/Folder01Icon"; -import FolderDownIconData from "@hugeicons/core-free-icons/FolderDownIcon"; -import FolderOpenData from "@hugeicons/core-free-icons/FolderOpenIcon"; -import FolderPlusData from "@hugeicons/core-free-icons/FolderAddIcon"; -import FolderTreeIconData from "@hugeicons/core-free-icons/FolderTreeIcon"; -import FolderXData from "@hugeicons/core-free-icons/FolderRemoveIcon"; -import GaugeIconData from "@hugeicons/core-free-icons/GaugeIcon"; -import GitBranchData from "@hugeicons/core-free-icons/GitBranchIcon"; -import GitCommitHorizontalData from "@hugeicons/core-free-icons/GitCommitIcon"; -import GitForkData from "@hugeicons/core-free-icons/GitForkIcon"; -import GitMergeData from "@hugeicons/core-free-icons/GitMergeIcon"; -import GitPullRequestData from "@hugeicons/core-free-icons/GitPullRequestIcon"; -import GlobeData from "@hugeicons/core-free-icons/GlobeIcon"; -import Globe2Data from "@hugeicons/core-free-icons/Globe02Icon"; -import GripVerticalData from "@hugeicons/core-free-icons/Drag01Icon"; -import HashData from "@hugeicons/core-free-icons/HashtagIcon"; -import HelpCircleIconData from "@hugeicons/core-free-icons/HelpCircleIcon"; -import HistoryData from "@hugeicons/core-free-icons/WorkHistoryIcon"; -import ImagePlusData from "@hugeicons/core-free-icons/ImageAdd01Icon"; -import InfoData from "@hugeicons/core-free-icons/InformationCircleIcon"; -import InspectionPanelIconData from "@hugeicons/core-free-icons/InspectionPanelIcon"; -import KeyboardData from "@hugeicons/core-free-icons/KeyboardIcon"; -import LaptopData from "@hugeicons/core-free-icons/LaptopIcon"; -import ListChecksData from "@hugeicons/core-free-icons/CheckListIcon"; -import ListRestartData from "@hugeicons/core-free-icons/ListRestartIcon"; -import ListTodoData from "@hugeicons/core-free-icons/CheckListIcon"; -import LoaderCircleData from "@hugeicons/core-free-icons/Loading03Icon"; -import Loading02IconData from "@hugeicons/core-free-icons/Loading02Icon"; -import LogsData from "@hugeicons/core-free-icons/LogsIcon"; -import LockData from "@hugeicons/core-free-icons/LockIcon"; -import LockOpenData from "@hugeicons/core-free-icons/SquareUnlock01Icon"; -import Maximize2Data from "@hugeicons/core-free-icons/Maximize02Icon"; -import MessageCircleData from "@hugeicons/core-free-icons/BubbleChatIcon"; -import MessageCircleQuestionMarkIconData from "@hugeicons/core-free-icons/MessageCircleQuestionMarkIcon"; -import MessageSquareData from "@hugeicons/core-free-icons/Message01Icon"; -import MessageSquarePlusData from "@hugeicons/core-free-icons/MessageAdd01Icon"; -import MessageSquareTextData from "@hugeicons/core-free-icons/Message02Icon"; -import MicData from "@hugeicons/core-free-icons/Mic01Icon"; -import Minimize2Data from "@hugeicons/core-free-icons/Minimize02Icon"; -import MinusData from "@hugeicons/core-free-icons/MinusSignIcon"; -import MonitorData from "@hugeicons/core-free-icons/ComputerIcon"; -import MonitorCogData from "@hugeicons/core-free-icons/ComputerSettingsIcon"; -import MonitorSmartphoneData from "@hugeicons/core-free-icons/ComputerPhoneSyncIcon"; -import SmartphoneData from "@hugeicons/core-free-icons/SmartPhone01Icon"; -import MoonData from "@hugeicons/core-free-icons/MoonIcon"; -import MoreHorizontalData from "@hugeicons/core-free-icons/MoreHorizontalIcon"; -import MoreHorizontalCircle01IconData from "@hugeicons/core-free-icons/MoreHorizontalCircle01Icon"; -import MoreVerticalData from "@hugeicons/core-free-icons/MoreVerticalIcon"; -import MousePointer2Data from "@hugeicons/core-free-icons/Cursor02Icon"; -import OrbitData from "@hugeicons/core-free-icons/Orbit01Icon"; -import PackageData from "@hugeicons/core-free-icons/PackageIcon"; -import PackageCheckData from "@hugeicons/core-free-icons/PackageDeliveredIcon"; -import PackagePlusData from "@hugeicons/core-free-icons/PackagePlusIcon"; -import PaletteData from "@hugeicons/core-free-icons/ColorPickerIcon"; -import PanelBottomData from "@hugeicons/core-free-icons/SidebarBottomIcon"; -import PanelLeftData from "@hugeicons/core-free-icons/PanelLeftIcon"; -import PanelRightData from "@hugeicons/core-free-icons/PanelRightIcon"; -import PauseData from "@hugeicons/core-free-icons/PauseIcon"; -import PawPrintIconData from "@hugeicons/core-free-icons/PawPrintIcon"; -import PenLineData from "@hugeicons/core-free-icons/PenTool01Icon"; -import PencilData from "@hugeicons/core-free-icons/PencilIcon"; -import PinData from "@hugeicons/core-free-icons/PinIcon"; -import PinOffData from "@hugeicons/core-free-icons/PinOffIcon"; -import PlayData from "@hugeicons/core-free-icons/PlayIcon"; -import PlusData from "@hugeicons/core-free-icons/Add01Icon"; -import PowerData from "@hugeicons/core-free-icons/PowerServiceIcon"; -import PuzzleData from "@hugeicons/core-free-icons/PuzzleIcon"; -import RefreshCwData from "@hugeicons/core-free-icons/RefreshIcon"; -import RotateCcwData from "@hugeicons/core-free-icons/RotateLeft01Icon"; -import RotateCwData from "@hugeicons/core-free-icons/RotateClockwiseIcon"; -import RouteData from "@hugeicons/core-free-icons/Route01Icon"; -import SaveData from "@hugeicons/core-free-icons/FloppyDiskIcon"; -import ScanTextData from "@hugeicons/core-free-icons/AiScanIcon"; -import SearchData from "@hugeicons/core-free-icons/Search01Icon"; -import SearchIconData from "@hugeicons/core-free-icons/Search01Icon"; -import SendData from "@hugeicons/core-free-icons/MailSend01Icon"; -import ServerData from "@hugeicons/core-free-icons/ServerStack01Icon"; -import Share2Data from "@hugeicons/core-free-icons/Share02Icon"; -import SettingsData from "@hugeicons/core-free-icons/Settings01Icon"; -import Settings2Data from "@hugeicons/core-free-icons/Settings02Icon"; -import ShieldAlertData from "@hugeicons/core-free-icons/Shield01Icon"; -import ShieldCheckData from "@hugeicons/core-free-icons/SecurityCheckIcon"; -import SlidersHorizontalData from "@hugeicons/core-free-icons/SlidersHorizontalIcon"; -import SparklesData from "@hugeicons/core-free-icons/SparklesIcon"; -import SquareData from "@hugeicons/core-free-icons/SquareIcon"; -import SquareDashedMousePointerIconData from "@hugeicons/core-free-icons/SquareDashedMousePointerIcon"; -import SquareKanbanData from "@hugeicons/core-free-icons/KanbanIcon"; -import SquarePenData from "@hugeicons/core-free-icons/Pen01Icon"; -import StarIconData from "@hugeicons/core-free-icons/StarIcon"; -import StoreData from "@hugeicons/core-free-icons/Store01Icon"; -import SunData from "@hugeicons/core-free-icons/Sun01Icon"; -import TargetData from "@hugeicons/core-free-icons/Target01Icon"; -import TerminalIconData from "@hugeicons/core-free-icons/TerminalIcon"; -import TicketData from "@hugeicons/core-free-icons/Ticket01Icon"; -import Trash2Data from "@hugeicons/core-free-icons/Delete02Icon"; -import TriangleAlertIconData from "@hugeicons/core-free-icons/TriangleAlertIcon"; -import UploadData from "@hugeicons/core-free-icons/Upload01Icon"; -import UserRoundData from "@hugeicons/core-free-icons/UserCircleIcon"; -import WrenchData from "@hugeicons/core-free-icons/Wrench01Icon"; -import WebhookData from "@hugeicons/core-free-icons/WebhookIcon"; -import XData from "@hugeicons/core-free-icons/Cancel01Icon"; -import XCircleData from "@hugeicons/core-free-icons/CancelCircleIcon"; -import XIconData from "@hugeicons/core-free-icons/XIcon"; -import ZapData from "@hugeicons/core-free-icons/FlashIcon"; +import type { Icon as PhosphorIcon, IconProps as PhosphorIconProps } from "@phosphor-icons/react"; +import { PulseIcon } from "@phosphor-icons/react/dist/csr/Pulse"; +import { WarningCircleIcon } from "@phosphor-icons/react/dist/csr/WarningCircle"; +import { WarningIcon } from "@phosphor-icons/react/dist/csr/Warning"; +import { ArchiveIcon } from "@phosphor-icons/react/dist/csr/Archive"; +import { TrayArrowUpIcon } from "@phosphor-icons/react/dist/csr/TrayArrowUp"; +import { TrayArrowDownIcon } from "@phosphor-icons/react/dist/csr/TrayArrowDown"; +import { ArrowDownIcon } from "@phosphor-icons/react/dist/csr/ArrowDown"; +import { ArrowLeftIcon } from "@phosphor-icons/react/dist/csr/ArrowLeft"; +import { ArrowRightIcon } from "@phosphor-icons/react/dist/csr/ArrowRight"; +import { ArrowUpIcon } from "@phosphor-icons/react/dist/csr/ArrowUp"; +import { ArrowUpRightIcon } from "@phosphor-icons/react/dist/csr/ArrowUpRight"; +import { AtIcon } from "@phosphor-icons/react/dist/csr/At"; +import { ChartBarIcon } from "@phosphor-icons/react/dist/csr/ChartBar"; +import { SquaresFourIcon } from "@phosphor-icons/react/dist/csr/SquaresFour"; +import { RobotIcon } from "@phosphor-icons/react/dist/csr/Robot"; +import { BookOpenIcon } from "@phosphor-icons/react/dist/csr/BookOpen"; +import { CubeIcon } from "@phosphor-icons/react/dist/csr/Cube"; +import { StackIcon } from "@phosphor-icons/react/dist/csr/Stack"; +import { BrainIcon } from "@phosphor-icons/react/dist/csr/Brain"; +import { HeadCircuitIcon } from "@phosphor-icons/react/dist/csr/HeadCircuit"; +import { BugIcon } from "@phosphor-icons/react/dist/csr/Bug"; +import { CheckIcon as PhosphorCheckIcon } from "@phosphor-icons/react/dist/csr/Check"; +import { CheckCircleIcon } from "@phosphor-icons/react/dist/csr/CheckCircle"; +import { CaretDownIcon } from "@phosphor-icons/react/dist/csr/CaretDown"; +import { CaretLeftIcon } from "@phosphor-icons/react/dist/csr/CaretLeft"; +import { CaretRightIcon } from "@phosphor-icons/react/dist/csr/CaretRight"; +import { CaretUpIcon } from "@phosphor-icons/react/dist/csr/CaretUp"; +import { CircleIcon as PhosphorCircleIcon } from "@phosphor-icons/react/dist/csr/Circle"; +import { RecordIcon } from "@phosphor-icons/react/dist/csr/Record"; +import { DotsThreeCircleIcon } from "@phosphor-icons/react/dist/csr/DotsThreeCircle"; +import { QuestionIcon } from "@phosphor-icons/react/dist/csr/Question"; +import { FilmSlateIcon } from "@phosphor-icons/react/dist/csr/FilmSlate"; +import { ClockIcon } from "@phosphor-icons/react/dist/csr/Clock"; +import { ClockCountdownIcon } from "@phosphor-icons/react/dist/csr/ClockCountdown"; +import { CodeIcon } from "@phosphor-icons/react/dist/csr/Code"; +import { ShippingContainerIcon } from "@phosphor-icons/react/dist/csr/ShippingContainer"; +import { CopyIcon } from "@phosphor-icons/react/dist/csr/Copy"; +import { ArrowBendUpLeftIcon } from "@phosphor-icons/react/dist/csr/ArrowBendUpLeft"; +import { ArrowBendUpRightIcon } from "@phosphor-icons/react/dist/csr/ArrowBendUpRight"; +import { DownloadSimpleIcon } from "@phosphor-icons/react/dist/csr/DownloadSimple"; +import { DotsThreeIcon } from "@phosphor-icons/react/dist/csr/DotsThree"; +import { EraserIcon } from "@phosphor-icons/react/dist/csr/Eraser"; +import { ArrowSquareOutIcon } from "@phosphor-icons/react/dist/csr/ArrowSquareOut"; +import { EyeIcon } from "@phosphor-icons/react/dist/csr/Eye"; +import { EyeSlashIcon } from "@phosphor-icons/react/dist/csr/EyeSlash"; +import { FileIcon as PhosphorFileIcon } from "@phosphor-icons/react/dist/csr/File"; +import { FileCodeIcon as PhosphorFileCodeIcon } from "@phosphor-icons/react/dist/csr/FileCode"; +import { FileImageIcon } from "@phosphor-icons/react/dist/csr/FileImage"; +import { FilePlusIcon as PhosphorFilePlusIcon } from "@phosphor-icons/react/dist/csr/FilePlus"; +import { FileTextIcon as PhosphorFileTextIcon } from "@phosphor-icons/react/dist/csr/FileText"; +import { FunnelIcon } from "@phosphor-icons/react/dist/csr/Funnel"; +import { FlagIcon } from "@phosphor-icons/react/dist/csr/Flag"; +import { FolderIcon as PhosphorFolderIcon } from "@phosphor-icons/react/dist/csr/Folder"; +import { FolderOpenIcon as PhosphorFolderOpenIcon } from "@phosphor-icons/react/dist/csr/FolderOpen"; +import { FolderPlusIcon as PhosphorFolderPlusIcon } from "@phosphor-icons/react/dist/csr/FolderPlus"; +import { TreeStructureIcon } from "@phosphor-icons/react/dist/csr/TreeStructure"; +import { FolderMinusIcon } from "@phosphor-icons/react/dist/csr/FolderMinus"; +import { GaugeIcon as PhosphorGaugeIcon } from "@phosphor-icons/react/dist/csr/Gauge"; +import { GitBranchIcon as PhosphorGitBranchIcon } from "@phosphor-icons/react/dist/csr/GitBranch"; +import { GitCommitIcon } from "@phosphor-icons/react/dist/csr/GitCommit"; +import { GitForkIcon as PhosphorGitForkIcon } from "@phosphor-icons/react/dist/csr/GitFork"; +import { GitMergeIcon as PhosphorGitMergeIcon } from "@phosphor-icons/react/dist/csr/GitMerge"; +import { GitPullRequestIcon as PhosphorGitPullRequestIcon } from "@phosphor-icons/react/dist/csr/GitPullRequest"; +import { GlobeIcon as PhosphorGlobeIcon } from "@phosphor-icons/react/dist/csr/Globe"; +import { GlobeSimpleIcon } from "@phosphor-icons/react/dist/csr/GlobeSimple"; +import { DotsSixVerticalIcon } from "@phosphor-icons/react/dist/csr/DotsSixVertical"; +import { HashIcon as PhosphorHashIcon } from "@phosphor-icons/react/dist/csr/Hash"; +import { ClockCounterClockwiseIcon } from "@phosphor-icons/react/dist/csr/ClockCounterClockwise"; +import { ImagesSquareIcon } from "@phosphor-icons/react/dist/csr/ImagesSquare"; +import { InfoIcon as PhosphorInfoIcon } from "@phosphor-icons/react/dist/csr/Info"; +import { BrowserIcon as PhosphorBrowserIcon } from "@phosphor-icons/react/dist/csr/Browser"; +import { KeyboardIcon as PhosphorKeyboardIcon } from "@phosphor-icons/react/dist/csr/Keyboard"; +import { LaptopIcon as PhosphorLaptopIcon } from "@phosphor-icons/react/dist/csr/Laptop"; +import { ListChecksIcon } from "@phosphor-icons/react/dist/csr/ListChecks"; +import { ArrowsCounterClockwiseIcon } from "@phosphor-icons/react/dist/csr/ArrowsCounterClockwise"; +import { SpinnerGapIcon } from "@phosphor-icons/react/dist/csr/SpinnerGap"; +import { CircleNotchIcon } from "@phosphor-icons/react/dist/csr/CircleNotch"; +import { ArticleIcon } from "@phosphor-icons/react/dist/csr/Article"; +import { LockIcon as PhosphorLockIcon } from "@phosphor-icons/react/dist/csr/Lock"; +import { LockOpenIcon as PhosphorLockOpenIcon } from "@phosphor-icons/react/dist/csr/LockOpen"; +import { ArrowsOutSimpleIcon } from "@phosphor-icons/react/dist/csr/ArrowsOutSimple"; +import { ChatCircleIcon } from "@phosphor-icons/react/dist/csr/ChatCircle"; +import { ChatCircleDotsIcon } from "@phosphor-icons/react/dist/csr/ChatCircleDots"; +import { ChatIcon } from "@phosphor-icons/react/dist/csr/Chat"; +import { ChatsIcon } from "@phosphor-icons/react/dist/csr/Chats"; +import { ChatTextIcon } from "@phosphor-icons/react/dist/csr/ChatText"; +import { MicrophoneIcon } from "@phosphor-icons/react/dist/csr/Microphone"; +import { ArrowsInSimpleIcon } from "@phosphor-icons/react/dist/csr/ArrowsInSimple"; +import { MinusIcon as PhosphorMinusIcon } from "@phosphor-icons/react/dist/csr/Minus"; +import { MonitorIcon as PhosphorMonitorIcon } from "@phosphor-icons/react/dist/csr/Monitor"; +import { DevicesIcon } from "@phosphor-icons/react/dist/csr/Devices"; +import { DeviceMobileIcon } from "@phosphor-icons/react/dist/csr/DeviceMobile"; +import { MoonIcon as PhosphorMoonIcon } from "@phosphor-icons/react/dist/csr/Moon"; +import { DotsThreeVerticalIcon } from "@phosphor-icons/react/dist/csr/DotsThreeVertical"; +import { CursorIcon } from "@phosphor-icons/react/dist/csr/Cursor"; +import { PlanetIcon } from "@phosphor-icons/react/dist/csr/Planet"; +import { PackageIcon as PhosphorPackageIcon } from "@phosphor-icons/react/dist/csr/Package"; +import { PaletteIcon as PhosphorPaletteIcon } from "@phosphor-icons/react/dist/csr/Palette"; +import { RowsIcon } from "@phosphor-icons/react/dist/csr/Rows"; +import { SidebarIcon } from "@phosphor-icons/react/dist/csr/Sidebar"; +import { PauseIcon as PhosphorPauseIcon } from "@phosphor-icons/react/dist/csr/Pause"; +import { PawPrintIcon as PhosphorPawPrintIcon } from "@phosphor-icons/react/dist/csr/PawPrint"; +import { PenNibIcon } from "@phosphor-icons/react/dist/csr/PenNib"; +import { PencilSimpleIcon } from "@phosphor-icons/react/dist/csr/PencilSimple"; +import { PushPinIcon } from "@phosphor-icons/react/dist/csr/PushPin"; +import { PushPinSlashIcon } from "@phosphor-icons/react/dist/csr/PushPinSlash"; +import { PlayIcon as PhosphorPlayIcon } from "@phosphor-icons/react/dist/csr/Play"; +import { PlusIcon as PhosphorPlusIcon } from "@phosphor-icons/react/dist/csr/Plus"; +import { PowerIcon as PhosphorPowerIcon } from "@phosphor-icons/react/dist/csr/Power"; +import { PuzzlePieceIcon } from "@phosphor-icons/react/dist/csr/PuzzlePiece"; +import { ArrowsClockwiseIcon } from "@phosphor-icons/react/dist/csr/ArrowsClockwise"; +import { ArrowCounterClockwiseIcon } from "@phosphor-icons/react/dist/csr/ArrowCounterClockwise"; +import { ArrowClockwiseIcon } from "@phosphor-icons/react/dist/csr/ArrowClockwise"; +import { PathIcon } from "@phosphor-icons/react/dist/csr/Path"; +import { FloppyDiskIcon } from "@phosphor-icons/react/dist/csr/FloppyDisk"; +import { ScanIcon } from "@phosphor-icons/react/dist/csr/Scan"; +import { MagnifyingGlassIcon } from "@phosphor-icons/react/dist/csr/MagnifyingGlass"; +import { PaperPlaneTiltIcon } from "@phosphor-icons/react/dist/csr/PaperPlaneTilt"; +import { DatabaseIcon } from "@phosphor-icons/react/dist/csr/Database"; +import { ShareNetworkIcon } from "@phosphor-icons/react/dist/csr/ShareNetwork"; +import { GearIcon } from "@phosphor-icons/react/dist/csr/Gear"; +import { GearSixIcon } from "@phosphor-icons/react/dist/csr/GearSix"; +import { ShieldWarningIcon } from "@phosphor-icons/react/dist/csr/ShieldWarning"; +import { ShieldCheckIcon as PhosphorShieldCheckIcon } from "@phosphor-icons/react/dist/csr/ShieldCheck"; +import { SlidersHorizontalIcon } from "@phosphor-icons/react/dist/csr/SlidersHorizontal"; +import { SparkleIcon } from "@phosphor-icons/react/dist/csr/Sparkle"; +import { SquareIcon as PhosphorSquareIcon } from "@phosphor-icons/react/dist/csr/Square"; +import { SelectionIcon } from "@phosphor-icons/react/dist/csr/Selection"; +import { KanbanIcon } from "@phosphor-icons/react/dist/csr/Kanban"; +import { NotePencilIcon } from "@phosphor-icons/react/dist/csr/NotePencil"; +import { PlusSquareIcon } from "@phosphor-icons/react/dist/csr/PlusSquare"; +import { StarIcon as PhosphorStarIcon } from "@phosphor-icons/react/dist/csr/Star"; +import { StorefrontIcon } from "@phosphor-icons/react/dist/csr/Storefront"; +import { SunIcon as PhosphorSunIcon } from "@phosphor-icons/react/dist/csr/Sun"; +import { TargetIcon as PhosphorTargetIcon } from "@phosphor-icons/react/dist/csr/Target"; +import { TerminalIcon as PhosphorTerminalIcon } from "@phosphor-icons/react/dist/csr/Terminal"; +import { TicketIcon as PhosphorTicketIcon } from "@phosphor-icons/react/dist/csr/Ticket"; +import { TrashIcon } from "@phosphor-icons/react/dist/csr/Trash"; +import { UploadSimpleIcon } from "@phosphor-icons/react/dist/csr/UploadSimple"; +import { UserCircleIcon } from "@phosphor-icons/react/dist/csr/UserCircle"; +import { WrenchIcon as PhosphorWrenchIcon } from "@phosphor-icons/react/dist/csr/Wrench"; +import { WebhooksLogoIcon } from "@phosphor-icons/react/dist/csr/WebhooksLogo"; +import { XIcon as PhosphorXIcon } from "@phosphor-icons/react/dist/csr/X"; +import { XCircleIcon as PhosphorXCircleIcon } from "@phosphor-icons/react/dist/csr/XCircle"; +import { LightningIcon } from "@phosphor-icons/react/dist/csr/Lightning"; -export type IconProps = Omit; -export type HugeIcon = ForwardRefExoticComponent< - IconProps & RefAttributes ->; +export type IconProps = Omit; +export type AppIcon = ForwardRefExoticComponent>; -function createIcon(icon: IconSvgElement, displayName: string): HugeIcon { +function createIcon( + Source: PhosphorIcon, + displayName: string, + defaults: Partial = {}, +): AppIcon { const Component = forwardRef( - ({ strokeWidth = 1.7, ...props }, ref) => ( - - ), + ({ children, color = "currentColor", weight = "regular", ...props }, ref) => { + // Render-prop primitives may inject a text fallback; glyphs stay atomic like the old adapter. + void children; + return ; + }, ); Component.displayName = displayName; return Component; } -export const Activity = createIcon(ActivityData, "Activity"); -export const AlertCircle = createIcon(AlertCircleData, "AlertCircle"); -export const AlertTriangle = createIcon(AlertTriangleData, "AlertTriangle"); -export const Archive = createIcon(ArchiveData, "Archive"); -export const ArchiveRestore = createIcon(ArchiveRestoreData, "ArchiveRestore"); -export const ArrowDown = createIcon(ArrowDownData, "ArrowDown"); -export const ArrowLeft = createIcon(ArrowLeftData, "ArrowLeft"); -export const ArrowRight = createIcon(ArrowRightData, "ArrowRight"); -export const ArrowUp = createIcon(ArrowUpData, "ArrowUp"); -export const ArrowUpRight = createIcon(ArrowUpRightData, "ArrowUpRight"); -export const AtSign = createIcon(AtSignData, "AtSign"); -export const BarChart3 = createIcon(BarChartIconData, "BarChart3"); -export const Blocks = createIcon(BlocksIconData, "Blocks"); -export const Bot = createIcon(BotData, "Bot"); -export const BookOpen = createIcon(BookOpenData, "BookOpen"); -export const Box = createIcon(BoxData, "Box"); -export const Boxes = createIcon(BoxesData, "Boxes"); -export const Brain = createIcon(BrainData, "Brain"); -export const BrainCircuit = createIcon(BrainCircuitData, "BrainCircuit"); -export const Bug = createIcon(BugData, "Bug"); -export const CalendarClock = createIcon(CalendarClockData, "CalendarClock"); -export const ChartNoAxesColumn = createIcon(ChartNoAxesColumnIconData, "ChartNoAxesColumn"); -export const Check = createIcon(CheckData, "Check"); -export const CheckCircle2 = createIcon(CheckmarkCircle02IconData, "CheckCircle2"); -export const CheckIcon = createIcon(CheckIconData, "CheckIcon"); -export const ChevronDown = createIcon(ChevronDownData, "ChevronDown"); -export const ChevronDownIcon = createIcon(ChevronDownIconData, "ChevronDownIcon"); -export const ChevronLeft = createIcon(ChevronLeftData, "ChevronLeft"); -export const ChevronRight = createIcon(ChevronRightData, "ChevronRight"); -export const ChevronUp = createIcon(ChevronUpData, "ChevronUp"); -export const ChevronUpIcon = createIcon(ChevronUpIconData, "ChevronUpIcon"); -export const Circle = createIcon(CircleData, "Circle"); -export const CircleAlert = createIcon(CircleAlertData, "CircleAlert"); -export const CircleCheck = createIcon(CircleCheckData, "CircleCheck"); -export const CircleDot = createIcon(CircleDotData, "CircleDot"); -export const CircleEllipsis = createIcon(MoreHorizontalCircle01IconData, "CircleEllipsis"); -export const CircleHelp = createIcon(HelpCircleIconData, "CircleHelp"); -export const Clapperboard = createIcon(ClapperboardData, "Clapperboard"); -export const Clock3 = createIcon(Clock3Data, "Clock3"); -export const Code2 = createIcon(CodeIconData, "Code2"); -export const Container = createIcon(ContainerData, "Container"); -export const Copy = createIcon(CopyData, "Copy"); -export const CornerUpLeft = createIcon(CornerUpLeftData, "CornerUpLeft"); -export const CornerUpRight = createIcon(CornerUpRightData, "CornerUpRight"); -export const Download = createIcon(DownloadData, "Download"); -export const Ellipsis = createIcon(EllipsisData, "Ellipsis"); -export const Eraser = createIcon(EraserData, "Eraser"); -export const ExternalLink = createIcon(ExternalLinkData, "ExternalLink"); -export const Eye = createIcon(EyeData, "Eye"); -export const EyeOff = createIcon(EyeOffData, "EyeOff"); -export const File = createIcon(FileData, "File"); -export const FileClock = createIcon(FileClockIconData, "FileClock"); -export const FileCode = createIcon(FileCodeData, "FileCode"); -export const FileCode2 = createIcon(FileCodeIconData, "FileCode2"); -export const FileImage = createIcon(FileImageData, "FileImage"); -export const FileJson = createIcon(FileBracesIconData, "FileJson"); -export const FilePlus = createIcon(FilePlusData, "FilePlus"); -export const FileText = createIcon(FileTextData, "FileText"); -export const FileType = createIcon(FileTextIconData, "FileType"); -export const Filter = createIcon(FilterData, "Filter"); -export const Flag = createIcon(FlagData, "Flag"); -export const Folder = createIcon(FolderData, "Folder"); -export const FolderDown = createIcon(FolderDownIconData, "FolderDown"); -export const FolderOpen = createIcon(FolderOpenData, "FolderOpen"); -export const FolderPlus = createIcon(FolderPlusData, "FolderPlus"); -export const FolderTree = createIcon(FolderTreeIconData, "FolderTree"); -export const FolderX = createIcon(FolderXData, "FolderX"); -export const Gauge = createIcon(GaugeIconData, "Gauge"); -export const GitBranch = createIcon(GitBranchData, "GitBranch"); -export const GitCommitHorizontal = createIcon(GitCommitHorizontalData, "GitCommitHorizontal"); -export const GitFork = createIcon(GitForkData, "GitFork"); -export const GitMerge = createIcon(GitMergeData, "GitMerge"); -export const GitPullRequest = createIcon(GitPullRequestData, "GitPullRequest"); -export const Globe = createIcon(GlobeData, "Globe"); -export const Globe2 = createIcon(Globe2Data, "Globe2"); -export const GripVertical = createIcon(GripVerticalData, "GripVertical"); -export const Hash = createIcon(HashData, "Hash"); -export const History = createIcon(HistoryData, "History"); -export const ImagePlus = createIcon(ImagePlusData, "ImagePlus"); -export const Info = createIcon(InfoData, "Info"); -export const Inspect = createIcon(InspectionPanelIconData, "Inspect"); -export const Keyboard = createIcon(KeyboardData, "Keyboard"); -export const Laptop = createIcon(LaptopData, "Laptop"); -export const ListChecks = createIcon(ListChecksData, "ListChecks"); -export const ListRestart = createIcon(ListRestartData, "ListRestart"); -export const ListTodo = createIcon(ListTodoData, "ListTodo"); -export const Loader2 = createIcon(Loading02IconData, "Loader2"); -export const LoaderCircle = createIcon(LoaderCircleData, "LoaderCircle"); -export const Logs = createIcon(LogsData, "Logs"); -export const Lock = createIcon(LockData, "Lock"); -export const LockOpen = createIcon(LockOpenData, "LockOpen"); -export const Maximize2 = createIcon(Maximize2Data, "Maximize2"); -export const MessageCircle = createIcon(MessageCircleData, "MessageCircle"); -export const MessageCircleQuestion = createIcon(MessageCircleQuestionMarkIconData, "MessageCircleQuestion"); -export const MessageSquare = createIcon(MessageSquareData, "MessageSquare"); -export const MessageSquarePlus = createIcon(MessageSquarePlusData, "MessageSquarePlus"); -export const MessageSquareText = createIcon(MessageSquareTextData, "MessageSquareText"); -export const Mic = createIcon(MicData, "Mic"); -export const Minimize2 = createIcon(Minimize2Data, "Minimize2"); -export const Minus = createIcon(MinusData, "Minus"); -export const Monitor = createIcon(MonitorData, "Monitor"); -export const MonitorCog = createIcon(MonitorCogData, "MonitorCog"); -export const MonitorSmartphone = createIcon(MonitorSmartphoneData, "MonitorSmartphone"); -export const Smartphone = createIcon(SmartphoneData, "Smartphone"); -export const Moon = createIcon(MoonData, "Moon"); -export const MoreHorizontal = createIcon(MoreHorizontalData, "MoreHorizontal"); -export const MoreVertical = createIcon(MoreVerticalData, "MoreVertical"); -export const MousePointer2 = createIcon(MousePointer2Data, "MousePointer2"); -export const Orbit = createIcon(OrbitData, "Orbit"); -export const Package = createIcon(PackageData, "Package"); -export const PackageCheck = createIcon(PackageCheckData, "PackageCheck"); -export const PackagePlus = createIcon(PackagePlusData, "PackagePlus"); -export const Palette = createIcon(PaletteData, "Palette"); -export const PanelBottom = createIcon(PanelBottomData, "PanelBottom"); -export const PanelLeft = createIcon(PanelLeftData, "PanelLeft"); -export const PanelRight = createIcon(PanelRightData, "PanelRight"); -export const Pause = createIcon(PauseData, "Pause"); -export const PawPrint = createIcon(PawPrintIconData, "PawPrint"); -export const PenLine = createIcon(PenLineData, "PenLine"); -export const Pencil = createIcon(PencilData, "Pencil"); -export const Pin = createIcon(PinData, "Pin"); -export const PinOff = createIcon(PinOffData, "PinOff"); -export const Play = createIcon(PlayData, "Play"); -export const Plus = createIcon(PlusData, "Plus"); -export const Power = createIcon(PowerData, "Power"); -export const Puzzle = createIcon(PuzzleData, "Puzzle"); -export const RefreshCw = createIcon(RefreshCwData, "RefreshCw"); -export const RotateCcw = createIcon(RotateCcwData, "RotateCcw"); -export const RotateCw = createIcon(RotateCwData, "RotateCw"); -export const Route = createIcon(RouteData, "Route"); -export const Save = createIcon(SaveData, "Save"); -export const ScanText = createIcon(ScanTextData, "ScanText"); -export const Search = createIcon(SearchData, "Search"); -export const SearchIcon = createIcon(SearchIconData, "SearchIcon"); -export const Send = createIcon(SendData, "Send"); -export const Server = createIcon(ServerData, "Server"); -export const Share2 = createIcon(Share2Data, "Share2"); -export const Settings = createIcon(SettingsData, "Settings"); -export const Settings2 = createIcon(Settings2Data, "Settings2"); -export const ShieldAlert = createIcon(ShieldAlertData, "ShieldAlert"); -export const ShieldCheck = createIcon(ShieldCheckData, "ShieldCheck"); -export const SlidersHorizontal = createIcon(SlidersHorizontalData, "SlidersHorizontal"); -export const Sparkles = createIcon(SparklesData, "Sparkles"); -export const Square = createIcon(SquareData, "Square"); -export const SquareDashedMousePointer = createIcon(SquareDashedMousePointerIconData, "SquareDashedMousePointer"); -export const SquareKanban = createIcon(SquareKanbanData, "SquareKanban"); -export const SquarePen = createIcon(SquarePenData, "SquarePen"); -export const SquarePlus = createIcon(AddSquareIconData, "SquarePlus"); -export const Star = createIcon(StarIconData, "Star"); -export const Store = createIcon(StoreData, "Store"); -export const Sun = createIcon(SunData, "Sun"); -export const Target = createIcon(TargetData, "Target"); -export const Terminal = createIcon(TerminalIconData, "Terminal"); -export const TerminalIcon = createIcon(TerminalIconData, "TerminalIcon"); -export const Ticket = createIcon(TicketData, "Ticket"); -export const Trash2 = createIcon(Trash2Data, "Trash2"); -export const TriangleAlert = createIcon(TriangleAlertIconData, "TriangleAlert"); -export const Upload = createIcon(UploadData, "Upload"); -export const UserRound = createIcon(UserRoundData, "UserRound"); -export const Wrench = createIcon(WrenchData, "Wrench"); -export const Webhook = createIcon(WebhookData, "Webhook"); -export const X = createIcon(XData, "X"); -export const XCircle = createIcon(XCircleData, "XCircle"); -export const XIcon = createIcon(XIconData, "XIcon"); -export const Zap = createIcon(ZapData, "Zap"); +export const Activity = createIcon(PulseIcon, "Activity"); +export const AlertCircle = createIcon(WarningCircleIcon, "AlertCircle"); +export const AlertTriangle = createIcon(WarningIcon, "AlertTriangle"); +export const Archive = createIcon(ArchiveIcon, "Archive"); +export const ArchiveRestore = createIcon(TrayArrowUpIcon, "ArchiveRestore"); +export const ArrowDown = createIcon(ArrowDownIcon, "ArrowDown"); +export const ArrowLeft = createIcon(ArrowLeftIcon, "ArrowLeft"); +export const ArrowRight = createIcon(ArrowRightIcon, "ArrowRight"); +export const ArrowUp = createIcon(ArrowUpIcon, "ArrowUp"); +export const ArrowUpRight = createIcon(ArrowUpRightIcon, "ArrowUpRight"); +export const AtSign = createIcon(AtIcon, "AtSign"); +export const BarChart3 = createIcon(ChartBarIcon, "BarChart3"); +export const Blocks = createIcon(SquaresFourIcon, "Blocks"); +export const Bot = createIcon(RobotIcon, "Bot"); +export const BookOpen = createIcon(BookOpenIcon, "BookOpen"); +export const Box = createIcon(CubeIcon, "Box"); +export const Boxes = createIcon(StackIcon, "Boxes"); +export const Brain = createIcon(BrainIcon, "Brain"); +export const BrainCircuit = createIcon(HeadCircuitIcon, "BrainCircuit"); +export const Bug = createIcon(BugIcon, "Bug"); +export const CalendarClock = createIcon(ClockCountdownIcon, "CalendarClock"); +export const ChartNoAxesColumn = createIcon(ChartBarIcon, "ChartNoAxesColumn"); +export const Check = createIcon(PhosphorCheckIcon, "Check"); +export const CheckCircle2 = createIcon(CheckCircleIcon, "CheckCircle2"); +export const CheckIcon = createIcon(PhosphorCheckIcon, "CheckIcon"); +export const ChevronDown = createIcon(CaretDownIcon, "ChevronDown"); +export const ChevronDownIcon = createIcon(CaretDownIcon, "ChevronDownIcon"); +export const ChevronLeft = createIcon(CaretLeftIcon, "ChevronLeft"); +export const ChevronRight = createIcon(CaretRightIcon, "ChevronRight"); +export const ChevronUp = createIcon(CaretUpIcon, "ChevronUp"); +export const ChevronUpIcon = createIcon(CaretUpIcon, "ChevronUpIcon"); +export const Circle = createIcon(PhosphorCircleIcon, "Circle"); +export const CircleAlert = createIcon(WarningCircleIcon, "CircleAlert"); +export const CircleCheck = createIcon(CheckCircleIcon, "CircleCheck"); +export const CircleDot = createIcon(RecordIcon, "CircleDot"); +export const CircleEllipsis = createIcon(DotsThreeCircleIcon, "CircleEllipsis"); +export const CircleHelp = createIcon(QuestionIcon, "CircleHelp"); +export const Clapperboard = createIcon(FilmSlateIcon, "Clapperboard"); +export const Clock3 = createIcon(ClockIcon, "Clock3"); +export const Code2 = createIcon(CodeIcon, "Code2"); +export const Container = createIcon(ShippingContainerIcon, "Container"); +export const Copy = createIcon(CopyIcon, "Copy"); +export const CornerUpLeft = createIcon(ArrowBendUpLeftIcon, "CornerUpLeft"); +export const CornerUpRight = createIcon(ArrowBendUpRightIcon, "CornerUpRight"); +export const Download = createIcon(DownloadSimpleIcon, "Download"); +export const Ellipsis = createIcon(DotsThreeIcon, "Ellipsis"); +export const Eraser = createIcon(EraserIcon, "Eraser"); +export const ExternalLink = createIcon(ArrowSquareOutIcon, "ExternalLink"); +export const Eye = createIcon(EyeIcon, "Eye"); +export const EyeOff = createIcon(EyeSlashIcon, "EyeOff"); +export const File = createIcon(PhosphorFileIcon, "File"); +export const FileClock = createIcon(ClockCounterClockwiseIcon, "FileClock"); +export const FileCode = createIcon(PhosphorFileCodeIcon, "FileCode"); +export const FileCode2 = createIcon(PhosphorFileCodeIcon, "FileCode2"); +export const FileImage = createIcon(FileImageIcon, "FileImage"); +export const FileJson = createIcon(PhosphorFileCodeIcon, "FileJson"); +export const FilePlus = createIcon(PhosphorFilePlusIcon, "FilePlus"); +export const FileText = createIcon(PhosphorFileTextIcon, "FileText"); +export const FileType = createIcon(PhosphorFileTextIcon, "FileType"); +export const Filter = createIcon(FunnelIcon, "Filter"); +export const Flag = createIcon(FlagIcon, "Flag"); +export const Folder = createIcon(PhosphorFolderIcon, "Folder"); +export const FolderDown = createIcon(TrayArrowDownIcon, "FolderDown"); +export const FolderOpen = createIcon(PhosphorFolderOpenIcon, "FolderOpen"); +export const FolderPlus = createIcon(PhosphorFolderPlusIcon, "FolderPlus"); +export const FolderTree = createIcon(TreeStructureIcon, "FolderTree"); +export const FolderX = createIcon(FolderMinusIcon, "FolderX"); +export const Gauge = createIcon(PhosphorGaugeIcon, "Gauge"); +export const GitBranch = createIcon(PhosphorGitBranchIcon, "GitBranch"); +export const GitCommitHorizontal = createIcon(GitCommitIcon, "GitCommitHorizontal"); +export const GitFork = createIcon(PhosphorGitForkIcon, "GitFork"); +export const GitMerge = createIcon(PhosphorGitMergeIcon, "GitMerge"); +export const GitPullRequest = createIcon(PhosphorGitPullRequestIcon, "GitPullRequest"); +export const Globe = createIcon(PhosphorGlobeIcon, "Globe"); +export const Globe2 = createIcon(GlobeSimpleIcon, "Globe2"); +export const GripVertical = createIcon(DotsSixVerticalIcon, "GripVertical"); +export const Hash = createIcon(PhosphorHashIcon, "Hash"); +export const History = createIcon(ClockCounterClockwiseIcon, "History"); +export const ImagePlus = createIcon(ImagesSquareIcon, "ImagePlus"); +export const Info = createIcon(PhosphorInfoIcon, "Info"); +export const Inspect = createIcon(PhosphorBrowserIcon, "Inspect"); +export const Keyboard = createIcon(PhosphorKeyboardIcon, "Keyboard"); +export const Laptop = createIcon(PhosphorLaptopIcon, "Laptop"); +export const ListChecks = createIcon(ListChecksIcon, "ListChecks"); +export const ListRestart = createIcon(ArrowsCounterClockwiseIcon, "ListRestart"); +export const ListTodo = createIcon(ListChecksIcon, "ListTodo"); +export const Loader2 = createIcon(SpinnerGapIcon, "Loader2"); +export const LoaderCircle = createIcon(CircleNotchIcon, "LoaderCircle"); +export const Logs = createIcon(ArticleIcon, "Logs"); +export const Lock = createIcon(PhosphorLockIcon, "Lock"); +export const LockOpen = createIcon(PhosphorLockOpenIcon, "LockOpen"); +export const Maximize2 = createIcon(ArrowsOutSimpleIcon, "Maximize2"); +export const MessageCircle = createIcon(ChatCircleIcon, "MessageCircle"); +export const MessageCircleQuestion = createIcon(ChatCircleDotsIcon, "MessageCircleQuestion"); +export const MessageSquare = createIcon(ChatIcon, "MessageSquare"); +export const MessageSquarePlus = createIcon(ChatsIcon, "MessageSquarePlus"); +export const MessageSquareText = createIcon(ChatTextIcon, "MessageSquareText"); +export const Mic = createIcon(MicrophoneIcon, "Mic"); +export const Minimize2 = createIcon(ArrowsInSimpleIcon, "Minimize2"); +export const Minus = createIcon(PhosphorMinusIcon, "Minus"); +export const Monitor = createIcon(PhosphorMonitorIcon, "Monitor"); +export const MonitorCog = createIcon(GearSixIcon, "MonitorCog"); +export const MonitorSmartphone = createIcon(DevicesIcon, "MonitorSmartphone"); +export const Smartphone = createIcon(DeviceMobileIcon, "Smartphone"); +export const Moon = createIcon(PhosphorMoonIcon, "Moon"); +export const MoreHorizontal = createIcon(DotsThreeIcon, "MoreHorizontal"); +export const MoreVertical = createIcon(DotsThreeVerticalIcon, "MoreVertical"); +export const MousePointer2 = createIcon(CursorIcon, "MousePointer2"); +export const Orbit = createIcon(PlanetIcon, "Orbit"); +export const Package = createIcon(PhosphorPackageIcon, "Package"); +export const PackageCheck = createIcon(PhosphorShieldCheckIcon, "PackageCheck"); +export const PackagePlus = createIcon(DownloadSimpleIcon, "PackagePlus"); +export const Palette = createIcon(PhosphorPaletteIcon, "Palette"); +export const PanelBottom = createIcon(RowsIcon, "PanelBottom"); +export const PanelLeft = createIcon(SidebarIcon, "PanelLeft"); +export const PanelRight = createIcon(SidebarIcon, "PanelRight", { mirrored: true }); +export const Pause = createIcon(PhosphorPauseIcon, "Pause"); +export const PawPrint = createIcon(PhosphorPawPrintIcon, "PawPrint"); +export const PenLine = createIcon(PenNibIcon, "PenLine"); +export const Pencil = createIcon(PencilSimpleIcon, "Pencil"); +export const Pin = createIcon(PushPinIcon, "Pin"); +export const PinOff = createIcon(PushPinSlashIcon, "PinOff"); +export const Play = createIcon(PhosphorPlayIcon, "Play"); +export const Plus = createIcon(PhosphorPlusIcon, "Plus"); +export const Power = createIcon(PhosphorPowerIcon, "Power"); +export const Puzzle = createIcon(PuzzlePieceIcon, "Puzzle"); +export const RefreshCw = createIcon(ArrowsClockwiseIcon, "RefreshCw"); +export const RotateCcw = createIcon(ArrowCounterClockwiseIcon, "RotateCcw"); +export const RotateCw = createIcon(ArrowClockwiseIcon, "RotateCw"); +export const Route = createIcon(PathIcon, "Route"); +export const Save = createIcon(FloppyDiskIcon, "Save"); +export const ScanText = createIcon(ScanIcon, "ScanText"); +export const Search = createIcon(MagnifyingGlassIcon, "Search"); +export const SearchIcon = createIcon(MagnifyingGlassIcon, "SearchIcon"); +export const Send = createIcon(PaperPlaneTiltIcon, "Send"); +export const Server = createIcon(DatabaseIcon, "Server"); +export const Share2 = createIcon(ShareNetworkIcon, "Share2"); +export const Settings = createIcon(GearIcon, "Settings"); +export const Settings2 = createIcon(GearSixIcon, "Settings2"); +export const ShieldAlert = createIcon(ShieldWarningIcon, "ShieldAlert"); +export const ShieldCheck = createIcon(PhosphorShieldCheckIcon, "ShieldCheck"); +export const SlidersHorizontal = createIcon(SlidersHorizontalIcon, "SlidersHorizontal"); +export const Sparkles = createIcon(SparkleIcon, "Sparkles"); +export const Square = createIcon(PhosphorSquareIcon, "Square"); +export const SquareDashedMousePointer = createIcon(SelectionIcon, "SquareDashedMousePointer"); +export const SquareKanban = createIcon(KanbanIcon, "SquareKanban"); +export const SquarePen = createIcon(NotePencilIcon, "SquarePen"); +export const SquarePlus = createIcon(PlusSquareIcon, "SquarePlus"); +export const Star = createIcon(PhosphorStarIcon, "Star"); +export const Store = createIcon(StorefrontIcon, "Store"); +export const Sun = createIcon(PhosphorSunIcon, "Sun"); +export const Target = createIcon(PhosphorTargetIcon, "Target"); +export const Terminal = createIcon(PhosphorTerminalIcon, "Terminal"); +export const TerminalIcon = createIcon(PhosphorTerminalIcon, "TerminalIcon"); +export const Ticket = createIcon(PhosphorTicketIcon, "Ticket"); +export const Trash2 = createIcon(TrashIcon, "Trash2"); +export const TriangleAlert = createIcon(WarningIcon, "TriangleAlert"); +export const Upload = createIcon(UploadSimpleIcon, "Upload"); +export const UserRound = createIcon(UserCircleIcon, "UserRound"); +export const Wrench = createIcon(PhosphorWrenchIcon, "Wrench"); +export const Webhook = createIcon(WebhooksLogoIcon, "Webhook"); +export const X = createIcon(PhosphorXIcon, "X"); +export const XCircle = createIcon(PhosphorXCircleIcon, "XCircle"); +export const XIcon = createIcon(PhosphorXIcon, "XIcon"); +export const Zap = createIcon(LightningIcon, "Zap"); diff --git a/apps/desktop/src/editor/Editor.tsx b/apps/desktop/src/editor/Editor.tsx index e0f9ace8..97183b46 100644 --- a/apps/desktop/src/editor/Editor.tsx +++ b/apps/desktop/src/editor/Editor.tsx @@ -121,9 +121,8 @@ export interface CanvasInsertOptions { // The `/` "Skills" group, built from the live library. Skills auto-discovered from a harness's // skill directory (~/.claude/skills, .codex/skills, …) carry a `source` and get their own group -// per product. Picking one inserts a real inline skill node either way. Rows all share the -// neutral ✦ glyph — a column of assorted emoji read as noise, and the group label already says -// what a row is; the skill's own icon still shows on the inserted chip. +// per product. Picking one inserts a real inline skill node either way. Rows use shared Phosphor +// concepts; an authored extension icon remains attached to the inserted chip as extension content. function skillItems(editor: CodeTwoEditor, skills: SkillInfo[]): DefaultReactSuggestionItem[] { return skills.map((s) => { const kind = s.kind === "subagent" ? "Subagent" : s.kind === "mcp" ? "MCP" : "Skill"; @@ -144,7 +143,7 @@ function skillItems(editor: CodeTwoEditor, skills: SkillInfo[]): DefaultReactSug return; } editor.insertInlineContent([ - { type: "skill", props: { skillId: s.id, name: s.name, icon: s.icon ?? "✦" } }, + { type: "skill", props: { skillId: s.id, name: s.name, icon: s.icon ?? "" } }, " ", ]); }, @@ -402,7 +401,7 @@ export function DocEditor({ props: { skillId: block.skill_id, name: skill?.name ?? block.skill_id, - icon: skill?.icon ?? "✦", + icon: skill?.icon ?? "", }, }, " "], }]; @@ -655,7 +654,7 @@ export function DocEditor({ return; } editor.insertInlineContent([ - { type: "skill", props: { skillId: skill.id, name: skill.name, icon: skill.icon ?? "✦" } }, + { type: "skill", props: { skillId: skill.id, name: skill.name, icon: skill.icon ?? "" } }, " ", ]); onEmptyChange(false); diff --git a/apps/desktop/src/editor/FileMenu.tsx b/apps/desktop/src/editor/FileMenu.tsx index 251f539d..5852707c 100644 --- a/apps/desktop/src/editor/FileMenu.tsx +++ b/apps/desktop/src/editor/FileMenu.tsx @@ -7,7 +7,7 @@ import { FileType, MessageSquare, Package, - type HugeIcon, + type AppIcon, } from "@/components/ui/icons"; import type { SuggestionMenuProps } from "@blocknote/react"; @@ -48,7 +48,7 @@ export interface ArtifactAtItem { /** Everything the `@` picker can insert: chats, then artifacts, then workspace files. */ export type AtItem = ChatItem | ArtifactAtItem | FileItem; -const BY_EXTENSION: Record = { +const BY_EXTENSION: Record = { ts: FileCode, tsx: FileCode, js: FileCode, jsx: FileCode, rs: FileCode, py: FileCode, go: FileCode, rb: FileCode, sh: FileCode, css: FileCode, html: FileCode, swift: FileCode, json: FileJson, lock: FileJson, @@ -58,7 +58,7 @@ const BY_EXTENSION: Record = { }; /** A page icon on every row says only "this is a file", which the user already knows. */ -function iconFor(name: string): HugeIcon { +function iconFor(name: string): AppIcon { const dot = name.lastIndexOf("."); return (dot > 0 && BY_EXTENSION[name.slice(dot + 1).toLowerCase()]) || FileText; } diff --git a/apps/desktop/src/editor/issueBlock.tsx b/apps/desktop/src/editor/issueBlock.tsx index bfd4772d..f4d2b1f6 100644 --- a/apps/desktop/src/editor/issueBlock.tsx +++ b/apps/desktop/src/editor/issueBlock.tsx @@ -1,5 +1,6 @@ import { createReactBlockSpec } from "@blocknote/react"; import { Badge } from "@/components/ui/badge"; +import { X } from "@/components/ui/icons"; import { TooltipButton } from "@/components/ui/tooltip"; import { useT } from "../i18n"; import type { DocBlock } from "../bridge"; @@ -100,7 +101,7 @@ function IssueRefCard({ className="shrink-0 text-muted-foreground" onClick={onRemove} > - × + ); diff --git a/apps/desktop/src/editor/slotCard.tsx b/apps/desktop/src/editor/slotCard.tsx index 4472ceda..28cf4eba 100644 --- a/apps/desktop/src/editor/slotCard.tsx +++ b/apps/desktop/src/editor/slotCard.tsx @@ -6,6 +6,7 @@ import type { DocBlock } from "../bridge"; import type { SceneSlotDef } from "../session/scene"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Sparkles } from "@/components/ui/icons"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { @@ -436,10 +437,12 @@ export function SlotCardView({ onKeyDown={onKeyDown} >
- {block.props.icon && ( + {block.props.icon.trim() && block.props.icon !== "✦" ? ( {block.props.icon} + ) : ( + )} {block.props.title} diff --git a/apps/desktop/src/environment/EnvironmentPopover.tsx b/apps/desktop/src/environment/EnvironmentPopover.tsx index 612cf2c2..b6361154 100644 --- a/apps/desktop/src/environment/EnvironmentPopover.tsx +++ b/apps/desktop/src/environment/EnvironmentPopover.tsx @@ -13,7 +13,7 @@ import { Settings, SlidersHorizontal, SquarePlus, - type HugeIcon, + type AppIcon, } from "@/components/ui/icons"; import { getArtifact, type GitStatus, type PlanEntry, type Project } from "../bridge"; @@ -23,6 +23,7 @@ import type { InteractiveToolPreview } from "../session/toolActivity"; import type { Turn } from "../session/turns"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; +import { GitSyncStatus } from "../git/GitSyncStatus"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { cn } from "@/lib/utils"; @@ -36,7 +37,7 @@ function EnvironmentRow({ active = false, disabled = false, }: { - icon: HugeIcon; + icon: AppIcon; label: string; description?: string; detail?: ReactNode; @@ -324,11 +325,7 @@ export function EnvironmentPopover({ label={isRepo ? git.branch || "?" : t("rail.notARepo")} detail={ isRepo && (git.ahead > 0 || git.behind > 0) ? ( - - {git.ahead > 0 && `↑${git.ahead}`} - {git.ahead > 0 && git.behind > 0 && " "} - {git.behind > 0 && `↓${git.behind}`} - + ) : undefined } /> diff --git a/apps/desktop/src/git/GitDockContent.tsx b/apps/desktop/src/git/GitDockContent.tsx index 5b11ae9c..63144103 100644 --- a/apps/desktop/src/git/GitDockContent.tsx +++ b/apps/desktop/src/git/GitDockContent.tsx @@ -6,6 +6,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { useT } from "../i18n"; import { cn } from "@/lib/utils"; import { GitHubPullRequestPanel } from "./GitHubPullRequestPanel"; +import { GitSyncStatus } from "./GitSyncStatus"; type GitDockContentProps = { cwd: string | null; @@ -42,8 +43,7 @@ export function GitDockContent({ {status.branch || "?"} - {status.ahead > 0 && ↑{status.ahead}} - {status.behind > 0 && ↓{status.behind}} +
{status.files.length === 0 ? ( diff --git a/apps/desktop/src/git/GitSyncStatus.test.tsx b/apps/desktop/src/git/GitSyncStatus.test.tsx new file mode 100644 index 00000000..a664894d --- /dev/null +++ b/apps/desktop/src/git/GitSyncStatus.test.tsx @@ -0,0 +1,32 @@ +import { afterEach, describe, expect, test } from "bun:test"; +import { activateDom, dom, mount } from "../../tests/domTestHarness"; +import { I18nProvider } from "../i18n"; +import { GitSyncStatus } from "./GitSyncStatus"; + +activateDom(); + +afterEach(() => { + dom.document.body.replaceChildren(); +}); + +describe("GitSyncStatus", () => { + test("keeps ahead and behind meaning available to assistive technology", () => { + const view = mount( + + + , + ); + + expect(view.container.querySelector('[aria-label="2 commits ahead"]')).not.toBeNull(); + expect(view.container.querySelector('[aria-label="3 commits behind"]')).not.toBeNull(); + expect(view.container.querySelectorAll("svg[aria-hidden]")).toHaveLength(2); + + view.unmount(); + }); + + test("renders nothing when the branch is synchronized", () => { + const view = mount(); + expect(view.container.childElementCount).toBe(0); + view.unmount(); + }); +}); diff --git a/apps/desktop/src/git/GitSyncStatus.tsx b/apps/desktop/src/git/GitSyncStatus.tsx new file mode 100644 index 00000000..7c7fab9d --- /dev/null +++ b/apps/desktop/src/git/GitSyncStatus.tsx @@ -0,0 +1,33 @@ +import { ArrowDown, ArrowUp } from "@/components/ui/icons"; +import { cn } from "@/lib/utils"; +import { useT } from "../i18n"; + +export function GitSyncStatus({ + ahead, + behind, + className, +}: { + ahead: number; + behind: number; + className?: string; +}) { + const t = useT(); + if (ahead <= 0 && behind <= 0) return null; + + return ( + + {ahead > 0 ? ( + + + {ahead} + + ) : null} + {behind > 0 ? ( + + + {behind} + + ) : null} + + ); +} diff --git a/apps/desktop/src/git/SourceControl.tsx b/apps/desktop/src/git/SourceControl.tsx index f7fd0442..baa7bc4a 100644 --- a/apps/desktop/src/git/SourceControl.tsx +++ b/apps/desktop/src/git/SourceControl.tsx @@ -46,6 +46,7 @@ import { type GitPhase, type SourceControlLoadState, } from "./state"; +import { GitSyncStatus } from "./GitSyncStatus"; type DiffSelection = | { kind: "working"; scope: GitDiffScope; path: string | null; label: string } @@ -544,8 +545,7 @@ export function SourceControlModal({ )} = { "rail.notARepo": "不是 Git 仓库。", "rail.clean": "工作区干净", + "git.ahead": "领先 {count} 个提交", + "git.behind": "落后 {count} 个提交", "rail.market": "{count} 个组件——打开 Plugin Hub", "rail.newSkill": "新建技能——在文档里输入 / 插入", diff --git a/apps/desktop/src/projects/ProjectIcon.tsx b/apps/desktop/src/projects/ProjectIcon.tsx index 0c6c2bf0..beb9c20e 100644 --- a/apps/desktop/src/projects/ProjectIcon.tsx +++ b/apps/desktop/src/projects/ProjectIcon.tsx @@ -61,7 +61,7 @@ export function ProjectIcon({ {url ? ( ) : ( - + )} ); diff --git a/apps/desktop/src/remote/Remote.tsx b/apps/desktop/src/remote/Remote.tsx index 122bf019..41c3d70e 100644 --- a/apps/desktop/src/remote/Remote.tsx +++ b/apps/desktop/src/remote/Remote.tsx @@ -15,6 +15,7 @@ import { } from "../bridge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; +import { Check } from "@/components/ui/icons"; import { StatusIndicator } from "@/components/business/status-indicator"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; @@ -305,7 +306,7 @@ export function RemoteModal({ onClose }: { onClose: () => void }) {
{link.url}
{selectedText &&
“{selectedText}”
} diff --git a/apps/desktop/tests/uiStack.test.ts b/apps/desktop/tests/uiStack.test.ts index 9342d766..ee7ed485 100644 --- a/apps/desktop/tests/uiStack.test.ts +++ b/apps/desktop/tests/uiStack.test.ts @@ -17,6 +17,7 @@ describe("desktop UI stack", () => { const packageJson = JSON.parse(read("package.json")); expect(config.style).toStartWith("base-"); + expect(config.iconLibrary).toBe("phosphor"); expect(packageJson.dependencies["@base-ui/react"]).toBeTruthy(); expect(packageJson.dependencies["radix-ui"]).toBeUndefined(); }); @@ -35,6 +36,40 @@ describe("desktop UI stack", () => { expect(productSource).not.toContain("asChild"); }); + test("keeps generic interface icons behind the Phosphor adapter", () => { + const files = sourceFiles("src"); + const sources = new Map(files.map((path) => [path, read(path)])); + const productSource = [...sources.values()].join("\n"); + const bridgeSource = sources.get("src/bridge.ts") ?? ""; + + expect( + files.filter((path) => sources.get(path)?.includes("@phosphor-icons/react")), + ).toEqual(["src/components/ui/icons.tsx"]); + expect(productSource).not.toContain("@hugeicons"); + expect(bridgeSource).not.toMatch(/\p{Extended_Pictographic}/u); + expect( + files.filter((path) => sources.get(path)?.includes("\s*×\s* sources.get(path)?.includes("✦")).sort()).toEqual([ + "src/editor/slotCard.tsx", + "src/skillInline.tsx", + ]); + + for (const path of [ + "src/environment/EnvironmentPopover.tsx", + "src/git/GitDockContent.tsx", + "src/git/SourceControl.tsx", + ]) { + expect(sources.get(path)).not.toMatch(/[↑↓]\$?\{/); + } + }); + test("uses the documented semantic radius scale for shared surfaces", () => { const cardSource = read("src/components/ui/card.tsx"); const badgeSource = read("src/components/ui/badge.tsx"); diff --git a/apps/desktop/tests/visualization.test.ts b/apps/desktop/tests/visualization.test.ts index cf65b100..b84f785e 100644 --- a/apps/desktop/tests/visualization.test.ts +++ b/apps/desktop/tests/visualization.test.ts @@ -47,7 +47,7 @@ describe("visualize transcript references", () => { expect(document).toContain("window.openai={sendFollowUpMessage"); expect(document).toContain("background:var(--background)"); expect(document).toContain("document.body.scrollHeight"); - expect(document).toContain(".viz-icon{"); + expect(document).not.toContain(".viz-icon{"); expect(document).toContain("--visualization-radius-control:12px"); expect(document).toContain("--visualization-radius-module:16px"); expect(document).toContain("border-radius:var(--visualization-radius-control)"); diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/acceptance.scene.json b/crates/core/schemas/agent-scenes/1.0.0/examples/acceptance.scene.json index 0ca4a95d..e8f4aea4 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/acceptance.scene.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/acceptance.scene.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "Acceptance", "description": "Read-only verification of the delivered work against the original acceptance criteria.", - "icon": "✅", "localizations": { "zh-CN": { "title": "验收", "description": "只读对照最初的验收标准逐条核验交付物。" } }, diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/develop.scene.json b/crates/core/schemas/agent-scenes/1.0.0/examples/develop.scene.json index 807941ea..bb1647b0 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/develop.scene.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/develop.scene.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "Develop", "description": "Plan-first implementation in an isolated worktree, producing a plan and a change summary.", - "icon": "🛠️", "localizations": { "zh-CN": { "title": "开发", "description": "在隔离 worktree 中计划先行地实现,产出计划与变更摘要。" } }, diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/fix.scene.json b/crates/core/schemas/agent-scenes/1.0.0/examples/fix.scene.json index 10d8e04e..10c24e0a 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/fix.scene.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/fix.scene.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "Fix", "description": "Resolve reported failures one by one, keeping the fix summary current.", - "icon": "🩹", "localizations": { "zh-CN": { "title": "修复", "description": "逐项修复报告的失败用例,维护修复摘要。" } }, diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/research.scene.json b/crates/core/schemas/agent-scenes/1.0.0/examples/research.scene.json index 09bb5e77..a6c7b917 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/research.scene.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/research.scene.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "Research", "description": "Survey the problem space read-only and produce a cited research report.", - "icon": "🔎", "localizations": { "zh-CN": { "title": "调研", "description": "只读勘察问题空间,产出带引用的调研报告。" } }, diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/rnd-lifecycle.pipeline.json b/crates/core/schemas/agent-scenes/1.0.0/examples/rnd-lifecycle.pipeline.json index d883deed..c7bc92d0 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/rnd-lifecycle.pipeline.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/rnd-lifecycle.pipeline.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "R&D lifecycle", "description": "The default research → develop → test → fix → acceptance pipeline with a test/fix loop.", - "icon": "🔁", "localizations": { "zh-CN": { "title": "研发生命周期", "description": "默认的调研 → 开发 → 测试 → 修复 → 验收流水线,含测试/修复循环。" } }, diff --git a/crates/core/schemas/agent-scenes/1.0.0/examples/test.scene.json b/crates/core/schemas/agent-scenes/1.0.0/examples/test.scene.json index 445ba06c..21c07fd4 100644 --- a/crates/core/schemas/agent-scenes/1.0.0/examples/test.scene.json +++ b/crates/core/schemas/agent-scenes/1.0.0/examples/test.scene.json @@ -4,7 +4,6 @@ "version": "1.0.0", "title": "Test", "description": "Exercise the change against its acceptance criteria and produce a pass/fail test report.", - "icon": "🧪", "localizations": { "zh-CN": { "title": "测试", "description": "对照验收标准执行测试,产出通过/失败矩阵报告。" } }, diff --git a/crates/core/src/market.rs b/crates/core/src/market.rs index 79f2ee43..28991829 100644 --- a/crates/core/src/market.rs +++ b/crates/core/src/market.rs @@ -46,14 +46,21 @@ pub fn parse_catalog(json: &str) -> serde_json::Result> { serde_json::from_str(json) } -fn fragment(id: &str, name: &str, icon: &str, author: &str, tags: &[&str], desc: &str, text: &str) -> MarketEntry { +fn fragment( + id: &str, + name: &str, + author: &str, + tags: &[&str], + desc: &str, + text: &str, +) -> MarketEntry { MarketEntry { id: id.into(), name: name.into(), description: desc.into(), author: author.into(), tags: tags.iter().map(|t| t.to_string()).collect(), - icon: Some(icon.into()), + icon: None, payload: SkillPayload::Fragment { text: text.into() }, } } @@ -64,7 +71,6 @@ pub fn builtin_catalog() -> Vec { fragment( "architect", "System Architect", - "🏛️", "codetwo", &["design", "planning"], "Design before coding: propose a component breakdown, data flow, and trade-offs.", @@ -74,7 +80,6 @@ pub fn builtin_catalog() -> Vec { fragment( "test-suite", "Test Suite Author", - "🧪", "codetwo", &["testing", "quality"], "Generate a thorough, deterministic test suite.", @@ -84,7 +89,6 @@ pub fn builtin_catalog() -> Vec { fragment( "doc-writer", "Docs Writer", - "📚", "codetwo", &["docs"], "Write clear docstrings and a concise README section.", @@ -94,7 +98,6 @@ pub fn builtin_catalog() -> Vec { fragment( "refactor-guru", "Refactor Guru", - "🧹", "codetwo", &["refactor", "quality"], "Improve clarity and structure with behavior-preserving refactors.", @@ -104,7 +107,6 @@ pub fn builtin_catalog() -> Vec { fragment( "sql-optimizer", "SQL Optimizer", - "🗄️", "community", &["database", "performance"], "Analyze and speed up SQL queries.", @@ -114,7 +116,6 @@ pub fn builtin_catalog() -> Vec { fragment( "rustacean", "Rust Expert", - "🦀", "community", &["rust", "language"], "Idiomatic, safe Rust with clear ownership.", @@ -124,7 +125,6 @@ pub fn builtin_catalog() -> Vec { fragment( "a11y-audit", "Accessibility Audit", - "♿", "community", &["frontend", "a11y"], "Audit UI for accessibility issues.", @@ -137,7 +137,7 @@ pub fn builtin_catalog() -> Vec { description: "Generate a Conventional Commits message.".into(), author: "codetwo".into(), tags: vec!["git".into(), "workflow".into()], - icon: Some("📝".into()), + icon: None, payload: SkillPayload::Macro { template: "Write a Conventional Commits message ({{type}}) for changes to {{scope}}, \ with a concise body explaining the why." @@ -151,7 +151,7 @@ pub fn builtin_catalog() -> Vec { description: "Draft a pull-request description.".into(), author: "codetwo".into(), tags: vec!["git".into(), "workflow".into()], - icon: Some("🔀".into()), + icon: None, payload: SkillPayload::Macro { template: "Draft a pull-request description for {{branch}}: summary, motivation, key \ changes, and a test plan." @@ -165,7 +165,7 @@ pub fn builtin_catalog() -> Vec { description: "Give the agent a browser via an MCP server.".into(), author: "codetwo".into(), tags: vec!["mcp", "browser", "tools"].iter().map(|s| s.to_string()).collect(), - icon: Some("🌐".into()), + icon: None, payload: SkillPayload::Mcp { server: McpServer { name: "browser".into(), @@ -184,7 +184,7 @@ pub fn builtin_catalog() -> Vec { description: "Let the agent search the web for docs and answers.".into(), author: "codetwo".into(), tags: vec!["mcp".into(), "search".into(), "tools".into()], - icon: Some("🔎".into()), + icon: None, payload: SkillPayload::Mcp { server: McpServer { name: "web-search".into(), @@ -203,7 +203,7 @@ pub fn builtin_catalog() -> Vec { description: "Scoped filesystem access via an MCP server.".into(), author: "community".into(), tags: vec!["mcp".into(), "tools".into()], - icon: Some("📂".into()), + icon: None, payload: SkillPayload::Mcp { server: McpServer { name: "filesystem".into(), @@ -252,4 +252,9 @@ mod tests { fn includes_a_browser_tool_entry() { assert!(builtin_catalog().iter().any(|e| e.id == "browser-tool" && e.kind() == SkillKind::Mcp)); } + + #[test] + fn builtin_catalog_leaves_icons_to_the_host_design_system() { + assert!(builtin_catalog().iter().all(|entry| entry.icon.is_none())); + } } diff --git a/crates/core/src/skill.rs b/crates/core/src/skill.rs index fcb9179d..d3c0712a 100644 --- a/crates/core/src/skill.rs +++ b/crates/core/src/skill.rs @@ -1163,7 +1163,7 @@ pub fn builtin_skills() -> Vec { id: "plan-first".into(), name: "Plan first".into(), description: "Propose a plan and wait for approval before editing".into(), - icon: Some("🗺️".into()), + icon: None, source: None, payload: SkillPayload::Fragment { text: "Before changing anything, produce a short numbered plan of the steps you \ @@ -1176,7 +1176,7 @@ pub fn builtin_skills() -> Vec { id: "reviewer".into(), name: "Code Reviewer".into(), description: "Meticulous senior reviewer persona".into(), - icon: Some("🔍".into()), + icon: None, source: None, payload: SkillPayload::Fragment { text: "Act as a meticulous senior code reviewer. Flag bugs, unsafe patterns, and \ @@ -1188,7 +1188,7 @@ pub fn builtin_skills() -> Vec { id: "test-writer".into(), name: "Test Writer".into(), description: "Write thorough tests".into(), - icon: Some("🧪".into()), + icon: None, source: None, payload: SkillPayload::Fragment { text: "Write thorough, isolated unit tests covering happy paths and edge cases. \ @@ -1200,7 +1200,7 @@ pub fn builtin_skills() -> Vec { id: "security-audit".into(), name: "Security Audit".into(), description: "Audit for vulnerabilities".into(), - icon: Some("🛡️".into()), + icon: None, source: None, payload: SkillPayload::Fragment { text: "Audit the code for security vulnerabilities: injection, auth gaps, unsafe \ @@ -1212,7 +1212,7 @@ pub fn builtin_skills() -> Vec { id: "commit-macro".into(), name: "Commit Message".into(), description: "Parameterized commit message".into(), - icon: Some("📝".into()), + icon: None, source: None, payload: SkillPayload::Macro { template: "Write a {{style}} commit message for changes to {{scope}}.".into(), @@ -1810,6 +1810,11 @@ mod tests { assert_eq!(scope.kind, SlotKind::Text); } + #[test] + fn builtin_skills_leave_icons_to_the_host_design_system() { + assert!(builtin_skills().iter().all(|skill| skill.icon.is_none())); + } + fn write_appshot_fixture(data_dir: &std::path::Path, id: &str) -> Vec { let captures = data_dir.join("appshots"); std::fs::create_dir_all(&captures).unwrap(); diff --git a/crates/core/tests/scene_conformance.rs b/crates/core/tests/scene_conformance.rs index f8eee296..358fb9d7 100644 --- a/crates/core/tests/scene_conformance.rs +++ b/crates/core/tests/scene_conformance.rs @@ -42,6 +42,11 @@ fn all_six_fixtures_load() { ); assert_eq!(lib.pipelines().len(), 1); assert_eq!(lib.pipelines()[0].pipeline.name, "rnd-lifecycle"); + assert!(lib.scenes().iter().all(|entry| entry.scene.icon.is_none())); + assert!(lib + .pipelines() + .iter() + .all(|entry| entry.pipeline.icon.is_none())); } #[test] diff --git a/docs/design/system.md b/docs/design/system.md index 779cd3e3..a162422b 100644 --- a/docs/design/system.md +++ b/docs/design/system.md @@ -34,7 +34,10 @@ desktop tool, not a mobile layout and not a showcase for decorative effects. - Persistent panels, cards, inputs, and dialogs are borderless and separated by solid surface tones plus controlled elevation. Menu-like transient layers add one restrained shared material. - Hover changes surface or text tone only. It never lifts, scales, or blooms a shadow. -- Icons use Hugeicons and `currentColor`. Provider marks may keep their shape, never their brand color. +- Icons use Phosphor's rounded regular weight and `currentColor`, imported through the shared icon + adapter. Built-in skills, market entries, and Scenes omit emoji icon metadata so the renderer's + semantic Phosphor defaults remain authoritative. Provider marks and explicitly authored external + content may keep their shape, never their brand color. - Official schemes are light, dark, and system. Built-in and custom palettes pass through the same theme resolver; product modules never branch on a palette name. diff --git a/docs/sdlc/changes/2026-09-01-rounded-icon-library/change.md b/docs/sdlc/changes/2026-09-01-rounded-icon-library/change.md new file mode 100644 index 00000000..043e8dd1 --- /dev/null +++ b/docs/sdlc/changes/2026-09-01-rounded-icon-library/change.md @@ -0,0 +1,150 @@ +--- +id: change-2026-09-01-rounded-icon-library +kind: change +schema: 2 +status: verified +risk: medium +owner: codex +approvers: [user] +approved_at: 2026-09-01 +created: 2026-09-01 +updated: 2026-09-01 +source: direct user requests on 2026-09-01 to replace the strange-looking icon set with a rounder library, switch every generic desktop interface icon, continue the migration, and remove fallback icons +inputs: apps/desktop/src/components/ui/icons.tsx, apps/desktop/package.json, docs/design/system.md, Apple HIG Icons and SF Symbols guidance +outputs: one rounder application icon family behind the existing icon adapter, removal of generic interface glyph bypasses, dependency cleanup, adapter tests, and rendered review evidence +scope: apps/desktop/components.json, apps/desktop/src/App.tsx, apps/desktop/src/bridge.ts, apps/desktop/src/components/ui/icons.tsx, apps/desktop/src/components/ui/icons.test.tsx, apps/desktop/src/editor/Editor.tsx, apps/desktop/src/editor/FileMenu.tsx, apps/desktop/src/editor/issueBlock.tsx, apps/desktop/src/editor/slotCard.tsx, apps/desktop/src/environment/EnvironmentPopover.tsx, apps/desktop/src/git/GitDockContent.tsx, apps/desktop/src/git/GitSyncStatus.tsx, apps/desktop/src/git/GitSyncStatus.test.tsx, apps/desktop/src/git/SourceControl.tsx, apps/desktop/src/i18n/strings.ts, apps/desktop/src/projects/ProjectIcon.tsx, apps/desktop/src/remote/Remote.tsx, apps/desktop/src/session/visualization.ts, apps/desktop/src/skillInline.tsx, apps/desktop/tests/uiStack.test.ts, apps/desktop/tests/visualization.test.ts, apps/desktop/package.json, apps/desktop/bun.lock, crates/core/src/market.rs, crates/core/src/skill.rs, crates/core/schemas/agent-scenes/1.0.0/examples/research.scene.json, crates/core/schemas/agent-scenes/1.0.0/examples/develop.scene.json, crates/core/schemas/agent-scenes/1.0.0/examples/test.scene.json, crates/core/schemas/agent-scenes/1.0.0/examples/fix.scene.json, crates/core/schemas/agent-scenes/1.0.0/examples/acceptance.scene.json, crates/core/schemas/agent-scenes/1.0.0/examples/rnd-lifecycle.pipeline.json, crates/core/tests/scene_conformance.rs, docs/design/system.md, docs/sdlc/changes/2026-09-01-rounded-icon-library/change.md +next_trigger: human visual review, merge, or release request +verification_mode: owner +verified_by: codex +verified_at: 2026-09-01 +--- + +# Adopt a rounder desktop icon family + +## Intent + +The desktop currently renders product glyphs through Hugeicons. The user reports that this visual +language feels strange and asked for a rounder icon library. The affected surface is the desktop +renderer: navigation, toolbars, menus, status, settings, and other shared controls all consume the +central icon adapter. + +The desired outcome is a softer, more coherent icon family with clear macOS-sized silhouettes, +without changing feature behavior, provider marks, data, protocols, window geometry, or the app +icon. Existing call sites should retain their stable semantic imports so the migration stays +narrow and reversible. + +## Spec + +Use Phosphor Icons behind `components/ui/icons.tsx`, with its regular weight as the application +default and `currentColor` inheritance preserved. Import concrete icon modules rather than the +package barrel so development and production transforms do not eagerly process the whole family. +Map each existing public adapter export to the closest Phosphor concept, preserve ref forwarding, +class names, accessibility properties, and inline sizing. Remove the ineffective legacy +`strokeWidth` compatibility instead of advertising a root SVG attribute that cannot change +Phosphor's filled regular-weight paths. + +Remove both Hugeicons dependencies after all adapter mappings compile. Provider and product brand +marks remain outside the generic glyph family. Built-in skill, market, and Scene metadata must not +ship fallback emoji; the renderer supplies its Phosphor defaults when no external authored icon is +present. Record Phosphor in both the design-system rules and shadcn generator configuration. +Rollback is the dependency, metadata, and adapter portion of this change. + +### Acceptance criteria + +- [x] AC-1: Every existing icon-adapter export renders a Phosphor SVG with regular weight, + `currentColor`, forwarded refs, caller classes, and accessible SVG props; action-oriented exports + do not collapse to their unmodified base concept. Verify with the focused adapter test and TypeScript. +- [x] AC-2: Hugeicons is absent from desktop source, manifest, lockfile, and generator configuration + while the renderer lint, test suite, and production build pass. +- [x] AC-3: The shared design-system preview shows recognizable, visually consistent icons at 12, + 14, and 16 px in light, dark, and narrow rendered states; verify with captured window evidence. +- [x] AC-4: Repository documentation, lifecycle, and worktree checks pass with the icon-family + contract and this Artifact in scope. +- [x] AC-5: Generic desktop interface icons no longer bypass the shared Phosphor adapter through + hard-coded SVG paths or standalone symbol characters. Provider marks, charts, QR content, + keyboard labels, authored external content, and numeric operators remain intentionally exempt. + Built-in skill, market, and Scene sources carry no fallback emoji, and Git synchronization + indicators retain explicit ahead/behind semantics for assistive technology. + +## Decision and gates + +The user approved Intent and implementation through the direct request on 2026-09-01. Phosphor was +selected over Tabler and Lucide because its rounded silhouettes and family-wide weights better fit +the requested direction while retaining React tree-shaking and MIT licensing. The implementation +preserves familiar icon concepts in line with the macOS HIG Icons and SF Symbols guidance. + +No security, data, provider, merge, release, deployment, or production Gate is granted. Final +visual taste remains a human review Gate after the rendered evidence is available. + +## Plan + +1. Replace the Hugeicons-backed adapter with individually imported Phosphor components while + retaining the adapter's existing export names and React contract. +2. Add a focused adapter test, update the dependency lock and design-system rule, and prove all + existing call sites still type-check. +3. Run desktop lint, tests, renderer build, repository lifecycle checks, and light/dark/narrow + rendered review; correct only icon-related regressions. + +## Build + +The desktop now exposes 165 semantic icon exports through the shared Phosphor adapter, using +concrete module imports and one regular-weight, `currentColor` wrapper. All generic interface +bypasses found in desktop source were migrated through that adapter: close controls, copied state, +Git ahead/behind state, saved-skill fallbacks, slot placeholders, and artifact prefixes. The +redundant filter glyph embedded in the static rich-transcript fixture was deleted instead of +introducing a second non-React icon pipeline. + +The continued pass removed the ineffective `strokeWidth` compatibility, gave action-oriented +exports distinct Phosphor concepts, changed shadcn generation to `phosphor`, and centralized the +three Git synchronization renderers behind one localized, screen-reader-labelled component. +Built-in browser fallbacks, live Core skills, market entries, and the six canonical Scene fixtures +now omit emoji icon metadata so the renderer's shared Phosphor defaults are authoritative. + +Provider marks, charts, QR content, keyboard labels, numeric operators, and authored extension +icons remain intentionally outside the generic application glyph family. + +## Verification + +Verdict: verified. + +### Acceptance evidence + +- AC-1: PASS — `bun test src/components/ui/icons.test.tsx src/git/GitSyncStatus.test.tsx + tests/uiStack.test.ts tests/visualization.test.ts` completed with 17 passing tests and 1,059 + expectations; the adapter tests cover all 165 exports, distinct action concepts, forwarded refs, + regular weight, and `currentColor`; `bunx tsc --noEmit` passed. +- AC-2: PASS — source and lockfile searches find no Hugeicons dependency or import; `bun run lint` + passed; after rebasing onto the current `origin/main`, full `bun test` completed with 816 passing + tests, 4,867 expectations, and zero failures across 141 files; `bun run build:renderer` passed. + The shadcn project report resolves `iconLibrary` to `phosphor`. +- AC-3: PASS — `bunx vite --host 127.0.0.1 --port 1423` served the local renderer for real-browser + inspection at 1280px in light and dark themes and at 900px in the Components view. All 17 visible + SVGs used the Phosphor `0 0 256 256` view box, the 12, 14, and 16px samples retained their bounds, + both widths reported `scrollWidth == innerWidth`, and the page had no Vite error overlay or + browser console warning/error. +- AC-4: PASS — `bun script/verify/docs.ts`, `bun script/verify/sdlc.ts`, + `bun script/verify/sdlc.ts --worktree`, and `git diff --check` passed. +- AC-5: PASS — `tests/uiStack.test.ts` enforces that only the shared adapter imports Phosphor, no + Hugeicons imports or built-in emoji fallback metadata exist, and raw SVG ownership is limited to + `ProviderIcon`, `ChartBlock`, and `Usage`. `GitSyncStatus.test.tsx` verifies localized ahead/behind + semantics while its SVGs remain hidden from assistive technology. `cargo test -p codetwo-core` + passed the complete Core suite, including the new built-in icon and Scene conformance assertions. + +Residual risk: semantic icon selection still has a subjective visual-taste component and awaits +the human review Gate. Packaged Windows rendering was not exercised. Authored extension icons are +preserved by design and can therefore differ visually from the application family. + +## Review and release + +Approval: pending human visual review. +Release target: none. +Release identity: not applicable until released. +Smoke evidence: not applicable until released. +Rollback: restore the previous Hugeicons dependencies and `apps/desktop/src/components/ui/icons.tsx`, then rerun the desktop gates. +No release: this scoped change prepares a local implementation and review surface only; merge and release were not requested. + +Preparing this section does not authorize merge, deployment, release, or production mutation. + +## Feedback + +The user's visual review of the rendered icon family is the next feedback boundary.