diff --git a/.changeset/migrate-web-icons-to-unplugin-icons.md b/.changeset/migrate-web-icons-to-unplugin-icons.md new file mode 100644 index 000000000..e15b783bd --- /dev/null +++ b/.changeset/migrate-web-icons-to-unplugin-icons.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +web: Compile icons at build time so the bundled web UI only carries the icons it renders. diff --git a/apps/kimi-web/package.json b/apps/kimi-web/package.json index 1b2eb5f2b..febbb8626 100644 --- a/apps/kimi-web/package.json +++ b/apps/kimi-web/package.json @@ -10,15 +10,11 @@ "build": "vite build", "typecheck": "vue-tsc --noEmit", "test": "vitest run", - "check:style": "node scripts/check-style.mjs", - "gen:icons": "node scripts/gen-icon-data.mjs" + "check:style": "node scripts/check-style.mjs" }, "dependencies": { "@fontsource-variable/inter": "^5.2.8", "@fontsource-variable/jetbrains-mono": "^5.2.8", - "@iconify-json/ri": "^1.2.10", - "@iconify/utils": "^3.1.3", - "@iconify/vue": "^5.0.1", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "katex": "^0.17.0", @@ -30,8 +26,10 @@ "vue-i18n": "^11.4.5" }, "devDependencies": { + "@iconify-json/ri": "^1.2.10", "@vitejs/plugin-vue": "^5.2.4", "typescript": "6.0.2", + "unplugin-icons": "^23.0.0", "vite": "^6.3.3", "vitest": "4.1.4", "vue-tsc": "~3.2.0", diff --git a/apps/kimi-web/scripts/check-style.mjs b/apps/kimi-web/scripts/check-style.mjs index e626cfee0..81480efe9 100644 --- a/apps/kimi-web/scripts/check-style.mjs +++ b/apps/kimi-web/scripts/check-style.mjs @@ -27,13 +27,13 @@ const DOMAIN_HEX_EXEMPT = new Set([ 'Terminal.vue', ]); -// Files that legitimately render their own : the icon primitive itself, -// bespoke data-viz / colored illustrations, the spinner, and brand marks -// (the Kimi wordmark on the loading screen). Everything else should use -// lib/icons.ts via /iconSvg(). The 32x22 Kimi eye logo is also exempted -// inline (matched by viewBox). +// Files that legitimately render their own : bespoke data-viz / colored +// illustrations, the spinner, and brand marks (the Kimi wordmark on the loading +// screen). Everything else should use lib/icons.ts via /iconSvg(). The +// 32x22 Kimi eye logo is also exempted inline (matched by viewBox). The icon +// primitive (components/ui/Icon.vue) itself renders no hand-written , so it +// is not exempted here. const ICON_EXEMPT = new Set([ - 'components/ui/Icon.vue', 'components/ui/Spinner.vue', 'components/ui/MoonSpinner.vue', 'components/ui/ContextRing.vue', diff --git a/apps/kimi-web/scripts/gen-icon-catalog.mjs b/apps/kimi-web/scripts/gen-icon-catalog.mjs deleted file mode 100644 index 79063bf06..000000000 --- a/apps/kimi-web/scripts/gen-icon-catalog.mjs +++ /dev/null @@ -1,40 +0,0 @@ -// scripts/gen-icon-catalog.mjs — generate the design-system §02 icon catalog -// HTML from the canonical registry (lib/icons.ts) so the two can never drift. -// Run: node --experimental-strip-types scripts/gen-icon-catalog.mjs -import { ICON_DATA } from '../src/lib/icon-data.ts'; - -// Display order + grouping. Names not listed here are appended under "Other". -const GROUPS = [ - ['Actions', ['plus', 'chat-new', 'close', 'check', 'search', 'copy', 'link', 'external-link', 'download', 'undo', 'send', 'image', 'settings', 'sliders', 'log-in']], - ['Navigation & layout', ['chevron-down', 'chevron-right', 'arrow-up', 'arrow-down', 'arrow-right', 'minus', 'panel-collapse', 'panel-expand', 'expand', 'collapse', 'list']], - ['Files & tools', ['folder', 'folder-closed', 'folder-plus', 'folder-solid', 'file', 'file-text', 'file-plus', 'file-off', 'image-off', 'code', 'terminal', 'pencil', 'glob', 'globe', 'check-list', 'bolt', 'git-pull-request']], - ['Communication', ['message', 'mail', 'user']], - ['Status & media', ['info', 'help-circle', 'alert-triangle', 'clock', 'sparkles', 'play', 'stop', 'star', 'star-outline', 'dots-horizontal']], -]; - -function render(name) { - const d = ICON_DATA[name]; - const vb = `0 0 ${d.width ?? 24} ${d.height ?? 24}`; - return `${d.body}`; -} - -const seen = new Set(); -const lines = []; -lines.push('
'); -for (const [label, names] of GROUPS) { - lines.push(`
${label.replaceAll('&', '&')}
`); - for (const name of names) { - seen.add(name); - lines.push(`
${render(name)}${name}
`); - } -} -const rest = Object.keys(ICON_DATA).filter((n) => !seen.has(n)); -if (rest.length) { - lines.push('
Other
'); - for (const name of rest) { - lines.push(`
${render(name)}${name}
`); - } -} -lines.push('
'); - -process.stdout.write(lines.join('\n') + '\n'); diff --git a/apps/kimi-web/scripts/gen-icon-data.mjs b/apps/kimi-web/scripts/gen-icon-data.mjs deleted file mode 100644 index ce48d09e2..000000000 --- a/apps/kimi-web/scripts/gen-icon-data.mjs +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env node -// scripts/gen-icon-data.mjs — generate src/lib/icon-data.ts, the tree-shaken -// Remix Icon (ri) subset that backs apps/kimi-web's icon registry. -// -// Single source of truth for "which existing icon name maps to which Remix -// icon" is the NAME_TO_REMIX map below. The SVG bytes are pulled straight from -// @iconify-json/ri at generation time, so the registry stays library-sourced -// (no hand-copied SVG) yet fully offline and tree-shaken (only the icons we -// list here end up in the bundle). -// -// Run after changing the map: pnpm gen:icons - -import { writeFileSync } from 'node:fs'; -import { resolve, dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import ri from '@iconify-json/ri/icons.json' with { type: 'json' }; -import { getIconData } from '@iconify/utils'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const OUT = resolve(__dirname, '../src/lib/icon-data.ts'); - -// Existing icon name → Remix icon name (ri:, prefix omitted here). -// Keep keys sorted within their group; the generated IconName union follows -// this order. Every value must exist in @iconify-json/ri (validated below). -const GROUPS = [ - ['Actions', { - plus: 'add-line', - 'chat-new': 'chat-new-line', - close: 'close-line', - check: 'check-line', - search: 'search-line', - copy: 'file-copy-line', - link: 'links-line', - 'external-link': 'external-link-line', - download: 'download-line', - undo: 'arrow-go-back-line', - send: 'arrow-up-line', - image: 'image-line', - settings: 'settings-3-line', - sliders: 'equalizer-line', - 'log-in': 'login-box-line', - }], - ['Navigation & layout', { - 'chevron-down': 'arrow-down-s-line', - 'chevron-right': 'arrow-right-s-line', - 'arrow-up': 'arrow-up-line', - 'arrow-down': 'arrow-down-line', - 'arrow-right': 'arrow-right-line', - minus: 'subtract-line', - 'panel-collapse': 'contract-left-line', - 'panel-expand': 'expand-right-line', - expand: 'expand-diagonal-line', - collapse: 'collapse-diagonal-line', - list: 'list-unordered', - sort: 'sort-desc', - grip: 'draggable', - }], - ['Files & tools', { - folder: 'folder-open-line', - 'folder-closed': 'folder-line', - 'folder-plus': 'folder-add-line', - 'folder-solid': 'folder-fill', - file: 'file-line', - 'file-text': 'file-text-line', - 'file-plus': 'file-add-line', - 'file-off': 'file-line', - 'image-off': 'image-line', - code: 'code-line', - terminal: 'terminal-box-line', - pencil: 'pencil-line', - tool: 'tools-line', - glob: 'braces-line', - globe: 'global-line', - 'check-list': 'list-check', - bolt: 'flashlight-line', - 'git-pull-request': 'git-pull-request-line', - }], - ['Communication', { - message: 'message-line', - mail: 'mail-line', - user: 'user-line', - }], - ['Status & media', { - info: 'information-line', - 'help-circle': 'question-line', - 'alert-triangle': 'alert-line', - clock: 'time-line', - sparkles: 'sparkling-line', - play: 'play-fill', - stop: 'stop-fill', - star: 'star-fill', - 'star-outline': 'star-line', - 'dots-horizontal': 'more-line', - }], -]; - -const NAME_TO_REMIX = Object.assign({}, ...GROUPS.map(([, m]) => m)); - -// --- resolve + validate ---------------------------------------------------- -const missing = []; -const data = {}; -for (const [name, riName] of Object.entries(NAME_TO_REMIX)) { - const icon = getIconData(ri, riName); - if (!icon) { - missing.push(`${name} → ri:${riName}`); - continue; - } - data[name] = { body: icon.body, width: icon.width ?? 24, height: icon.height ?? 24 }; -} - -if (missing.length) { - console.error('gen-icon-data: the following Remix icons were not found in @iconify-json/ri:'); - for (const m of missing) console.error(` ${m}`); - process.exit(1); -} - -// --- emit ------------------------------------------------------------------ -const names = Object.keys(data); -const keyOf = (n) => (/^[a-zA-Z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)); - -const lines = []; -lines.push('// GENERATED FILE — do not edit by hand.'); -lines.push('// Source of truth: scripts/gen-icon-data.mjs (run `pnpm gen:icons`).'); -lines.push('// Icons are Remix Icon (ri) — https://remixicon.com/ — Apache-2.0.'); -lines.push(''); -lines.push('export type IconName ='); -for (let i = 0; i < names.length; i++) { - const suffix = i === names.length - 1 ? ';' : ''; - lines.push(` | ${JSON.stringify(names[i])}${suffix}`); -} -lines.push(''); -lines.push('export interface IconData {'); -lines.push(' /** Inner SVG markup (paths/shapes), rendered inside our wrapper. */'); -lines.push(' body: string;'); -lines.push(' /** Source grid width in px. Remix icons are 24. */'); -lines.push(' width?: number;'); -lines.push(' /** Source grid height in px. Remix icons are 24. */'); -lines.push(' height?: number;'); -lines.push('}'); -lines.push(''); -lines.push('/** Existing name → fully-qualified Remix icon id. */'); -lines.push('export const NAME_TO_REMIX: Record = {'); -for (const name of names) lines.push(` ${keyOf(name)}: ${JSON.stringify('ri:' + NAME_TO_REMIX[name])},`); -lines.push('};'); -lines.push(''); -lines.push('/** Per-icon SVG data, pulled from @iconify-json/ri. */'); -lines.push('export const ICON_DATA: Record = {'); -for (const name of names) { - const { body, width, height } = data[name]; - lines.push(` ${keyOf(name)}: { body: ${JSON.stringify(body)}, width: ${width}, height: ${height} },`); -} -lines.push('};'); -lines.push(''); - -writeFileSync(OUT, lines.join('\n')); -console.log(`gen-icon-data: wrote ${names.length} icons to src/lib/icon-data.ts`); diff --git a/apps/kimi-web/src/components/ui/Icon.vue b/apps/kimi-web/src/components/ui/Icon.vue index 769220f96..aaaa00218 100644 --- a/apps/kimi-web/src/components/ui/Icon.vue +++ b/apps/kimi-web/src/components/ui/Icon.vue @@ -1,6 +1,6 @@ + lib/icons.ts at a token size. Use everywhere instead of hand-writing raw SVG. --> diff --git a/apps/kimi-web/src/env.d.ts b/apps/kimi-web/src/env.d.ts index 21bfa6af6..c2089e474 100644 --- a/apps/kimi-web/src/env.d.ts +++ b/apps/kimi-web/src/env.d.ts @@ -27,3 +27,14 @@ declare module '*?worker&type=module' { const WorkerFactory: new () => Worker; export default WorkerFactory; } + +// unplugin-icons `?raw` imports — `unplugin-icons/types/vue` declares +// `~icons/*` as a Vue FunctionalComponent (for direct component imports). The +// `?raw` query re-exports the raw SVG source, which must type as `string`; +// this more-specific pattern overrides the component declaration for `?raw` +// imports only (e.g. `~icons/ri/add-line?raw`), leaving component imports +// (`~icons/ri/add-line`) typed as components. +declare module '~icons/*?raw' { + const src: string; + export default src; +} diff --git a/apps/kimi-web/src/lib/icon-data.ts b/apps/kimi-web/src/lib/icon-data.ts deleted file mode 100644 index 5d3e8c1da..000000000 --- a/apps/kimi-web/src/lib/icon-data.ts +++ /dev/null @@ -1,199 +0,0 @@ -// GENERATED FILE — do not edit by hand. -// Source of truth: scripts/gen-icon-data.mjs (run `pnpm gen:icons`). -// Icons are Remix Icon (ri) — https://remixicon.com/ — Apache-2.0. - -export type IconName = - | "plus" - | "chat-new" - | "close" - | "check" - | "search" - | "copy" - | "link" - | "external-link" - | "download" - | "undo" - | "send" - | "image" - | "settings" - | "sliders" - | "log-in" - | "chevron-down" - | "chevron-right" - | "arrow-up" - | "arrow-down" - | "arrow-right" - | "minus" - | "panel-collapse" - | "panel-expand" - | "expand" - | "collapse" - | "list" - | "sort" - | "grip" - | "folder" - | "folder-closed" - | "folder-plus" - | "folder-solid" - | "file" - | "file-text" - | "file-plus" - | "file-off" - | "image-off" - | "code" - | "terminal" - | "pencil" - | "tool" - | "glob" - | "globe" - | "check-list" - | "bolt" - | "git-pull-request" - | "message" - | "mail" - | "user" - | "info" - | "help-circle" - | "alert-triangle" - | "clock" - | "sparkles" - | "play" - | "stop" - | "star" - | "star-outline" - | "dots-horizontal"; - -export interface IconData { - /** Inner SVG markup (paths/shapes), rendered inside our wrapper. */ - body: string; - /** Source grid width in px. Remix icons are 24. */ - width?: number; - /** Source grid height in px. Remix icons are 24. */ - height?: number; -} - -/** Existing name → fully-qualified Remix icon id. */ -export const NAME_TO_REMIX: Record = { - plus: "ri:add-line", - "chat-new": "ri:chat-new-line", - close: "ri:close-line", - check: "ri:check-line", - search: "ri:search-line", - copy: "ri:file-copy-line", - link: "ri:links-line", - "external-link": "ri:external-link-line", - download: "ri:download-line", - undo: "ri:arrow-go-back-line", - send: "ri:arrow-up-line", - image: "ri:image-line", - settings: "ri:settings-3-line", - sliders: "ri:equalizer-line", - "log-in": "ri:login-box-line", - "chevron-down": "ri:arrow-down-s-line", - "chevron-right": "ri:arrow-right-s-line", - "arrow-up": "ri:arrow-up-line", - "arrow-down": "ri:arrow-down-line", - "arrow-right": "ri:arrow-right-line", - minus: "ri:subtract-line", - "panel-collapse": "ri:contract-left-line", - "panel-expand": "ri:expand-right-line", - expand: "ri:expand-diagonal-line", - collapse: "ri:collapse-diagonal-line", - list: "ri:list-unordered", - sort: "ri:sort-desc", - grip: "ri:draggable", - folder: "ri:folder-open-line", - "folder-closed": "ri:folder-line", - "folder-plus": "ri:folder-add-line", - "folder-solid": "ri:folder-fill", - file: "ri:file-line", - "file-text": "ri:file-text-line", - "file-plus": "ri:file-add-line", - "file-off": "ri:file-line", - "image-off": "ri:image-line", - code: "ri:code-line", - terminal: "ri:terminal-box-line", - pencil: "ri:pencil-line", - tool: "ri:tools-line", - glob: "ri:braces-line", - globe: "ri:global-line", - "check-list": "ri:list-check", - bolt: "ri:flashlight-line", - "git-pull-request": "ri:git-pull-request-line", - message: "ri:message-line", - mail: "ri:mail-line", - user: "ri:user-line", - info: "ri:information-line", - "help-circle": "ri:question-line", - "alert-triangle": "ri:alert-line", - clock: "ri:time-line", - sparkles: "ri:sparkling-line", - play: "ri:play-fill", - stop: "ri:stop-fill", - star: "ri:star-fill", - "star-outline": "ri:star-line", - "dots-horizontal": "ri:more-line", -}; - -/** Per-icon SVG data, pulled from @iconify-json/ri. */ -export const ICON_DATA: Record = { - plus: { body: "", width: 24, height: 24 }, - "chat-new": { body: "", width: 24, height: 24 }, - close: { body: "", width: 24, height: 24 }, - check: { body: "", width: 24, height: 24 }, - search: { body: "", width: 24, height: 24 }, - copy: { body: "", width: 24, height: 24 }, - link: { body: "", width: 24, height: 24 }, - "external-link": { body: "", width: 24, height: 24 }, - download: { body: "", width: 24, height: 24 }, - undo: { body: "", width: 24, height: 24 }, - send: { body: "", width: 24, height: 24 }, - image: { body: "", width: 24, height: 24 }, - settings: { body: "", width: 24, height: 24 }, - sliders: { body: "", width: 24, height: 24 }, - "log-in": { body: "", width: 24, height: 24 }, - "chevron-down": { body: "", width: 24, height: 24 }, - "chevron-right": { body: "", width: 24, height: 24 }, - "arrow-up": { body: "", width: 24, height: 24 }, - "arrow-down": { body: "", width: 24, height: 24 }, - "arrow-right": { body: "", width: 24, height: 24 }, - minus: { body: "", width: 24, height: 24 }, - "panel-collapse": { body: "", width: 24, height: 24 }, - "panel-expand": { body: "", width: 24, height: 24 }, - expand: { body: "", width: 24, height: 24 }, - collapse: { body: "", width: 24, height: 24 }, - list: { body: "", width: 24, height: 24 }, - sort: { body: "", width: 24, height: 24 }, - grip: { body: "", width: 24, height: 24 }, - folder: { body: "", width: 24, height: 24 }, - "folder-closed": { body: "", width: 24, height: 24 }, - "folder-plus": { body: "", width: 24, height: 24 }, - "folder-solid": { body: "", width: 24, height: 24 }, - file: { body: "", width: 24, height: 24 }, - "file-text": { body: "", width: 24, height: 24 }, - "file-plus": { body: "", width: 24, height: 24 }, - "file-off": { body: "", width: 24, height: 24 }, - "image-off": { body: "", width: 24, height: 24 }, - code: { body: "", width: 24, height: 24 }, - terminal: { body: "", width: 24, height: 24 }, - pencil: { body: "", width: 24, height: 24 }, - tool: { body: "", width: 24, height: 24 }, - glob: { body: "", width: 24, height: 24 }, - globe: { body: "", width: 24, height: 24 }, - "check-list": { body: "", width: 24, height: 24 }, - bolt: { body: "", width: 24, height: 24 }, - "git-pull-request": { body: "", width: 24, height: 24 }, - message: { body: "", width: 24, height: 24 }, - mail: { body: "", width: 24, height: 24 }, - user: { body: "", width: 24, height: 24 }, - info: { body: "", width: 24, height: 24 }, - "help-circle": { body: "", width: 24, height: 24 }, - "alert-triangle": { body: "", width: 24, height: 24 }, - clock: { body: "", width: 24, height: 24 }, - sparkles: { body: "", width: 24, height: 24 }, - play: { body: "", width: 24, height: 24 }, - stop: { body: "", width: 24, height: 24 }, - star: { body: "", width: 24, height: 24 }, - "star-outline": { body: "", width: 24, height: 24 }, - "dots-horizontal": { body: "", width: 24, height: 24 }, -}; diff --git a/apps/kimi-web/src/lib/icons.test.ts b/apps/kimi-web/src/lib/icons.test.ts index ba65a2f32..953a30e41 100644 --- a/apps/kimi-web/src/lib/icons.test.ts +++ b/apps/kimi-web/src/lib/icons.test.ts @@ -1,47 +1,65 @@ // apps/kimi-web/src/lib/icons.test.ts import { describe, expect, it } from 'vitest'; -import { ICONS, iconSvg } from './icons'; +import { ICONS, SIZE_PX, getIcon, iconSvg } from './icons'; describe('ICONS registry', () => { - it('has a non-empty body for every entry', () => { - for (const [name, def] of Object.entries(ICONS)) { - expect(def.body.trim(), `${name} body`).not.toBe(''); + it('is non-empty', () => { + expect(Object.keys(ICONS).length).toBeGreaterThan(0); + }); + + it('every entry has a component and a non-empty raw svg', () => { + for (const [name, entry] of Object.entries(ICONS)) { + // unplugin-icons component can be a function or a defineComponent object + const ct = typeof entry.component; + expect(['function', 'object'], `${name} component type`).toContain(ct); + expect(typeof entry.svg, `${name} svg type`).toBe('string'); + expect(entry.svg.trim(), `${name} svg`).not.toBe(''); + expect(entry.svg.toLowerCase(), `${name} svg contains )', () => { - for (const [name, def] of Object.entries(ICONS)) { - expect(def.body.toLowerCase(), `${name}`).not.toContain(' { + for (const [name, entry] of Object.entries(ICONS)) { + expect(entry.svg, `${name} viewBox`).toContain('viewBox="0 0 24 24"'); } }); +}); - it('every entry is fill-based on a 24x24 grid (Remix)', () => { - for (const [name, def] of Object.entries(ICONS)) { - expect(def.fill, `${name} fill`).toBe(true); - expect(def.viewBox, `${name} viewBox`).toBe('0 0 24 24'); - } +describe('getIcon', () => { + it('returns the entry for a known name', () => { + expect(getIcon('plus')).toBe(ICONS.plus); + }); + + it('returns undefined for an unknown name (runtime fallback)', () => { + // @ts-expect-error - intentional runtime misuse path + expect(getIcon('definitely-not-an-icon')).toBeUndefined(); }); }); describe('iconSvg', () => { - it('renders a Remix icon with the registry defaults', () => { + it('renders a Remix icon with kw-icon class and default md size', () => { const svg = iconSvg('plus'); - expect(svg.startsWith(' { - expect(iconSvg('plus', 'sm')).toContain('width="14" height="14"'); - expect(iconSvg('plus', 'md')).toContain('width="16" height="16"'); - expect(iconSvg('plus', 'lg')).toContain('width="20" height="20"'); + expect(iconSvg('plus', 'sm')).toContain(`width="${SIZE_PX.sm}" height="${SIZE_PX.sm}"`); + expect(iconSvg('plus', 'md')).toContain(`width="${SIZE_PX.md}" height="${SIZE_PX.md}"`); + expect(iconSvg('plus', 'lg')).toContain(`width="${SIZE_PX.lg}" height="${SIZE_PX.lg}"`); }); - it('renders a filled icon with currentColor and no stroke', () => { - const svg = iconSvg('star'); - expect(svg).toContain('fill="currentColor"'); - expect(svg).not.toContain('stroke='); + it('does not duplicate width/height attributes from the raw icon', () => { + const svg = iconSvg('plus'); + const widthCount = (svg.match(/\bwidth="/g) ?? []).length; + const heightCount = (svg.match(/\bheight="/g) ?? []).length; + expect(widthCount).toBe(1); + expect(heightCount).toBe(1); + }); + + it('returns empty string for an unknown name', () => { + // @ts-expect-error - intentional runtime misuse path + expect(iconSvg('definitely-not-an-icon')).toBe(''); }); }); diff --git a/apps/kimi-web/src/lib/icons.ts b/apps/kimi-web/src/lib/icons.ts index d53740d1b..3511af539 100644 --- a/apps/kimi-web/src/lib/icons.ts +++ b/apps/kimi-web/src/lib/icons.ts @@ -1,57 +1,374 @@ // apps/kimi-web/src/lib/icons.ts // Single source of truth for apps/kimi-web icons (design-system §02). // -// Icons come from Remix Icon (https://remixicon.com/, Apache-2.0). The per-icon -// SVG data is generated into ./icon-data.ts by scripts/gen-icon-data.mjs — a -// tree-shaken subset of @iconify-json/ri — so the registry is library-sourced, -// offline, and only bundles the icons we actually use. +// Icons come from Remix Icon (https://remixicon.com/, Apache-2.0) and are +// bundled by unplugin-icons at build time — only the icons listed below end up +// in the production bundle. Each icon is imported twice: once as a Vue +// component (for ) and once as a `?raw` SVG string (for +// iconSvg() in v-html contexts such as lib/toolMeta.ts). // // Remix icons are fill-based (fill="currentColor") on a 24x24 source grid; the -// rendered size is always the token size. Colour follows text. +// rendered size comes from the size token prop. Colour follows text. // // Two consumers share this registry: // - the Vue component (components/ui/Icon.vue) for template use; // - iconSvg() below, for v-html contexts (e.g. lib/toolMeta.ts). -import { iconToSVG } from '@iconify/utils'; -import { ICON_DATA, type IconData, type IconName } from './icon-data'; +import type { Component } from 'vue'; -export type { IconName } from './icon-data'; -export { NAME_TO_REMIX } from './icon-data'; +// Components (Vue) --------------------------------------------------------- +import RiAddLine from '~icons/ri/add-line'; +import RiAlertLine from '~icons/ri/alert-line'; +import RiArrowDownLine from '~icons/ri/arrow-down-line'; +import RiArrowDownSLine from '~icons/ri/arrow-down-s-line'; +import RiArrowGoBackLine from '~icons/ri/arrow-go-back-line'; +import RiArrowRightLine from '~icons/ri/arrow-right-line'; +import RiArrowRightSLine from '~icons/ri/arrow-right-s-line'; +import RiArrowUpLine from '~icons/ri/arrow-up-line'; +import RiBracesLine from '~icons/ri/braces-line'; +import RiChatNewLine from '~icons/ri/chat-new-line'; +import RiCheckLine from '~icons/ri/check-line'; +import RiCloseLine from '~icons/ri/close-line'; +import RiCodeLine from '~icons/ri/code-line'; +import RiCollapseDiagonalLine from '~icons/ri/collapse-diagonal-line'; +import RiContractLeftLine from '~icons/ri/contract-left-line'; +import RiDownloadLine from '~icons/ri/download-line'; +import RiDraggable from '~icons/ri/draggable'; +import RiEqualizerLine from '~icons/ri/equalizer-line'; +import RiExpandDiagonalLine from '~icons/ri/expand-diagonal-line'; +import RiExpandRightLine from '~icons/ri/expand-right-line'; +import RiExternalLinkLine from '~icons/ri/external-link-line'; +import RiFileAddLine from '~icons/ri/file-add-line'; +import RiFileCopyLine from '~icons/ri/file-copy-line'; +import RiFileLine from '~icons/ri/file-line'; +import RiFileTextLine from '~icons/ri/file-text-line'; +import RiFlashlightLine from '~icons/ri/flashlight-line'; +import RiFolderAddLine from '~icons/ri/folder-add-line'; +import RiFolderFill from '~icons/ri/folder-fill'; +import RiFolderLine from '~icons/ri/folder-line'; +import RiFolderOpenLine from '~icons/ri/folder-open-line'; +import RiGitPullRequestLine from '~icons/ri/git-pull-request-line'; +import RiGlobalLine from '~icons/ri/global-line'; +import RiImageLine from '~icons/ri/image-line'; +import RiInformationLine from '~icons/ri/information-line'; +import RiLinksLine from '~icons/ri/links-line'; +import RiListCheck from '~icons/ri/list-check'; +import RiListUnordered from '~icons/ri/list-unordered'; +import RiLoginBoxLine from '~icons/ri/login-box-line'; +import RiMailLine from '~icons/ri/mail-line'; +import RiMessageLine from '~icons/ri/message-line'; +import RiMoreLine from '~icons/ri/more-line'; +import RiPencilLine from '~icons/ri/pencil-line'; +import RiPlayFill from '~icons/ri/play-fill'; +import RiQuestionLine from '~icons/ri/question-line'; +import RiSearchLine from '~icons/ri/search-line'; +import RiSettings3Line from '~icons/ri/settings-3-line'; +import RiSortDesc from '~icons/ri/sort-desc'; +import RiSparklingLine from '~icons/ri/sparkling-line'; +import RiStarFill from '~icons/ri/star-fill'; +import RiStarLine from '~icons/ri/star-line'; +import RiStopFill from '~icons/ri/stop-fill'; +import RiSubtractLine from '~icons/ri/subtract-line'; +import RiTerminalBoxLine from '~icons/ri/terminal-box-line'; +import RiTimeLine from '~icons/ri/time-line'; +import RiToolsLine from '~icons/ri/tools-line'; +import RiUserLine from '~icons/ri/user-line'; + +// Raw SVG strings ---------------------------------------------------------- +import RawAddLine from '~icons/ri/add-line?raw'; +import RawAlertLine from '~icons/ri/alert-line?raw'; +import RawArrowDownLine from '~icons/ri/arrow-down-line?raw'; +import RawArrowDownSLine from '~icons/ri/arrow-down-s-line?raw'; +import RawArrowGoBackLine from '~icons/ri/arrow-go-back-line?raw'; +import RawArrowRightLine from '~icons/ri/arrow-right-line?raw'; +import RawArrowRightSLine from '~icons/ri/arrow-right-s-line?raw'; +import RawArrowUpLine from '~icons/ri/arrow-up-line?raw'; +import RawBracesLine from '~icons/ri/braces-line?raw'; +import RawChatNewLine from '~icons/ri/chat-new-line?raw'; +import RawCheckLine from '~icons/ri/check-line?raw'; +import RawCloseLine from '~icons/ri/close-line?raw'; +import RawCodeLine from '~icons/ri/code-line?raw'; +import RawCollapseDiagonalLine from '~icons/ri/collapse-diagonal-line?raw'; +import RawContractLeftLine from '~icons/ri/contract-left-line?raw'; +import RawDownloadLine from '~icons/ri/download-line?raw'; +import RawDraggable from '~icons/ri/draggable?raw'; +import RawEqualizerLine from '~icons/ri/equalizer-line?raw'; +import RawExpandDiagonalLine from '~icons/ri/expand-diagonal-line?raw'; +import RawExpandRightLine from '~icons/ri/expand-right-line?raw'; +import RawExternalLinkLine from '~icons/ri/external-link-line?raw'; +import RawFileAddLine from '~icons/ri/file-add-line?raw'; +import RawFileCopyLine from '~icons/ri/file-copy-line?raw'; +import RawFileLine from '~icons/ri/file-line?raw'; +import RawFileTextLine from '~icons/ri/file-text-line?raw'; +import RawFlashlightLine from '~icons/ri/flashlight-line?raw'; +import RawFolderAddLine from '~icons/ri/folder-add-line?raw'; +import RawFolderFill from '~icons/ri/folder-fill?raw'; +import RawFolderLine from '~icons/ri/folder-line?raw'; +import RawFolderOpenLine from '~icons/ri/folder-open-line?raw'; +import RawGitPullRequestLine from '~icons/ri/git-pull-request-line?raw'; +import RawGlobalLine from '~icons/ri/global-line?raw'; +import RawImageLine from '~icons/ri/image-line?raw'; +import RawInformationLine from '~icons/ri/information-line?raw'; +import RawLinksLine from '~icons/ri/links-line?raw'; +import RawListCheck from '~icons/ri/list-check?raw'; +import RawListUnordered from '~icons/ri/list-unordered?raw'; +import RawLoginBoxLine from '~icons/ri/login-box-line?raw'; +import RawMailLine from '~icons/ri/mail-line?raw'; +import RawMessageLine from '~icons/ri/message-line?raw'; +import RawMoreLine from '~icons/ri/more-line?raw'; +import RawPencilLine from '~icons/ri/pencil-line?raw'; +import RawPlayFill from '~icons/ri/play-fill?raw'; +import RawQuestionLine from '~icons/ri/question-line?raw'; +import RawSearchLine from '~icons/ri/search-line?raw'; +import RawSettings3Line from '~icons/ri/settings-3-line?raw'; +import RawSortDesc from '~icons/ri/sort-desc?raw'; +import RawSparklingLine from '~icons/ri/sparkling-line?raw'; +import RawStarFill from '~icons/ri/star-fill?raw'; +import RawStarLine from '~icons/ri/star-line?raw'; +import RawStopFill from '~icons/ri/stop-fill?raw'; +import RawSubtractLine from '~icons/ri/subtract-line?raw'; +import RawTerminalBoxLine from '~icons/ri/terminal-box-line?raw'; +import RawTimeLine from '~icons/ri/time-line?raw'; +import RawToolsLine from '~icons/ri/tools-line?raw'; +import RawUserLine from '~icons/ri/user-line?raw'; + +// Public types ------------------------------------------------------------- +export type IconName = + | 'plus' + | 'chat-new' + | 'close' + | 'check' + | 'search' + | 'copy' + | 'link' + | 'external-link' + | 'download' + | 'undo' + | 'send' + | 'image' + | 'settings' + | 'sliders' + | 'log-in' + | 'chevron-down' + | 'chevron-right' + | 'arrow-up' + | 'arrow-down' + | 'arrow-right' + | 'minus' + | 'panel-collapse' + | 'panel-expand' + | 'expand' + | 'collapse' + | 'list' + | 'sort' + | 'grip' + | 'folder' + | 'folder-closed' + | 'folder-plus' + | 'folder-solid' + | 'file' + | 'file-text' + | 'file-plus' + | 'file-off' + | 'image-off' + | 'code' + | 'terminal' + | 'pencil' + | 'tool' + | 'glob' + | 'globe' + | 'check-list' + | 'bolt' + | 'git-pull-request' + | 'message' + | 'mail' + | 'user' + | 'info' + | 'help-circle' + | 'alert-triangle' + | 'clock' + | 'sparkles' + | 'play' + | 'stop' + | 'star' + | 'star-outline' + | 'dots-horizontal'; export type IconSize = 'sm' | 'md' | 'lg'; export const SIZE_PX: Record = { sm: 14, md: 16, lg: 20 }; -export interface IconDef { - /** Inner SVG markup only — no outer . Remix icons are fill-based. */ - body: string; - /** Source grid. Remix icons are "0 0 24 24". */ - viewBox?: string; - /** Solid icon (fill="currentColor", no stroke). Always true for Remix. */ - fill?: boolean; +export interface IconEntry { + /** Vue component that renders the icon (used by ). */ + component: Component; + /** Raw `` string (used by iconSvg() in v-html contexts). */ + svg: string; } -function viewBoxOf(d: IconData): string { - return `0 0 ${d.width ?? 24} ${d.height ?? 24}`; +function entry(component: Component, svg: string): IconEntry { + return { component, svg }; } -/** Back-compat registry view over the generated Remix data. */ -export const ICONS: Record = Object.fromEntries( - (Object.entries(ICON_DATA) as [IconName, IconData][]).map(([name, d]) => [ - name, - { body: d.body, viewBox: viewBoxOf(d), fill: true }, - ]), -) as Record; +export const ICONS: Record = { + plus: entry(RiAddLine, RawAddLine), + 'chat-new': entry(RiChatNewLine, RawChatNewLine), + close: entry(RiCloseLine, RawCloseLine), + check: entry(RiCheckLine, RawCheckLine), + search: entry(RiSearchLine, RawSearchLine), + copy: entry(RiFileCopyLine, RawFileCopyLine), + link: entry(RiLinksLine, RawLinksLine), + 'external-link': entry(RiExternalLinkLine, RawExternalLinkLine), + download: entry(RiDownloadLine, RawDownloadLine), + undo: entry(RiArrowGoBackLine, RawArrowGoBackLine), + send: entry(RiArrowUpLine, RawArrowUpLine), + image: entry(RiImageLine, RawImageLine), + settings: entry(RiSettings3Line, RawSettings3Line), + sliders: entry(RiEqualizerLine, RawEqualizerLine), + 'log-in': entry(RiLoginBoxLine, RawLoginBoxLine), + 'chevron-down': entry(RiArrowDownSLine, RawArrowDownSLine), + 'chevron-right': entry(RiArrowRightSLine, RawArrowRightSLine), + 'arrow-up': entry(RiArrowUpLine, RawArrowUpLine), + 'arrow-down': entry(RiArrowDownLine, RawArrowDownLine), + 'arrow-right': entry(RiArrowRightLine, RawArrowRightLine), + minus: entry(RiSubtractLine, RawSubtractLine), + 'panel-collapse': entry(RiContractLeftLine, RawContractLeftLine), + 'panel-expand': entry(RiExpandRightLine, RawExpandRightLine), + expand: entry(RiExpandDiagonalLine, RawExpandDiagonalLine), + collapse: entry(RiCollapseDiagonalLine, RawCollapseDiagonalLine), + list: entry(RiListUnordered, RawListUnordered), + sort: entry(RiSortDesc, RawSortDesc), + grip: entry(RiDraggable, RawDraggable), + folder: entry(RiFolderOpenLine, RawFolderOpenLine), + 'folder-closed': entry(RiFolderLine, RawFolderLine), + 'folder-plus': entry(RiFolderAddLine, RawFolderAddLine), + 'folder-solid': entry(RiFolderFill, RawFolderFill), + file: entry(RiFileLine, RawFileLine), + 'file-text': entry(RiFileTextLine, RawFileTextLine), + 'file-plus': entry(RiFileAddLine, RawFileAddLine), + 'file-off': entry(RiFileLine, RawFileLine), + 'image-off': entry(RiImageLine, RawImageLine), + code: entry(RiCodeLine, RawCodeLine), + terminal: entry(RiTerminalBoxLine, RawTerminalBoxLine), + pencil: entry(RiPencilLine, RawPencilLine), + tool: entry(RiToolsLine, RawToolsLine), + glob: entry(RiBracesLine, RawBracesLine), + globe: entry(RiGlobalLine, RawGlobalLine), + 'check-list': entry(RiListCheck, RawListCheck), + bolt: entry(RiFlashlightLine, RawFlashlightLine), + 'git-pull-request': entry(RiGitPullRequestLine, RawGitPullRequestLine), + message: entry(RiMessageLine, RawMessageLine), + mail: entry(RiMailLine, RawMailLine), + user: entry(RiUserLine, RawUserLine), + info: entry(RiInformationLine, RawInformationLine), + 'help-circle': entry(RiQuestionLine, RawQuestionLine), + 'alert-triangle': entry(RiAlertLine, RawAlertLine), + clock: entry(RiTimeLine, RawTimeLine), + sparkles: entry(RiSparklingLine, RawSparklingLine), + play: entry(RiPlayFill, RawPlayFill), + stop: entry(RiStopFill, RawStopFill), + star: entry(RiStarFill, RawStarFill), + 'star-outline': entry(RiStarLine, RawStarLine), + 'dots-horizontal': entry(RiMoreLine, RawMoreLine), +}; -export function getIcon(name: IconName): IconDef { +export function getIcon(name: IconName): IconEntry { return ICONS[name]; } +function applySize(svg: string, px: number): string { + return svg + .replace(/\s(?:width|height)="[^"]*"/g, '') + .replace(/^

Icon

-

Icons always come from the centralized registry lib/icons.ts: in templates use the <Icon name size /> component (components/ui/Icon.vue); for v-html contexts (such as a tool glyph) use iconSvg(name, size). Do not hand-write <svg> — the scripts/check-style.mjs icon-from-registry rule flags stray SVGs. Icons come from Remix Icon (Apache-2.0), uniformly in a fill style (fill="currentColor", 24×24 source grid), with color following the text; size uses the three tokens below. The registry is generated on demand by scripts/gen-icon-data.mjs from @iconify-json/ri, offline and tree-shaken. The whole site uses only this one icon family; do not mix in other icon libraries, and never hand-write SVG paths. When an icon is missing, add it to the registry (edit the mapping in gen-icon-data.mjs and regenerate) rather than drawing it in a component.

+

Icons always come from the centralized registry lib/icons.ts: in templates use the <Icon name size /> component (components/ui/Icon.vue); for v-html contexts (such as a tool glyph) use iconSvg(name, size). Do not hand-write <svg> — the scripts/check-style.mjs icon-from-registry rule flags stray SVGs. Icons come from Remix Icon (Apache-2.0), uniformly in a fill style (fill="currentColor", 24×24 source grid), with color following the text; size uses the three tokens below. The registry is bundled on demand by unplugin-icons at build time from @iconify-json/ri — only icons imported in lib/icons.ts end up in the production bundle, fully offline and tree-shaken. The whole site uses only this one icon family; do not mix in other icon libraries, and never hand-write SVG paths. When an icon is missing, add it to the registry — two static ~icons/ri/* imports (component + ?raw string) plus one entry in ICONS in lib/icons.ts; the import names (e.g. RiFolderOpenLine / RawFolderOpenLine) show the ri: icon id. Do not draw it in a component.

Size scale

@@ -311,72 +313,15 @@ onUnmounted(() => {

Icon library

-

Currently registered icons, grouped by purpose. The icon data is determined by the NAME_TO_REMIX mapping in scripts/gen-icon-data.mjs; running pnpm gen:icons generates lib/icon-data.ts. This catalog is regenerated with node --experimental-strip-types scripts/gen-icon-catalog.mjs so the registry and the document never drift.

+

Currently registered icons, grouped by purpose. The display order and grouping are defined by ICON_GROUPS in lib/icons.ts (a hand-maintained array covering the same icon names), and this catalog is rendered directly from that array so the registry and the document never drift.

-
Actions
-
plus
-
chat-new
-
close
-
check
-
search
-
copy
-
link
-
external-link
-
download
-
undo
-
send
-
image
-
settings
-
sliders
-
log-in
-
Navigation & layout
-
chevron-down
-
chevron-right
-
arrow-up
-
arrow-down
-
arrow-right
-
minus
-
panel-collapse
-
panel-expand
-
expand
-
collapse
-
list
-
Files & tools
-
folder
-
folder-closed
-
folder-plus
-
folder-solid
-
file
-
file-text
-
file-plus
-
file-off
-
image-off
-
code
-
terminal
-
pencil
-
glob
-
globe
-
check-list
-
bolt
-
git-pull-request
-
Communication
-
message
-
mail
-
user
-
Status & media
-
info
-
help-circle
-
alert-triangle
-
clock
-
sparkles
-
play
-
stop
-
star
-
star-outline
-
dots-horizontal
-
Other
-
sort
-
grip
+

Do not use emoji as functional icons (the sole exception is the moon phases 🌑…🌘, used only in the "waiting for the Agent to respond" chat state). The Kimi brand mark (the 32×22 eye logo) is a brand asset and is not part of this icon system.

@@ -2340,7 +2285,7 @@ onUnmounted(() => { .icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 8px; margin: 14px 0; } .icon-group-label { grid-column: 1 / -1; margin-top: 10px; font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--d-fg-muted); } .icon-cell { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--d-line); border-radius: 8px; background: var(--d-surface); } - .icon-cell .p-ic { width: 20px; height: 20px; color: var(--d-fg-soft); } + .icon-cell .kw-icon { width: 20px; height: 20px; color: var(--d-fg-soft); } .icon-cell .ic-name { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--d-fg); } .icon-sizes { display: flex; align-items: end; gap: 22px; flex-wrap: wrap; } .icon-sizes .sz { display: flex; flex-direction: column; align-items: center; gap: 8px; font-size: 11px; color: var(--d-fg-muted); font-family: "JetBrains Mono", ui-monospace, monospace; } diff --git a/apps/kimi-web/tsconfig.json b/apps/kimi-web/tsconfig.json index f918545e4..2275c1c7d 100644 --- a/apps/kimi-web/tsconfig.json +++ b/apps/kimi-web/tsconfig.json @@ -17,7 +17,7 @@ "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": false, - "types": ["vite/client"] + "types": ["vite/client", "unplugin-icons/types/vue"] }, "include": ["src/**/*.ts", "src/**/*.vue", "src/env.d.ts"] } diff --git a/apps/kimi-web/vite.config.ts b/apps/kimi-web/vite.config.ts index 1de3a1667..d72cf22f3 100644 --- a/apps/kimi-web/vite.config.ts +++ b/apps/kimi-web/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; +import Icons from 'unplugin-icons/vite'; import { readFileSync } from 'node:fs'; const webPort = Number(process.env.WEB_PORT) || 5175; @@ -11,7 +12,7 @@ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), }; export default defineConfig({ - plugins: [vue()], + plugins: [vue(), Icons({ compiler: 'vue3' })], // Expose the dev proxy's upstream server target to the client so the UI can // show which server it is connected to (the browser otherwise only sees its // own same-origin URL). Unused by the same-origin production build. diff --git a/flake.nix b/flake.nix index a672a7b16..bdb4158d0 100644 --- a/flake.nix +++ b/flake.nix @@ -152,7 +152,7 @@ inherit (finalAttrs) pname version src pnpmWorkspaces; inherit pnpm; fetcherVersion = 3; - hash = "sha256-+hTrX3pa8fjK+eh4q2BqOEAm9C7zvMXrKf4bwbJoG98="; + hash = "sha256-RPjCWL7NqDSKgpHGL16zPlUOfjWN2rkaDY/4GFAD8VA="; }; nativeBuildInputs = [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7f4098e7..7396c9c5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,15 +172,6 @@ importers: '@fontsource-variable/jetbrains-mono': specifier: ^5.2.8 version: 5.2.8 - '@iconify-json/ri': - specifier: ^1.2.10 - version: 1.2.10 - '@iconify/utils': - specifier: ^3.1.3 - version: 3.1.3 - '@iconify/vue': - specifier: ^5.0.1 - version: 5.0.1(vue@3.5.35(typescript@6.0.2)) '@xterm/addon-fit': specifier: ^0.11.0 version: 0.11.0 @@ -209,12 +200,18 @@ importers: specifier: ^11.4.5 version: 11.4.5(vue@3.5.35(typescript@6.0.2)) devDependencies: + '@iconify-json/ri': + specifier: ^1.2.10 + version: 1.2.10 '@vitejs/plugin-vue': specifier: ^5.2.4 version: 5.2.4(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.35(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 + unplugin-icons: + specifier: ^23.0.0 + version: 23.0.1(@vue/compiler-sfc@3.5.35) vite: specifier: ^6.3.3 version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)(yaml@2.8.3) @@ -1627,11 +1624,6 @@ packages: '@iconify/utils@3.1.3': resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} - '@iconify/vue@5.0.1': - resolution: {integrity: sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw==} - peerDependencies: - vue: '>=3.0.0' - '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -3243,6 +3235,11 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -3739,6 +3736,12 @@ packages: engines: {node: '>=18'} hasBin: true + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + config-file-ts@0.2.8-rc1: resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} @@ -4354,6 +4357,9 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + exsolve@1.1.0: + resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -5258,6 +5264,10 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -5656,6 +5666,9 @@ packages: engines: {node: '>=10'} hasBin: true + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -6029,6 +6042,12 @@ packages: resolution: {integrity: sha512-u9mdErTewKSMsr+ceCt8VcNuNP0ro5AXiPXhUVApuEyqr2Zlvt+DdCFBcm+yGWN8mhOdZJ27meIDbnoZgfzpOw==} hasBin: true + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + plist@3.1.1: resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} engines: {node: '>=10.4.0'} @@ -7025,6 +7044,9 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + uhyphen@0.2.0: resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} @@ -7098,6 +7120,27 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-icons@23.0.1: + resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==} + peerDependencies: + '@svgr/core': '>=7.0.0' + '@svgx/core': ^1.0.1 + '@vue/compiler-sfc': ^3.0.2 + svelte: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@svgr/core': + optional: true + '@svgx/core': + optional: true + '@vue/compiler-sfc': + optional: true + svelte: + optional: true + + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unrun@0.2.34: resolution: {integrity: sha512-LyaghRBR++r7svhDK6tnDz2XaYHWdneBOA0jbS8wnRsHerI9MFljX4fIiTgbbNbEVzZ0C9P1OjWLLe1OqoaaEw==} engines: {node: '>=20.19.0'} @@ -7376,6 +7419,9 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -8508,11 +8554,6 @@ snapshots: '@iconify/types': 2.0.0 import-meta-resolve: 4.2.0 - '@iconify/vue@5.0.1(vue@3.5.35(typescript@6.0.2))': - dependencies: - '@iconify/types': 2.0.0 - vue: 3.5.35(typescript@6.0.2) - '@inquirer/external-editor@1.0.3(@types/node@22.19.17)': dependencies: chardet: 2.1.1 @@ -10107,6 +10148,8 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn@8.17.0: {} + agent-base@6.0.2: dependencies: debug: 4.4.3 @@ -10676,6 +10719,10 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + confbox@0.1.8: {} + + confbox@0.2.4: {} + config-file-ts@0.2.8-rc1: dependencies: glob: 10.5.0 @@ -11464,6 +11511,8 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.1.0: {} + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -12483,6 +12532,12 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.2 + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -12983,6 +13038,13 @@ snapshots: mkdirp@1.0.4: {} + mlly@1.8.2: + dependencies: + acorn: 8.17.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + mri@1.2.0: {} ms@2.1.3: {} @@ -13362,6 +13424,18 @@ snapshots: pkg-pr-new@0.0.75: {} + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.1.0 + pathe: 2.0.3 + plist@3.1.1: dependencies: '@xmldom/xmldom': 0.9.10 @@ -14565,6 +14639,8 @@ snapshots: uc.micro@2.1.0: {} + ufo@1.6.4: {} + uhyphen@0.2.0: {} uint8array-extras@1.5.0: {} @@ -14644,6 +14720,23 @@ snapshots: unpipe@1.0.0: {} + unplugin-icons@23.0.1(@vue/compiler-sfc@3.5.35): + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/utils': 3.1.3 + local-pkg: 1.2.1 + obug: 2.1.1 + unplugin: 2.3.11 + optionalDependencies: + '@vue/compiler-sfc': 3.5.35 + + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.17.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + unrun@0.2.34: dependencies: rolldown: 1.0.0-rc.12 @@ -14924,6 +15017,8 @@ snapshots: webidl-conversions@7.0.0: optional: true + webpack-virtual-modules@0.6.2: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3