sbuilder-mcp 0.80.0 → 0.81.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ All notable changes to this project are documented in this file.
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.81.1] - 2026-10-10
10
+
11
+ ### Fixed
12
+ - `sb_set` and `sb_add` suggest the right icon for a misspelled name, ranking names that contain the typed word first, then close typos by edit distance, and honoring a `Fill` or `Line` suffix the caller wrote.
13
+ - `sb_duplicate` with `to_page_id` reports that `parent_id` is not on the target page and says to omit it or read ids with `sb_outline`.
14
+ - `sb_media_upload` of a font file tells the caller how to create a font family instead of suggesting `sb_set` with `src`.
15
+
16
+ ## [0.81.0] - 2026-10-10
17
+
18
+ ### Added
19
+ - `sb_set` and `sb_add` warn with `unknown_icon` and the nearest valid name when an icon key is not in the editor's icon list, and `sb_review` reports the same finding.
20
+ - `sb_catalog_search` finds icons with an `icon:<word>` query, and `sb_traits_for` returns `icon_keys` for elements that take an icon.
21
+ - `sb_duplicate` accepts `to_page_id` and `to_site_id` to paste a section onto another page or another site, running every check before the target page changes.
22
+ - `sb_store` with `action:"template_save"` accepts `template_id` to overwrite an existing template's content, guarded by its revision.
23
+ - `sb_media_upload` accepts font files (woff2, woff, ttf, otf).
24
+
9
25
  ## [0.80.0] - 2026-10-10
10
26
 
11
27
  ### Added
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,22 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
6
6
  Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## [0.81.1] - 2026-10-10
10
+
11
+ ### Fixed
12
+ - `sb_set` và `sb_add` gợi ý đúng icon cho tên gõ sai, ưu tiên tên chứa nguyên từ đã gõ, rồi đến lỗi gõ gần theo khoảng cách sửa, và tôn trọng đuôi `Fill` hoặc `Line` do người gọi viết.
13
+ - `sb_duplicate` với `to_page_id` báo `parent_id` không có trên trang đích và hướng dẫn bỏ qua nó hoặc đọc id bằng `sb_outline`.
14
+ - `sb_media_upload` với file font hướng dẫn cách tạo họ font thay vì gợi ý `sb_set` với `src`.
15
+
16
+ ## [0.81.0] - 2026-10-10
17
+
18
+ ### Added
19
+ - `sb_set` và `sb_add` cảnh báo `unknown_icon` kèm tên hợp lệ gần nhất khi khoá icon không có trong danh sách icon của editor, và `sb_review` báo cùng phát hiện đó.
20
+ - `sb_catalog_search` tìm icon bằng truy vấn `icon:<từ>`, và `sb_traits_for` trả về `icon_keys` cho các element nhận icon.
21
+ - `sb_duplicate` nhận `to_page_id` và `to_site_id` để dán section sang trang hoặc site khác, chạy mọi kiểm tra trước khi đổi trang đích.
22
+ - `sb_store` với `action:"template_save"` nhận `template_id` để ghi đè nội dung template có sẵn, có rào theo rev.
23
+ - `sb_media_upload` nhận file font (woff2, woff, ttf, otf).
24
+
9
25
  ## [0.80.0] - 2026-10-10
10
26
 
11
27
  ### Added
package/README.md CHANGED
@@ -93,7 +93,7 @@ in this client: one would put the very key the platform exists to hold back into
93
93
  | `sb_set` | Write style/config/specials. Per breakpoint by default |
94
94
  | `sb_move` | Move a node to another parent |
95
95
  | `sb_remove` | Remove a node and its subtree |
96
- | `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original |
96
+ | `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original — or onto another page or site, as the editor's copy/paste does (assets copied, references that cannot travel listed) |
97
97
  | `sb_templates` | The store's saved section templates, plus the BUILT-IN layouts — hero, feature trio, stats, FAQ, CTA band, product shelf, category strip, brand wall, trust band — composed against the page's own tokens |
98
98
  | `sb_template_use` | Instantiate a template into a page |
99
99
  | `sb_page_list` | Every page on the site |
package/README.vi.md CHANGED
@@ -90,7 +90,7 @@ nhét ngược lại vào mọi bản cài.
90
90
  | `sb_set` | Ghi style/config/specials. Mặc định theo breakpoint |
91
91
  | `sb_move` | Chuyển node sang cha khác |
92
92
  | `sb_remove` | Xoá node và cả cây con |
93
- | `sb_duplicate` | Nhân bản một node và cả cây con dưới id mới, ngay sau bản gốc |
93
+ | `sb_duplicate` | Nhân bản một node và cả cây con dưới id mới, ngay sau bản gốc — hoặc sang trang/site khác như copy/paste của editor (ảnh được copy, tham chiếu không mang theo được sẽ được liệt kê) |
94
94
  | `sb_templates` | Section template đã lưu của cửa hàng, cộng bộ layout DỰNG SẴN — hero, ba lợi ích, số liệu, FAQ, dải kêu gọi, kệ sản phẩm, dải danh mục, dải logo, dải cam kết — soạn theo token của chính trang |
95
95
  | `sb_template_use` | Thả một template vào trang |
96
96
  | `sb_page_list` | Mọi trang của site |
@@ -4,6 +4,7 @@ import { isBaseOnlyConfig } from '../domains/site/baseonly.js';
4
4
  import { bindableFields } from '../domains/site/writecheck.js';
5
5
  import { animationValues, animationVocabulary, elementVocabularies, sharedVocabularies, vocabulariesForWrites, } from '../domains/site/vocabulary.js';
6
6
  import { neverTranslatedOn, translatableSpecials } from '../domains/site/translate.js';
7
+ import { iconKeysOf } from '../domains/site/icons.js';
7
8
  /** Eight to choose from; the hints for the chosen one come with sb_traits_for. */
8
9
  export const DEFAULT_CATALOG_LIMIT = 8;
9
10
  /**
@@ -302,6 +303,8 @@ export function traitsFor(type, control) {
302
303
  ...(el.bindingEvents ? { binding_events: el.bindingEvents } : {}),
303
304
  // The specials a record binding may target on this element.
304
305
  ...(bindable.length ? { bindable } : {}),
306
+ // Keys holding a RemixIcon name — sb_catalog_search query "icon:<word>" finds one.
307
+ ...(iconKeysOf(el.type).length ? { icon_keys: iconKeysOf(el.type) } : {}),
305
308
  // The keys this element actually seeds. For `config` and `specials` —
306
309
  // which, unlike `style`, are NOT open — this is the machine-readable
307
310
  // answer to "what does this element store", and often the only one.
@@ -15,3 +15,102 @@ export const ICON_SOURCE = {
15
15
  * skipped, because the page then carries an empty box nobody put there.
16
16
  */
17
17
  export const ICON_NAMES = new Set(["24HoursFill", "24HoursLine", "4kFill", "4kLine", "AB", "AccessibilityFill", "AccessibilityLine", "AccountBox2Fill", "AccountBox2Line", "AccountBoxFill", "AccountBoxLine", "AccountCircle2Fill", "AccountCircle2Line", "AccountCircleFill", "AccountCircleLine", "AccountPinBoxFill", "AccountPinBoxLine", "AccountPinCircleFill", "AccountPinCircleLine", "AddBoxFill", "AddBoxLine", "AddCircleFill", "AddCircleLine", "AddFill", "AddLargeFill", "AddLargeLine", "AddLine", "AdminFill", "AdminLine", "AdvertisementFill", "AdvertisementLine", "AedElectrodesFill", "AedElectrodesLine", "AedFill", "AedLine", "Ai", "AiAgentFill", "AiAgentLine", "AiGenerate", "AiGenerate2", "AiGenerate2Fill", "AiGenerate2Line", "AiGenerate3dFill", "AiGenerate3dLine", "AiGenerateText", "AirplayFill", "AirplayLine", "AlarmAddFill", "AlarmAddLine", "AlarmFill", "AlarmLine", "AlarmSnoozeFill", "AlarmSnoozeLine", "AlarmWarningFill", "AlarmWarningLine", "AlbumFill", "AlbumLine", "AlertFill", "AlertLine", "AlibabaCloudFill", "AlibabaCloudLine", "AliensFill", "AliensLine", "AlignBottom", "AlignCenter", "AlignItemBottomFill", "AlignItemBottomLine", "AlignItemHorizontalCenterFill", "AlignItemHorizontalCenterLine", "AlignItemLeftFill", "AlignItemLeftLine", "AlignItemRightFill", "AlignItemRightLine", "AlignItemTopFill", "AlignItemTopLine", "AlignItemVerticalCenterFill", "AlignItemVerticalCenterLine", "AlignJustify", "AlignLeft", "AlignRight", "AlignTop", "AlignVertically", "AlipayFill", "AlipayLine", "AmazonFill", "AmazonLine", "AnchorFill", "AnchorLine", "AncientGateFill", "AncientGateLine", "AncientPavilionFill", "AncientPavilionLine", "AndroidFill", "AndroidLine", "AngularjsFill", "AngularjsLine", "AnthropicFill", "AnthropicLine", "Anticlockwise2Fill", "Anticlockwise2Line", "AnticlockwiseFill", "AnticlockwiseLine", "AppStoreFill", "AppStoreLine", "AppleFill", "AppleLine", "Apps2AddFill", "Apps2AddLine", "Apps2AiFill", "Apps2AiLine", "Apps2Fill", "Apps2Line", "AppsAiFill", "AppsAiLine", "AppsFill", "AppsLine", "Archive2Fill", "Archive2Line", "ArchiveDrawerFill", "ArchiveDrawerLine", "ArchiveFill", "ArchiveLine", "ArchiveStackFill", "ArchiveStackLine", "AreaChartFill", "AreaChartLine", "ArmchairFill", "ArmchairLine", "ArrowDownBoxFill", "ArrowDownBoxLine", "ArrowDownCircleFill", "ArrowDownCircleLine", "ArrowDownDoubleFill", "ArrowDownDoubleLine", "ArrowDownFill", "ArrowDownLine", "ArrowDownLongFill", "ArrowDownLongLine", "ArrowDownSFill", "ArrowDownSLine", "ArrowDownWideFill", "ArrowDownWideLine", "ArrowDropDownFill", "ArrowDropDownLine", "ArrowDropLeftFill", "ArrowDropLeftLine", "ArrowDropRightFill", "ArrowDropRightLine", "ArrowDropUpFill", "ArrowDropUpLine", "ArrowGoBackFill", "ArrowGoBackLine", "ArrowGoForwardFill", "ArrowGoForwardLine", "ArrowLeftBoxFill", "ArrowLeftBoxLine", "ArrowLeftCircleFill", "ArrowLeftCircleLine", "ArrowLeftDoubleFill", "ArrowLeftDoubleLine", "ArrowLeftDownBoxFill", "ArrowLeftDownBoxLine", "ArrowLeftDownFill", "ArrowLeftDownLine", "ArrowLeftDownLongFill", "ArrowLeftDownLongLine", "ArrowLeftFill", "ArrowLeftLine", "ArrowLeftLongFill", "ArrowLeftLongLine", "ArrowLeftRightFill", "ArrowLeftRightLine", "ArrowLeftSFill", "ArrowLeftSLine", "ArrowLeftUpBoxFill", "ArrowLeftUpBoxLine", "ArrowLeftUpFill", "ArrowLeftUpLine", "ArrowLeftUpLongFill", "ArrowLeftUpLongLine", "ArrowLeftWideFill", "ArrowLeftWideLine", "ArrowRightBoxFill", "ArrowRightBoxLine", "ArrowRightCircleFill", "ArrowRightCircleLine", "ArrowRightDoubleFill", "ArrowRightDoubleLine", "ArrowRightDownBoxFill", "ArrowRightDownBoxLine", "ArrowRightDownFill", "ArrowRightDownLine", "ArrowRightDownLongFill", "ArrowRightDownLongLine", "ArrowRightFill", "ArrowRightLine", "ArrowRightLongFill", "ArrowRightLongLine", "ArrowRightSFill", "ArrowRightSLine", "ArrowRightUpBoxFill", "ArrowRightUpBoxLine", "ArrowRightUpFill", "ArrowRightUpLine", "ArrowRightUpLongFill", "ArrowRightUpLongLine", "ArrowRightWideFill", "ArrowRightWideLine", "ArrowTurnBackFill", "ArrowTurnBackLine", "ArrowTurnForwardFill", "ArrowTurnForwardLine", "ArrowUpBoxFill", "ArrowUpBoxLine", "ArrowUpCircleFill", "ArrowUpCircleLine", "ArrowUpDoubleFill", "ArrowUpDoubleLine", "ArrowUpDownFill", "ArrowUpDownLine", "ArrowUpFill", "ArrowUpLine", "ArrowUpLongFill", "ArrowUpLongLine", "ArrowUpSFill", "ArrowUpSLine", "ArrowUpWideFill", "ArrowUpWideLine", "Artboard2Fill", "Artboard2Line", "ArtboardFill", "ArtboardLine", "ArticleFill", "ArticleLine", "AspectRatioFill", "AspectRatioLine", "Asterisk", "AtFill", "AtLine", "AtomFill", "AtomLine", "Attachment2", "AttachmentFill", "AttachmentLine", "AuctionFill", "AuctionLine", "AwardFill", "AwardLine", "BaiduFill", "BaiduLine", "BallPenFill", "BallPenLine", "BankCard2Fill", "BankCard2Line", "BankCardFill", "BankCardLine", "BankFill", "BankLine", "BarChart2Fill", "BarChart2Line", "BarChartBoxAiFill", "BarChartBoxAiLine", "BarChartBoxFill", "BarChartBoxLine", "BarChartFill", "BarChartGroupedFill", "BarChartGroupedLine", "BarChartHorizontalFill", "BarChartHorizontalLine", "BarChartLine", "BarcodeBoxFill", "BarcodeBoxLine", "BarcodeFill", "BarcodeLine", "BardFill", "BardLine", "BarricadeFill", "BarricadeLine", "BaseStationFill", "BaseStationLine", "BaseballFill", "BaseballLine", "BasketballFill", "BasketballLine", "Battery2ChargeFill", "Battery2ChargeLine", "Battery2Fill", "Battery2Line", "BatteryChargeFill", "BatteryChargeLine", "BatteryFill", "BatteryLine", "BatteryLowFill", "BatteryLowLine", "BatterySaverFill", "BatterySaverLine", "BatteryShareFill", "BatteryShareLine", "BearSmileFill", "BearSmileLine", "BeerFill", "BeerLine", "BehanceFill", "BehanceLine", "BellFill", "BellLine", "BikeFill", "BikeLine", "BilibiliFill", "BilibiliLine", "BillFill", "BillLine", "BilliardsFill", "BilliardsLine", "BinocularsFill", "BinocularsLine", "BitCoinFill", "BitCoinLine", "BlazeFill", "BlazeLine", "BlenderFill", "BlenderLine", "BloggerFill", "BloggerLine", "BlueskyFill", "BlueskyLine", "BluetoothConnectFill", "BluetoothConnectLine", "BluetoothFill", "BluetoothLine", "BlurOffFill", "BlurOffLine", "BnbFill", "BnbLine", "BodyScanFill", "BodyScanLine", "Bold", "Book2Fill", "Book2Line", "Book3Fill", "Book3Line", "BookAiFill", "BookAiLine", "BookFill", "BookLine", "BookMarkedFill", "BookMarkedLine", "BookOpenFill", "BookOpenLine", "BookReadFill", "BookReadLine", "BookShelfFill", "BookShelfLine", "BookletFill", "BookletLine", "Bookmark2Fill", "Bookmark2Line", "Bookmark3Fill", "Bookmark3Line", "BookmarkFill", "BookmarkLine", "BootstrapFill", "BootstrapLine", "BowlFill", "BowlLine", "Box1Fill", "Box1Line", "Box2Fill", "Box2Line", "Box3Fill", "Box3Line", "BoxingFill", "BoxingLine", "BracesFill", "BracesLine", "BracketsFill", "BracketsLine", "Brain2Fill", "Brain2Line", "Brain3Fill", "Brain3Line", "Brain4Fill", "Brain4Line", "BrainAi3Fill", "BrainAi3Line", "BrainFill", "BrainLine", "BreadFill", "BreadLine", "Briefcase2Fill", "Briefcase2Line", "Briefcase3Fill", "Briefcase3Line", "Briefcase4Fill", "Briefcase4Line", "Briefcase5Fill", "Briefcase5Line", "BriefcaseFill", "BriefcaseLine", "BringForward", "BringToFront", "BroadcastFill", "BroadcastLine", "Brush2Fill", "Brush2Line", "Brush3Fill", "Brush3Line", "Brush4Fill", "Brush4Line", "BrushAi3Fill", "BrushAi3Line", "BrushAiFill", "BrushAiLine", "BrushFill", "BrushLine", "BtcFill", "BtcLine", "BubbleChartFill", "BubbleChartLine", "Bug2Fill", "Bug2Line", "BugFill", "BugLine", "Building2Fill", "Building2Line", "Building3Fill", "Building3Line", "Building4Fill", "Building4Line", "BuildingFill", "BuildingLine", "Bus2Fill", "Bus2Line", "BusFill", "BusLine", "BusWifiFill", "BusWifiLine", "CactusFill", "CactusLine", "Cake2Fill", "Cake2Line", "Cake3Fill", "Cake3Line", "CakeFill", "CakeLine", "CalculatorFill", "CalculatorLine", "Calendar2Fill", "Calendar2Line", "CalendarCheckFill", "CalendarCheckLine", "CalendarCloseFill", "CalendarCloseLine", "CalendarEventFill", "CalendarEventLine", "CalendarFill", "CalendarLine", "CalendarScheduleFill", "CalendarScheduleLine", "CalendarTodoFill", "CalendarTodoLine", "CalendarView", "Camera2Fill", "Camera2Line", "Camera3Fill", "Camera3Line", "Camera4Fill", "Camera4Line", "CameraAi2Fill", "CameraAi2Line", "CameraAiFill", "CameraAiLine", "CameraFill", "CameraLensAiFill", "CameraLensAiLine", "CameraLensFill", "CameraLensLine", "CameraLine", "CameraOffFill", "CameraOffLine", "CameraSwitchFill", "CameraSwitchLine", "CandleFill", "CandleLine", "CapsuleFill", "CapsuleLine", "CarFill", "CarLine", "CarWashingFill", "CarWashingLine", "CaravanFill", "CaravanLine", "CarouselView", "CashFill", "CashLine", "CastFill", "CastLine", "CellphoneFill", "CellphoneLine", "CelsiusFill", "CelsiusLine", "CentosFill", "CentosLine", "Certificate2Fill", "Certificate2Line", "CertificateFill", "CertificateLine", "CharacterRecognitionFill", "CharacterRecognitionLine", "ChargingPile2Fill", "ChargingPile2Line", "ChargingPileFill", "ChargingPileLine", "Chat1Fill", "Chat1Line", "Chat2Fill", "Chat2Line", "Chat3Fill", "Chat3Line", "Chat4Fill", "Chat4Line", "ChatAi2Fill", "ChatAi2Line", "ChatAi3Fill", "ChatAi3Line", "ChatAi4Fill", "ChatAi4Line", "ChatAiFill", "ChatAiLine", "ChatCheckFill", "ChatCheckLine", "ChatDeleteFill", "ChatDeleteLine", "ChatDownloadFill", "ChatDownloadLine", "ChatFollowUpFill", "ChatFollowUpLine", "ChatForwardFill", "ChatForwardLine", "ChatHeartFill", "ChatHeartLine", "ChatHistoryFill", "ChatHistoryLine", "ChatNewFill", "ChatNewLine", "ChatOffFill", "ChatOffLine", "ChatPollFill", "ChatPollLine", "ChatPrivateFill", "ChatPrivateLine", "ChatQuoteFill", "ChatQuoteLine", "ChatSearchFill", "ChatSearchLine", "ChatSettingsFill", "ChatSettingsLine", "ChatSmile2Fill", "ChatSmile2Line", "ChatSmile3Fill", "ChatSmile3Line", "ChatSmileAi3Fill", "ChatSmileAi3Line", "ChatSmileAiFill", "ChatSmileAiLine", "ChatSmileFill", "ChatSmileLine", "ChatThreadFill", "ChatThreadLine", "ChatUnreadFill", "ChatUnreadLine", "ChatUploadFill", "ChatUploadLine", "ChatVoiceAiFill", "ChatVoiceAiLine", "ChatVoiceFill", "ChatVoiceLine", "CheckDoubleFill", "CheckDoubleLine", "CheckFill", "CheckLine", "CheckboxBlankCircleFill", "CheckboxBlankCircleLine", "CheckboxBlankFill", "CheckboxBlankLine", "CheckboxCircleFill", "CheckboxCircleLine", "CheckboxFill", "CheckboxIndeterminateFill", "CheckboxIndeterminateLine", "CheckboxLine", "CheckboxMultipleBlankFill", "CheckboxMultipleBlankLine", "CheckboxMultipleFill", "CheckboxMultipleLine", "ChessFill", "ChessLine", "ChinaRailwayFill", "ChinaRailwayLine", "ChromeFill", "ChromeLine", "CircleFill", "CircleLine", "ClapperboardAiFill", "ClapperboardAiLine", "ClapperboardFill", "ClapperboardLine", "ClaudeFill", "ClaudeLine", "ClipboardFill", "ClipboardLine", "Clockwise2Fill", "Clockwise2Line", "ClockwiseFill", "ClockwiseLine", "CloseCircleFill", "CloseCircleLine", "CloseFill", "CloseLargeFill", "CloseLargeLine", "CloseLine", "ClosedCaptioningAiFill", "ClosedCaptioningAiLine", "ClosedCaptioningFill", "ClosedCaptioningLine", "CloudFill", "CloudLine", "CloudOffFill", "CloudOffLine", "CloudWindyFill", "CloudWindyLine", "Cloudy2Fill", "Cloudy2Line", "CloudyFill", "CloudyLine", "CodeAiFill", "CodeAiLine", "CodeBlock", "CodeBoxFill", "CodeBoxLine", "CodeFill", "CodeLine", "CodeSFill", "CodeSLine", "CodeSSlashFill", "CodeSSlashLine", "CodeView", "CodepenFill", "CodepenLine", "CoinFill", "CoinLine", "CoinsFill", "CoinsLine", "CollageFill", "CollageLine", "CollapseDiagonal2Fill", "CollapseDiagonal2Line", "CollapseDiagonalFill", "CollapseDiagonalLine", "CollapseHorizontalFill", "CollapseHorizontalLine", "CollapseVerticalFill", "CollapseVerticalLine", "ColorFilterAiFill", "ColorFilterAiLine", "ColorFilterFill", "ColorFilterLine", "CommandFill", "CommandLine", "CommunityFill", "CommunityLine", "Compass2Fill", "Compass2Line", "Compass3Fill", "Compass3Line", "Compass4Fill", "Compass4Line", "CompassDiscoverFill", "CompassDiscoverLine", "CompassFill", "CompassLine", "Compasses2Fill", "Compasses2Line", "CompassesFill", "CompassesLine", "ComputerFill", "ComputerLine", "ContactsBook2Fill", "ContactsBook2Line", "ContactsBook3Fill", "ContactsBook3Line", "ContactsBookFill", "ContactsBookLine", "ContactsBookUploadFill", "ContactsBookUploadLine", "ContactsFill", "ContactsLine", "ContractFill", "ContractLeftFill", "ContractLeftLine", "ContractLeftRightFill", "ContractLeftRightLine", "ContractLine", "ContractRightFill", "ContractRightLine", "ContractUpDownFill", "ContractUpDownLine", "Contrast2Fill", "Contrast2Line", "ContrastDrop2Fill", "ContrastDrop2Line", "ContrastDropFill", "ContrastDropLine", "ContrastFill", "ContrastLine", "CookieFill", "CookieLine", "CopilotFill", "CopilotLine", "CopperCoinFill", "CopperCoinLine", "CopperDiamondFill", "CopperDiamondLine", "CopyleftFill", "CopyleftLine", "CopyrightFill", "CopyrightLine", "CoreosFill", "CoreosLine", "CornerDownLeftFill", "CornerDownLeftLine", "CornerDownRightFill", "CornerDownRightLine", "CornerLeftDownFill", "CornerLeftDownLine", "CornerLeftUpFill", "CornerLeftUpLine", "CornerRightDownFill", "CornerRightDownLine", "CornerRightUpFill", "CornerRightUpLine", "CornerUpLeftDoubleFill", "CornerUpLeftDoubleLine", "CornerUpLeftFill", "CornerUpLeftLine", "CornerUpRightDoubleFill", "CornerUpRightDoubleLine", "CornerUpRightFill", "CornerUpRightLine", "Coupon2Fill", "Coupon2Line", "Coupon3Fill", "Coupon3Line", "Coupon4Fill", "Coupon4Line", "Coupon5Fill", "Coupon5Line", "CouponFill", "CouponLine", "CpuFill", "CpuLine", "CreativeCommonsByFill", "CreativeCommonsByLine", "CreativeCommonsFill", "CreativeCommonsLine", "CreativeCommonsNcFill", "CreativeCommonsNcLine", "CreativeCommonsNdFill", "CreativeCommonsNdLine", "CreativeCommonsSaFill", "CreativeCommonsSaLine", "CreativeCommonsZeroFill", "CreativeCommonsZeroLine", "CriminalFill", "CriminalLine", "Crop2Fill", "Crop2Line", "CropFill", "CropLine", "CrossFill", "CrossLine", "Crosshair2Fill", "Crosshair2Line", "CrosshairFill", "CrosshairLine", "Css3Fill", "Css3Line", "CupFill", "CupLine", "CurrencyFill", "CurrencyLine", "CursorAiFill", "CursorAiLine", "CursorFill", "CursorHand", "CursorLine", "CustomSize", "CustomerService2Fill", "CustomerService2Line", "CustomerServiceFill", "CustomerServiceLine", "Dashboard2Fill", "Dashboard2Line", "Dashboard3Fill", "Dashboard3Line", "DashboardFill", "DashboardHorizontalFill", "DashboardHorizontalLine", "DashboardLine", "Database2Fill", "Database2Line", "DatabaseFill", "DatabaseLine", "DeepseekFill", "DeepseekLine", "DeleteBack2Fill", "DeleteBack2Line", "DeleteBackFill", "DeleteBackLine", "DeleteBin2Fill", "DeleteBin2Line", "DeleteBin3Fill", "DeleteBin3Line", "DeleteBin4Fill", "DeleteBin4Line", "DeleteBin5Fill", "DeleteBin5Line", "DeleteBin6Fill", "DeleteBin6Line", "DeleteBin7Fill", "DeleteBin7Line", "DeleteBinFill", "DeleteBinLine", "DeleteColumn", "DeleteRow", "DeviceFill", "DeviceLine", "DeviceRecoverFill", "DeviceRecoverLine", "DiamondFill", "DiamondLine", "DiamondRingFill", "DiamondRingLine", "Dice1Fill", "Dice1Line", "Dice2Fill", "Dice2Line", "Dice3Fill", "Dice3Line", "Dice4Fill", "Dice4Line", "Dice5Fill", "Dice5Line", "Dice6Fill", "Dice6Line", "DiceFill", "DiceLine", "DingdingFill", "DingdingLine", "DirectionFill", "DirectionLine", "DiscFill", "DiscLine", "DiscordFill", "DiscordLine", "DiscountPercentFill", "DiscountPercentLine", "DiscussFill", "DiscussLine", "DislikeFill", "DislikeLine", "DisqusFill", "DisqusLine", "DivideFill", "DivideLine", "DnaFill", "DnaLine", "DonutChartFill", "DonutChartLine", "DoorClosedFill", "DoorClosedLine", "DoorFill", "DoorLine", "DoorLockBoxFill", "DoorLockBoxLine", "DoorLockFill", "DoorLockLine", "DoorOpenFill", "DoorOpenLine", "DossierFill", "DossierLine", "DoubanFill", "DoubanLine", "DoubleQuotesL", "DoubleQuotesR", "Download2Fill", "Download2Line", "DownloadCloud2Fill", "DownloadCloud2Line", "DownloadCloudFill", "DownloadCloudLine", "DownloadFill", "DownloadLine", "DraftFill", "DraftLine", "DragDropFill", "DragDropLine", "DragMove2Fill", "DragMove2Line", "DragMoveFill", "DragMoveLine", "Draggable", "DrawFill", "DrawLine", "DribbbleFill", "DribbbleLine", "Drinks2Fill", "Drinks2Line", "DrinksFill", "DrinksLine", "DriveFill", "DriveLine", "DrizzleFill", "DrizzleLine", "DropFill", "DropLine", "DropboxFill", "DropboxLine", "DropdownList", "DropperFill", "DropperLine", "DualSim1Fill", "DualSim1Line", "DualSim2Fill", "DualSim2Line", "DvFill", "DvLine", "DvdAiFill", "DvdAiLine", "DvdFill", "DvdLine", "EBike2Fill", "EBike2Line", "EBikeFill", "EBikeLine", "EarthFill", "EarthLine", "EarthquakeFill", "EarthquakeLine", "EdgeFill", "EdgeLine", "EdgeNewFill", "EdgeNewLine", "Edit2Fill", "Edit2Line", "EditBoxFill", "EditBoxLine", "EditCircleFill", "EditCircleLine", "EditFill", "EditLine", "EjectFill", "EjectLine", "EmojiStickerFill", "EmojiStickerLine", "Emotion2Fill", "Emotion2Line", "EmotionAddFill", "EmotionAddLine", "EmotionFill", "EmotionHappyFill", "EmotionHappyLine", "EmotionLaughFill", "EmotionLaughLine", "EmotionLine", "EmotionNormalFill", "EmotionNormalLine", "EmotionSadFill", "EmotionSadLine", "EmotionUnhappyFill", "EmotionUnhappyLine", "EmpathizeFill", "EmpathizeLine", "Emphasis", "EmphasisCn", "EnglishInput", "EqualFill", "EqualLine", "Equalizer2Fill", "Equalizer2Line", "Equalizer3Fill", "Equalizer3Line", "EqualizerFill", "EqualizerLine", "EraserFill", "EraserLine", "ErrorWarningFill", "ErrorWarningLine", "EthFill", "EthLine", "EvernoteFill", "EvernoteLine", "Exchange2Fill", "Exchange2Line", "ExchangeBoxFill", "ExchangeBoxLine", "ExchangeCnyFill", "ExchangeCnyLine", "ExchangeDollarFill", "ExchangeDollarLine", "ExchangeFill", "ExchangeFundsFill", "ExchangeFundsLine", "ExchangeLine", "ExpandDiagonal2Fill", "ExpandDiagonal2Line", "ExpandDiagonalFill", "ExpandDiagonalLine", "ExpandDiagonalS2Fill", "ExpandDiagonalS2Line", "ExpandDiagonalSFill", "ExpandDiagonalSLine", "ExpandHeightFill", "ExpandHeightLine", "ExpandHorizontalFill", "ExpandHorizontalLine", "ExpandHorizontalSFill", "ExpandHorizontalSLine", "ExpandLeftFill", "ExpandLeftLine", "ExpandLeftRightFill", "ExpandLeftRightLine", "ExpandRightFill", "ExpandRightLine", "ExpandUpDownFill", "ExpandUpDownLine", "ExpandVerticalFill", "ExpandVerticalLine", "ExpandVerticalSFill", "ExpandVerticalSLine", "ExpandWidthFill", "ExpandWidthLine", "ExportFill", "ExportLine", "ExternalLinkFill", "ExternalLinkLine", "Eye2Fill", "Eye2Line", "EyeCloseFill", "EyeCloseLine", "EyeFill", "EyeLine", "EyeOffFill", "EyeOffLine", "FacebookBoxFill", "FacebookBoxLine", "FacebookCircleFill", "FacebookCircleLine", "FacebookFill", "FacebookLine", "FahrenheitFill", "FahrenheitLine", "FediverseFill", "FediverseLine", "FeedbackFill", "FeedbackLine", "FigmaFill", "FigmaLine", "File2Fill", "File2Line", "File3Fill", "File3Line", "File4Fill", "File4Line", "FileAddFill", "FileAddLine", "FileAi2Fill", "FileAi2Line", "FileAiFill", "FileAiLine", "FileChart2Fill", "FileChart2Line", "FileChartFill", "FileChartLine", "FileCheckFill", "FileCheckLine", "FileCloseFill", "FileCloseLine", "FileCloudFill", "FileCloudLine", "FileCodeFill", "FileCodeLine", "FileCopy2Fill", "FileCopy2Line", "FileCopyFill", "FileCopyLine", "FileDamageFill", "FileDamageLine", "FileDownloadFill", "FileDownloadLine", "FileEditFill", "FileEditLine", "FileExcel2Fill", "FileExcel2Line", "FileExcelFill", "FileExcelLine", "FileFill", "FileForbidFill", "FileForbidLine", "FileGifFill", "FileGifLine", "FileHistoryFill", "FileHistoryLine", "FileHwpFill", "FileHwpLine", "FileImageFill", "FileImageLine", "FileInfoFill", "FileInfoLine", "FileLine", "FileList2Fill", "FileList2Line", "FileList3Fill", "FileList3Line", "FileListFill", "FileListLine", "FileLockFill", "FileLockLine", "FileMarkedFill", "FileMarkedLine", "FileMusicFill", "FileMusicLine", "FilePaper2Fill", "FilePaper2Line", "FilePaperFill", "FilePaperLine", "FilePdf2Fill", "FilePdf2Line", "FilePdfFill", "FilePdfLine", "FilePpt2Fill", "FilePpt2Line", "FilePptFill", "FilePptLine", "FileReduceFill", "FileReduceLine", "FileScanFill", "FileScanLine", "FileSearchFill", "FileSearchLine", "FileSettingsFill", "FileSettingsLine", "FileShield2Fill", "FileShield2Line", "FileShieldFill", "FileShieldLine", "FileShredFill", "FileShredLine", "FileTextFill", "FileTextLine", "FileTransferFill", "FileTransferLine", "FileUnknowFill", "FileUnknowLine", "FileUploadFill", "FileUploadLine", "FileUserFill", "FileUserLine", "FileVideoFill", "FileVideoLine", "FileWarningFill", "FileWarningLine", "FileWord2Fill", "FileWord2Line", "FileWordFill", "FileWordLine", "FileZipFill", "FileZipLine", "FilmAiFill", "FilmAiLine", "FilmFill", "FilmLine", "Filter2Fill", "Filter2Line", "Filter3Fill", "Filter3Line", "FilterFill", "FilterLine", "FilterOffFill", "FilterOffLine", "FindReplaceFill", "FindReplaceLine", "FinderFill", "FinderLine", "Fingerprint2Fill", "Fingerprint2Line", "FingerprintFill", "FingerprintLine", "FireFill", "FireLine", "FirebaseFill", "FirebaseLine", "FirefoxBrowserFill", "FirefoxBrowserLine", "FirefoxFill", "FirefoxLine", "FirstAidKitFill", "FirstAidKitLine", "FiverrFill", "FiverrLine", "Flag2Fill", "Flag2Line", "FlagFill", "FlagLine", "FlagOffFill", "FlagOffLine", "FlashlightFill", "FlashlightLine", "FlaskFill", "FlaskLine", "FlickrFill", "FlickrLine", "FlightLandFill", "FlightLandLine", "FlightTakeoffFill", "FlightTakeoffLine", "FlipHorizontal2Fill", "FlipHorizontal2Line", "FlipHorizontalFill", "FlipHorizontalLine", "FlipVertical2Fill", "FlipVertical2Line", "FlipVerticalFill", "FlipVerticalLine", "FloodFill", "FloodLine", "FlowChart", "FlowerFill", "FlowerLine", "FlutterFill", "FlutterLine", "Focus2Fill", "Focus2Line", "Focus3Fill", "Focus3Line", "FocusFill", "FocusLine", "FocusMode", "FoggyFill", "FoggyLine", "Folder2Fill", "Folder2Line", "Folder3Fill", "Folder3Line", "Folder4Fill", "Folder4Line", "Folder5Fill", "Folder5Line", "Folder6Fill", "Folder6Line", "FolderAddFill", "FolderAddLine", "FolderChart2Fill", "FolderChart2Line", "FolderChartFill", "FolderChartLine", "FolderCheckFill", "FolderCheckLine", "FolderCloseFill", "FolderCloseLine", "FolderCloudFill", "FolderCloudLine", "FolderDownloadFill", "FolderDownloadLine", "FolderFill", "FolderForbidFill", "FolderForbidLine", "FolderHistoryFill", "FolderHistoryLine", "FolderImageFill", "FolderImageLine", "FolderInfoFill", "FolderInfoLine", "FolderKeyholeFill", "FolderKeyholeLine", "FolderLine", "FolderLockFill", "FolderLockLine", "FolderMusicFill", "FolderMusicLine", "FolderOpenFill", "FolderOpenLine", "FolderReceivedFill", "FolderReceivedLine", "FolderReduceFill", "FolderReduceLine", "FolderSettingsFill", "FolderSettingsLine", "FolderSharedFill", "FolderSharedLine", "FolderShield2Fill", "FolderShield2Line", "FolderShieldFill", "FolderShieldLine", "FolderTransferFill", "FolderTransferLine", "FolderUnknowFill", "FolderUnknowLine", "FolderUploadFill", "FolderUploadLine", "FolderUserFill", "FolderUserLine", "FolderVideoFill", "FolderVideoLine", "FolderWarningFill", "FolderWarningLine", "FolderZipFill", "FolderZipLine", "FoldersFill", "FoldersLine", "FontColor", "FontFamily", "FontMono", "FontSansSerif", "FontSerif", "FontSize", "FontSize2", "FontSizeAi", "FootballFill", "FootballLine", "FootprintFill", "FootprintLine", "Forbid2Fill", "Forbid2Line", "ForbidFill", "ForbidLine", "FormatClear", "Formula", "Forward10Fill", "Forward10Line", "Forward15Fill", "Forward15Line", "Forward30Fill", "Forward30Line", "Forward5Fill", "Forward5Line", "ForwardEndFill", "ForwardEndLine", "ForwardEndMiniFill", "ForwardEndMiniLine", "FridgeFill", "FridgeLine", "FriendicaFill", "FriendicaLine", "FullscreenExitFill", "FullscreenExitLine", "FullscreenFill", "FullscreenLine", "FunctionAddFill", "FunctionAddLine", "FunctionAiFill", "FunctionAiLine", "FunctionFill", "FunctionLine", "Functions", "FundsBoxFill", "FundsBoxLine", "FundsFill", "FundsLine", "GalleryFill", "GalleryLine", "GalleryUploadFill", "GalleryUploadLine", "GalleryView", "GalleryView2", "Game2Fill", "Game2Line", "GameFill", "GameLine", "GamepadFill", "GamepadLine", "GasStationFill", "GasStationLine", "GatsbyFill", "GatsbyLine", "GeminiFill", "GeminiLine", "GenderlessFill", "GenderlessLine", "Ghost2Fill", "Ghost2Line", "Ghost3Fill", "Ghost3Line", "Ghost4Fill", "Ghost4Line", "GhostFill", "GhostLine", "GhostSmileFill", "GhostSmileLine", "Gift2Fill", "Gift2Line", "GiftFill", "GiftLine", "GitBranchFill", "GitBranchLine", "GitClosePullRequestFill", "GitClosePullRequestLine", "GitCommitFill", "GitCommitLine", "GitForkFill", "GitForkLine", "GitMergeFill", "GitMergeLine", "GitPrDraftFill", "GitPrDraftLine", "GitPullRequestFill", "GitPullRequestLine", "GitRepositoryCommitsFill", "GitRepositoryCommitsLine", "GitRepositoryFill", "GitRepositoryLine", "GitRepositoryPrivateFill", "GitRepositoryPrivateLine", "GitbookFill", "GitbookLine", "GiteeFill", "GiteeLine", "GithubFill", "GithubLine", "GitlabFill", "GitlabLine", "Glasses2Fill", "Glasses2Line", "GlassesFill", "GlassesLine", "GlobalFill", "GlobalLine", "GlobalOffFill", "GlobalOffLine", "GlobeFill", "GlobeLine", "Goblet2Fill", "Goblet2Line", "GobletBrokenFill", "GobletBrokenLine", "GobletFill", "GobletLine", "GogglesFill", "GogglesLine", "GolfBallFill", "GolfBallLine", "GoogleFill", "GoogleLine", "GooglePlayFill", "GooglePlayLine", "GovernmentFill", "GovernmentLine", "GpsFill", "GpsLine", "GradienterFill", "GradienterLine", "GraduationCapFill", "GraduationCapLine", "GridFill", "GridLine", "GrokAiFill", "GrokAiLine", "Group2Fill", "Group2Line", "Group3Fill", "Group3Line", "GroupFill", "GroupLine", "GuideFill", "GuideLine", "H1", "H2", "H3", "H4", "H5", "H6", "HailFill", "HailLine", "HammerFill", "HammerLine", "Hand", "Hand2", "HandCoinFill", "HandCoinLine", "HandHeartFill", "HandHeartLine", "HandSanitizerFill", "HandSanitizerLine", "HandbagFill", "HandbagLine", "HardDrive2Fill", "HardDrive2Line", "HardDrive3Fill", "HardDrive3Line", "HardDriveFill", "HardDriveLine", "Hashtag", "Haze2Fill", "Haze2Line", "HazeFill", "HazeLine", "HdFill", "HdLine", "Heading", "HeadphoneFill", "HeadphoneLine", "HealthBookFill", "HealthBookLine", "Heart2Fill", "Heart2Line", "Heart3Fill", "Heart3Line", "HeartAdd2Fill", "HeartAdd2Line", "HeartAddFill", "HeartAddLine", "HeartFill", "HeartLine", "HeartPulseFill", "HeartPulseLine", "HeartsFill", "HeartsLine", "HeavyShowersFill", "HeavyShowersLine", "HexagonFill", "HexagonLine", "HistoryFill", "HistoryLine", "Home2Fill", "Home2Line", "Home3Fill", "Home3Line", "Home4Fill", "Home4Line", "Home5Fill", "Home5Line", "Home6Fill", "Home6Line", "Home7Fill", "Home7Line", "Home8Fill", "Home8Line", "Home9Fill", "Home9Line", "HomeFill", "HomeGearFill", "HomeGearLine", "HomeHeartFill", "HomeHeartLine", "HomeLine", "HomeOfficeFill", "HomeOfficeLine", "HomeSmile2Fill", "HomeSmile2Line", "HomeSmileFill", "HomeSmileLine", "HomeWifiFill", "HomeWifiLine", "HonorOfKingsFill", "HonorOfKingsLine", "HonourFill", "HonourLine", "HospitalFill", "HospitalLine", "HotelBedFill", "HotelBedLine", "HotelFill", "HotelLine", "HotspotFill", "HotspotLine", "Hourglass2Fill", "Hourglass2Line", "HourglassFill", "HourglassLine", "HqFill", "HqLine", "Html5Fill", "Html5Line", "IdCardFill", "IdCardLine", "IeFill", "IeLine", "Image2Fill", "Image2Line", "ImageAddFill", "ImageAddLine", "ImageAiFill", "ImageAiLine", "ImageCircleAiFill", "ImageCircleAiLine", "ImageCircleFill", "ImageCircleLine", "ImageDownloadFill", "ImageDownloadLine", "ImageEditFill", "ImageEditLine", "ImageFill", "ImageLine", "ImageUploadFill", "ImageUploadLine", "ImportFill", "ImportLine", "Inbox2Fill", "Inbox2Line", "InboxArchiveFill", "InboxArchiveLine", "InboxFill", "InboxLine", "InboxUnarchiveFill", "InboxUnarchiveLine", "IncreaseDecreaseFill", "IncreaseDecreaseLine", "IndentDecrease", "IndentIncrease", "IndeterminateCircleFill", "IndeterminateCircleLine", "InfinityFill", "InfinityLine", "InfoCardFill", "InfoCardLine", "InfoI", "Information2Fill", "Information2Line", "InformationFill", "InformationLine", "InformationOffFill", "InformationOffLine", "InfraredThermometerFill", "InfraredThermometerLine", "InkBottleFill", "InkBottleLine", "InputCursorMove", "InputField", "InputMethodFill", "InputMethodLine", "InsertColumnLeft", "InsertColumnRight", "InsertRowBottom", "InsertRowTop", "InstagramFill", "InstagramLine", "InstallFill", "InstallLine", "InstanceFill", "InstanceLine", "InvisionFill", "InvisionLine", "IssuesFill", "IssuesLine", "IssuesReopenFill", "IssuesReopenLine", "Italic", "JavaFill", "JavaLine", "JavascriptFill", "JavascriptLine", "JewelryFill", "JewelryLine", "KakaoTalkFill", "KakaoTalkLine", "KanbanView", "KanbanView2", "Key2Fill", "Key2Line", "KeyFill", "KeyLine", "KeyboardBoxFill", "KeyboardBoxLine", "KeyboardFill", "KeyboardLine", "KeynoteFill", "KeynoteLine", "KickFill", "KickLine", "KnifeBloodFill", "KnifeBloodLine", "KnifeFill", "KnifeLine", "LandscapeAiFill", "LandscapeAiLine", "LandscapeFill", "LandscapeLine", "Layout2Fill", "Layout2Line", "Layout3Fill", "Layout3Line", "Layout4Fill", "Layout4Line", "Layout5Fill", "Layout5Line", "Layout6Fill", "Layout6Line", "LayoutBottom2Fill", "LayoutBottom2Line", "LayoutBottomFill", "LayoutBottomLine", "LayoutColumnFill", "LayoutColumnLine", "LayoutFill", "LayoutGrid2Fill", "LayoutGrid2Line", "LayoutGridFill", "LayoutGridLine", "LayoutHorizontalFill", "LayoutHorizontalLine", "LayoutLeft2Fill", "LayoutLeft2Line", "LayoutLeftFill", "LayoutLeftLine", "LayoutLine", "LayoutMasonryFill", "LayoutMasonryLine", "LayoutRight2Fill", "LayoutRight2Line", "LayoutRightFill", "LayoutRightLine", "LayoutRowFill", "LayoutRowLine", "LayoutTop2Fill", "LayoutTop2Line", "LayoutTopFill", "LayoutTopLine", "LayoutVerticalFill", "LayoutVerticalLine", "LeafFill", "LeafLine", "LetterSpacing2", "LifebuoyFill", "LifebuoyLine", "LightbulbAiFill", "LightbulbAiLine", "LightbulbFill", "LightbulbFlashFill", "LightbulbFlashLine", "LightbulbLine", "LineChartFill", "LineChartLine", "LineFill", "LineHeight", "LineHeight2", "LineLine", "Link", "LinkM", "LinkUnlink", "LinkUnlinkM", "LinkedinBoxFill", "LinkedinBoxLine", "LinkedinFill", "LinkedinLine", "LinksFill", "LinksLine", "ListCheck", "ListCheck2", "ListCheck3", "ListIndefinite", "ListOrdered", "ListOrdered2", "ListRadio", "ListSettingsFill", "ListSettingsLine", "ListUnordered", "ListView", "LiveFill", "LiveLine", "Loader2Fill", "Loader2Line", "Loader3Fill", "Loader3Line", "Loader4Fill", "Loader4Line", "Loader5Fill", "Loader5Line", "LoaderFill", "LoaderLine", "Lock2Fill", "Lock2Line", "LockFill", "LockLine", "LockPasswordFill", "LockPasswordLine", "LockStarFill", "LockStarLine", "LockUnlockFill", "LockUnlockLine", "LoginBoxFill", "LoginBoxLine", "LoginCircleFill", "LoginCircleLine", "LogoutBoxFill", "LogoutBoxLine", "LogoutBoxRFill", "LogoutBoxRLine", "LogoutCircleFill", "LogoutCircleLine", "LogoutCircleRFill", "LogoutCircleRLine", "LoopLeftAiFill", "LoopLeftAiLine", "LoopLeftFill", "LoopLeftLine", "LoopRightAiFill", "LoopRightAiLine", "LoopRightFill", "LoopRightLine", "LuggageCartFill", "LuggageCartLine", "LuggageDepositFill", "LuggageDepositLine", "LungsFill", "LungsLine", "MacFill", "MacLine", "MacbookFill", "MacbookLine", "MagicFill", "MagicLine", "MailAddFill", "MailAddLine", "MailAiFill", "MailAiLine", "MailCheckFill", "MailCheckLine", "MailCloseFill", "MailCloseLine", "MailDownloadFill", "MailDownloadLine", "MailFill", "MailForbidFill", "MailForbidLine", "MailLine", "MailLockFill", "MailLockLine", "MailOpenFill", "MailOpenLine", "MailSendFill", "MailSendLine", "MailSettingsFill", "MailSettingsLine", "MailStarFill", "MailStarLine", "MailUnreadFill", "MailUnreadLine", "MailVolumeFill", "MailVolumeLine", "Map2Fill", "Map2Line", "MapFill", "MapLine", "MapPin2Fill", "MapPin2Line", "MapPin3Fill", "MapPin3Line", "MapPin4Fill", "MapPin4Line", "MapPin5Fill", "MapPin5Line", "MapPinAddFill", "MapPinAddLine", "MapPinFill", "MapPinLine", "MapPinRangeFill", "MapPinRangeLine", "MapPinTimeFill", "MapPinTimeLine", "MapPinUserFill", "MapPinUserLine", "MarkPenFill", "MarkPenLine", "MarkdownFill", "MarkdownLine", "MarkupFill", "MarkupLine", "MastercardFill", "MastercardLine", "MastodonFill", "MastodonLine", "Medal2Fill", "Medal2Line", "MedalFill", "MedalLine", "MedicineBottleFill", "MedicineBottleLine", "MediumFill", "MediumLine", "Megaphone2Fill", "Megaphone2Line", "MegaphoneFill", "MegaphoneLine", "MemoriesFill", "MemoriesLine", "MenFill", "MenLine", "MentalHealthFill", "MentalHealthLine", "Menu2Fill", "Menu2Line", "Menu3Fill", "Menu3Line", "Menu4Fill", "Menu4Line", "Menu5Fill", "Menu5Line", "MenuAddFill", "MenuAddLine", "MenuFill", "MenuFold2Fill", "MenuFold2Line", "MenuFold3Fill", "MenuFold3Line", "MenuFold4Fill", "MenuFold4Line", "MenuFoldFill", "MenuFoldLine", "MenuLine", "MenuSearchFill", "MenuSearchLine", "MenuUnfold2Fill", "MenuUnfold2Line", "MenuUnfold3Fill", "MenuUnfold3Line", "MenuUnfold4Fill", "MenuUnfold4Line", "MenuUnfoldFill", "MenuUnfoldLine", "MergeCellsHorizontal", "MergeCellsVertical", "Message2Fill", "Message2Line", "Message3Fill", "Message3Line", "MessageAi3Fill", "MessageAi3Line", "MessageFill", "MessageLine", "MessengerFill", "MessengerLine", "MetaFill", "MetaLine", "MeteorFill", "MeteorLine", "Mic2AiFill", "Mic2AiLine", "Mic2Fill", "Mic2Line", "MicAiFill", "MicAiLine", "MicFill", "MicLine", "MicOffFill", "MicOffLine", "MickeyFill", "MickeyLine", "MicroscopeFill", "MicroscopeLine", "MicrosoftCopilotFill", "MicrosoftCopilotLine", "MicrosoftFill", "MicrosoftLine", "MicrosoftLoopFill", "MicrosoftLoopLine", "MindMap", "MiniProgramFill", "MiniProgramLine", "MistFill", "MistLine", "MixtralFill", "MixtralLine", "MobileDownloadFill", "MobileDownloadLine", "MoneyCnyBoxFill", "MoneyCnyBoxLine", "MoneyCnyCircleFill", "MoneyCnyCircleLine", "MoneyDollarBoxFill", "MoneyDollarBoxLine", "MoneyDollarCircleFill", "MoneyDollarCircleLine", "MoneyEuroBoxFill", "MoneyEuroBoxLine", "MoneyEuroCircleFill", "MoneyEuroCircleLine", "MoneyPoundBoxFill", "MoneyPoundBoxLine", "MoneyPoundCircleFill", "MoneyPoundCircleLine", "MoneyRupeeCircleFill", "MoneyRupeeCircleLine", "MoonClearFill", "MoonClearLine", "MoonCloudyFill", "MoonCloudyLine", "MoonFill", "MoonFoggyFill", "MoonFoggyLine", "MoonLine", "More2Fill", "More2Line", "MoreFill", "MoreLine", "MosaicFill", "MosaicLine", "MotorbikeFill", "MotorbikeLine", "MouseFill", "MouseLine", "Movie2AiFill", "Movie2AiLine", "Movie2Fill", "Movie2Line", "MovieAiFill", "MovieAiLine", "MovieFill", "MovieLine", "MultiImageFill", "MultiImageLine", "Music2Fill", "Music2Line", "MusicAiFill", "MusicAiLine", "MusicFill", "MusicLine", "MvAiFill", "MvAiLine", "MvFill", "MvLine", "NavigationFill", "NavigationLine", "NeteaseCloudMusicFill", "NeteaseCloudMusicLine", "NetflixFill", "NetflixLine", "NetworkErrorFill", "NetworkErrorLine", "NetworkFill", "NetworkLine", "NetworkOffFill", "NetworkOffLine", "NewsFill", "NewsLine", "NewspaperFill", "NewspaperLine", "NextjsFill", "NextjsLine", "NftFill", "NftLine", "NoCreditCardFill", "NoCreditCardLine", "NodeTree", "NodejsFill", "NodejsLine", "Notification2Fill", "Notification2Line", "Notification3Fill", "Notification3Line", "Notification4Fill", "Notification4Line", "NotificationBadgeFill", "NotificationBadgeLine", "NotificationFill", "NotificationLine", "NotificationOffFill", "NotificationOffLine", "NotificationSnoozeFill", "NotificationSnoozeLine", "NotionFill", "NotionLine", "NpmjsFill", "NpmjsLine", "Number0", "Number1", "Number2", "Number3", "Number4", "Number5", "Number6", "Number7", "Number8", "Number9", "NumbersFill", "NumbersLine", "NurseFill", "NurseLine", "OctagonFill", "OctagonLine", "OilFill", "OilLine", "Omega", "OpenArmFill", "OpenArmLine", "OpenSourceFill", "OpenSourceLine", "OpenaiFill", "OpenaiLine", "OpenbaseFill", "OpenbaseLine", "OperaFill", "OperaLine", "OrderPlayFill", "OrderPlayLine", "OrganizationChart", "Outlet2Fill", "Outlet2Line", "OutletFill", "OutletLine", "Overline", "P2pFill", "P2pLine", "PageSeparator", "PagesFill", "PagesLine", "PaintBrushFill", "PaintBrushLine", "PaintFill", "PaintLine", "PaintingAiFill", "PaintingAiLine", "PaintingFill", "PaintingLine", "PaletteFill", "PaletteLine", "PantoneFill", "PantoneLine", "Paragraph", "ParentFill", "ParentLine", "ParenthesesFill", "ParenthesesLine", "ParkingBoxFill", "ParkingBoxLine", "ParkingFill", "ParkingLine", "PassExpiredFill", "PassExpiredLine", "PassPendingFill", "PassPendingLine", "PassValidFill", "PassValidLine", "PassportFill", "PassportLine", "PatreonFill", "PatreonLine", "PauseCircleFill", "PauseCircleLine", "PauseFill", "PauseLargeFill", "PauseLargeLine", "PauseLine", "PauseMiniFill", "PauseMiniLine", "PaypalFill", "PaypalLine", "PenNibFill", "PenNibLine", "PencilAi2Fill", "PencilAi2Line", "PencilAiFill", "PencilAiLine", "PencilFill", "PencilLine", "PencilRuler2Fill", "PencilRuler2Line", "PencilRulerFill", "PencilRulerLine", "PentagonFill", "PentagonLine", "PercentFill", "PercentLine", "PerplexityFill", "PerplexityLine", "PhoneCameraFill", "PhoneCameraLine", "PhoneFill", "PhoneFindFill", "PhoneFindLine", "PhoneLine", "PhoneLockFill", "PhoneLockLine", "PhpFill", "PhpLine", "PianoFill", "PianoGrandFill", "PianoGrandLine", "PianoLine", "PictureInPicture2Fill", "PictureInPicture2Line", "PictureInPictureExitFill", "PictureInPictureExitLine", "PictureInPictureFill", "PictureInPictureLine", "PieChart2Fill", "PieChart2Line", "PieChartBoxFill", "PieChartBoxLine", "PieChartFill", "PieChartLine", "PinDistanceFill", "PinDistanceLine", "PingPongFill", "PingPongLine", "PinterestFill", "PinterestLine", "PinyinInput", "PixFill", "PixLine", "PixelfedFill", "PixelfedLine", "PlaneFill", "PlaneLine", "PlanetFill", "PlanetLine", "PlantFill", "PlantLine", "PlayCircleFill", "PlayCircleLine", "PlayFill", "PlayLargeFill", "PlayLargeLine", "PlayLine", "PlayList2Fill", "PlayList2Line", "PlayListAddFill", "PlayListAddLine", "PlayListFill", "PlayListLine", "PlayMiniFill", "PlayMiniLine", "PlayReverseFill", "PlayReverseLargeFill", "PlayReverseLargeLine", "PlayReverseLine", "PlayReverseMiniFill", "PlayReverseMiniLine", "PlaystationFill", "PlaystationLine", "Plug2Fill", "Plug2Line", "Plug3Fill", "Plug3Line", "PlugFill", "PlugLine", "PokerClubsFill", "PokerClubsLine", "PokerDiamondsFill", "PokerDiamondsLine", "PokerHeartsFill", "PokerHeartsLine", "PokerSpadesFill", "PokerSpadesLine", "Polaroid2Fill", "Polaroid2Line", "PolaroidFill", "PolaroidLine", "PoliceBadgeFill", "PoliceBadgeLine", "PoliceCarFill", "PoliceCarLine", "PresentationFill", "PresentationLine", "PriceTag2Fill", "PriceTag2Line", "PriceTag3Fill", "PriceTag3Line", "PriceTagFill", "PriceTagLine", "PrinterCloudFill", "PrinterCloudLine", "PrinterFill", "PrinterLine", "ProductHuntFill", "ProductHuntLine", "ProfileFill", "ProfileLine", "Progress1Fill", "Progress1Line", "Progress2Fill", "Progress2Line", "Progress3Fill", "Progress3Line", "Progress4Fill", "Progress4Line", "Progress5Fill", "Progress5Line", "Progress6Fill", "Progress6Line", "Progress7Fill", "Progress7Line", "Progress8Fill", "Progress8Line", "Prohibited2Fill", "Prohibited2Line", "ProhibitedFill", "ProhibitedLine", "Projector2Fill", "Projector2Line", "ProjectorFill", "ProjectorLine", "PsychotherapyFill", "PsychotherapyLine", "PulseAiFill", "PulseAiLine", "PulseFill", "PulseLine", "Pushpin2Fill", "Pushpin2Line", "PushpinFill", "PushpinLine", "Puzzle2Fill", "Puzzle2Line", "PuzzleFill", "PuzzleLine", "QqFill", "QqLine", "QrCodeFill", "QrCodeLine", "QrScan2Fill", "QrScan2Line", "QrScanAiFill", "QrScanAiLine", "QrScanFill", "QrScanLine", "QuestionAnswerFill", "QuestionAnswerLine", "QuestionFill", "QuestionLine", "QuestionMark", "QuestionnaireFill", "QuestionnaireLine", "QuillPenAiFill", "QuillPenAiLine", "QuillPenFill", "QuillPenLine", "QuoteText", "QwenAiFill", "QwenAiLine", "RadarFill", "RadarLine", "Radio2Fill", "Radio2Line", "RadioButtonFill", "RadioButtonLine", "RadioFill", "RadioLine", "RainbowFill", "RainbowLine", "RainyFill", "RainyLine", "Ram2Fill", "Ram2Line", "RamFill", "RamLine", "ReactjsFill", "ReactjsLine", "ReceiptFill", "ReceiptLine", "RecordCircleFill", "RecordCircleLine", "RecordMailFill", "RecordMailLine", "RectangleFill", "RectangleLine", "RecycleFill", "RecycleLine", "RedPacketFill", "RedPacketLine", "Reddit2Fill", "Reddit2Line", "RedditFill", "RedditLine", "RefreshFill", "RefreshLine", "Refund2Fill", "Refund2Line", "RefundFill", "RefundLine", "RegisteredFill", "RegisteredLine", "RemixFill", "RemixLine", "RemixRunFill", "RemixRunLine", "RemixiconFill", "RemixiconLine", "RemoteControl2Fill", "RemoteControl2Line", "RemoteControlFill", "RemoteControlLine", "Repeat2Fill", "Repeat2Line", "RepeatFill", "RepeatLine", "RepeatOneFill", "RepeatOneLine", "Replay10Fill", "Replay10Line", "Replay15Fill", "Replay15Line", "Replay30Fill", "Replay30Line", "Replay5Fill", "Replay5Line", "ReplyAllFill", "ReplyAllLine", "ReplyFill", "ReplyLine", "ReservedFill", "ReservedLine", "ResetLeftFill", "ResetLeftLine", "ResetRightFill", "ResetRightLine", "RestTimeFill", "RestTimeLine", "RestartFill", "RestartLine", "Restaurant2Fill", "Restaurant2Line", "RestaurantFill", "RestaurantLine", "RewindFill", "RewindLine", "RewindMiniFill", "RewindMiniLine", "RewindStartFill", "RewindStartLine", "RewindStartMiniFill", "RewindStartMiniLine", "RfidFill", "RfidLine", "RhythmFill", "RhythmLine", "RidingFill", "RidingLine", "RoadMapFill", "RoadMapLine", "RoadsterFill", "RoadsterLine", "Robot2Fill", "Robot2Line", "Robot3Fill", "Robot3Line", "RobotFill", "RobotLine", "Rocket2Fill", "Rocket2Line", "RocketFill", "RocketLine", "RotateLockFill", "RotateLockLine", "RoundedCorner", "RouteFill", "RouteLine", "RouterFill", "RouterLine", "RssFill", "RssLine", "Ruler2Fill", "Ruler2Line", "RulerFill", "RulerLine", "RunFill", "RunLine", "SafariFill", "SafariLine", "Safe2Fill", "Safe2Line", "Safe3Fill", "Safe3Line", "SafeFill", "SafeLine", "SailboatFill", "SailboatLine", "Save2Fill", "Save2Line", "Save3Fill", "Save3Line", "SaveFill", "SaveLine", "Scales2Fill", "Scales2Line", "Scales3Fill", "Scales3Line", "ScalesFill", "ScalesLine", "Scan2Fill", "Scan2Line", "ScanFill", "ScanLine", "SchoolFill", "SchoolLine", "Scissors2Fill", "Scissors2Line", "ScissorsCutFill", "ScissorsCutLine", "ScissorsFill", "ScissorsLine", "Screenshot2Fill", "Screenshot2Line", "ScreenshotFill", "ScreenshotLine", "ScrollToBottomFill", "ScrollToBottomLine", "SdCardFill", "SdCardLine", "SdCardMiniFill", "SdCardMiniLine", "Search2Fill", "Search2Line", "SearchAi2Fill", "SearchAi2Line", "SearchAi3Fill", "SearchAi3Line", "SearchAi4Fill", "SearchAi4Line", "SearchAiFill", "SearchAiLine", "SearchEyeFill", "SearchEyeLine", "SearchFill", "SearchLine", "SecurePaymentFill", "SecurePaymentLine", "SeedlingFill", "SeedlingLine", "SendBackward", "SendInsFill", "SendInsLine", "SendPlane2Fill", "SendPlane2Line", "SendPlaneFill", "SendPlaneLine", "SendToBack", "SensorFill", "SensorLine", "SeoFill", "SeoLine", "Separator", "ServerFill", "ServerLine", "ServiceBellFill", "ServiceBellLine", "ServiceFill", "ServiceLine", "Settings2Fill", "Settings2Line", "Settings3Fill", "Settings3Line", "Settings4Fill", "Settings4Line", "Settings5Fill", "Settings5Line", "Settings6Fill", "Settings6Line", "SettingsFill", "SettingsLine", "ShadowFill", "ShadowLine", "ShakeHandsFill", "ShakeHandsLine", "Shape2Fill", "Shape2Line", "ShapeFill", "ShapeLine", "ShapesFill", "ShapesLine", "Share2Fill", "Share2Line", "ShareBoxFill", "ShareBoxLine", "ShareCircleFill", "ShareCircleLine", "ShareFill", "ShareForward2Fill", "ShareForward2Line", "ShareForwardBoxFill", "ShareForwardBoxLine", "ShareForwardFill", "ShareForwardLine", "ShareLine", "ShieldCheckFill", "ShieldCheckLine", "ShieldCrossFill", "ShieldCrossLine", "ShieldFill", "ShieldFlashFill", "ShieldFlashLine", "ShieldKeyholeFill", "ShieldKeyholeLine", "ShieldLine", "ShieldStarFill", "ShieldStarLine", "ShieldUserFill", "ShieldUserLine", "Shining2Fill", "Shining2Line", "ShiningFill", "ShiningLine", "Ship2Fill", "Ship2Line", "ShipFill", "ShipLine", "ShirtFill", "ShirtLine", "ShoppingBag2Fill", "ShoppingBag2Line", "ShoppingBag3Fill", "ShoppingBag3Line", "ShoppingBag4Fill", "ShoppingBag4Line", "ShoppingBagFill", "ShoppingBagLine", "ShoppingBasket2Fill", "ShoppingBasket2Line", "ShoppingBasketFill", "ShoppingBasketLine", "ShoppingCart2Fill", "ShoppingCart2Line", "ShoppingCartFill", "ShoppingCartLine", "ShowersFill", "ShowersLine", "ShuffleFill", "ShuffleLine", "ShutDownFill", "ShutDownLine", "SideBarFill", "SideBarLine", "SidebarFoldFill", "SidebarFoldLine", "SidebarUnfoldFill", "SidebarUnfoldLine", "SignalCellular1Fill", "SignalCellular1Line", "SignalCellular2Fill", "SignalCellular2Line", "SignalCellular3Fill", "SignalCellular3Line", "SignalCellularOffFill", "SignalCellularOffLine", "SignalTowerFill", "SignalTowerLine", "SignalWifi1Fill", "SignalWifi1Line", "SignalWifi2Fill", "SignalWifi2Line", "SignalWifi3Fill", "SignalWifi3Line", "SignalWifiErrorFill", "SignalWifiErrorLine", "SignalWifiFill", "SignalWifiLine", "SignalWifiOffFill", "SignalWifiOffLine", "SignpostFill", "SignpostLine", "SimCard2Fill", "SimCard2Line", "SimCardFill", "SimCardLine", "SimCardWarningFill", "SimCardWarningLine", "SingleQuotesL", "SingleQuotesR", "SipFill", "SipLine", "Sketching", "SkipBackFill", "SkipBackLine", "SkipBackMiniFill", "SkipBackMiniLine", "SkipDownFill", "SkipDownLine", "SkipForwardFill", "SkipForwardLine", "SkipForwardMiniFill", "SkipForwardMiniLine", "SkipLeftFill", "SkipLeftLine", "SkipRightFill", "SkipRightLine", "SkipUpFill", "SkipUpLine", "Skull2Fill", "Skull2Line", "SkullFill", "SkullLine", "SkypeFill", "SkypeLine", "SlackFill", "SlackLine", "SlashCommands", "SlashCommands2", "SliceFill", "SliceLine", "Slideshow2Fill", "Slideshow2Line", "Slideshow3Fill", "Slideshow3Line", "Slideshow4Fill", "Slideshow4Line", "SlideshowFill", "SlideshowLine", "SlideshowView", "SlowDownFill", "SlowDownLine", "SmartphoneFill", "SmartphoneLine", "SnapchatFill", "SnapchatLine", "SnowflakeFill", "SnowflakeLine", "SnowyFill", "SnowyLine", "SofaFill", "SofaLine", "SortAlphabetAsc", "SortAlphabetDesc", "SortAsc", "SortDesc", "SortNumberAsc", "SortNumberDesc", "SoundModuleFill", "SoundModuleLine", "SoundcloudFill", "SoundcloudLine", "Space", "SpaceShip2Fill", "SpaceShip2Line", "SpaceShipFill", "SpaceShipLine", "Spam2Fill", "Spam2Line", "Spam3Fill", "Spam3Line", "SpamFill", "SpamLine", "Sparkling2Fill", "Sparkling2Line", "SparklingFill", "SparklingLine", "SpeakAiFill", "SpeakAiLine", "SpeakFill", "SpeakLine", "Speaker2Fill", "Speaker2Line", "Speaker3Fill", "Speaker3Line", "SpeakerFill", "SpeakerLine", "SpectrumFill", "SpectrumLine", "SpeechToTextFill", "SpeechToTextLine", "SpeedFill", "SpeedLine", "SpeedMiniFill", "SpeedMiniLine", "SpeedUpFill", "SpeedUpLine", "SplitCellsHorizontal", "SplitCellsVertical", "SpotifyFill", "SpotifyLine", "SpyFill", "SpyLine", "SquareFill", "SquareLine", "SquareRoot", "StackFill", "StackLine", "StackOverflowFill", "StackOverflowLine", "StackedChartFill", "StackedChartLine", "StackedView", "StackshareFill", "StackshareLine", "StairsFill", "StairsLine", "StarFill", "StarHalfFill", "StarHalfLine", "StarHalfSFill", "StarHalfSLine", "StarLine", "StarOffFill", "StarOffLine", "StarSFill", "StarSLine", "StarSmileFill", "StarSmileLine", "SteamFill", "SteamLine", "Steering2Fill", "Steering2Line", "SteeringFill", "SteeringLine", "StethoscopeFill", "StethoscopeLine", "StickyNote2Fill", "StickyNote2Line", "StickyNoteAddFill", "StickyNoteAddLine", "StickyNoteFill", "StickyNoteLine", "StockFill", "StockLine", "StopCircleFill", "StopCircleLine", "StopFill", "StopLargeFill", "StopLargeLine", "StopLine", "StopMiniFill", "StopMiniLine", "Store2Fill", "Store2Line", "Store3Fill", "Store3Line", "StoreFill", "StoreLine", "Strikethrough", "Strikethrough2", "SubredditFill", "SubredditLine", "Subscript", "Subscript2", "SubtractFill", "SubtractLine", "SubwayFill", "SubwayLine", "SubwayWifiFill", "SubwayWifiLine", "Suitcase2Fill", "Suitcase2Line", "Suitcase3Fill", "Suitcase3Line", "SuitcaseFill", "SuitcaseLine", "SunCloudyFill", "SunCloudyLine", "SunFill", "SunFoggyFill", "SunFoggyLine", "SunLine", "SupabaseFill", "SupabaseLine", "Superscript", "Superscript2", "SurgicalMaskFill", "SurgicalMaskLine", "SurroundSoundFill", "SurroundSoundLine", "SurveyFill", "SurveyLine", "SvelteFill", "SvelteLine", "Swap2Fill", "Swap2Line", "Swap3Fill", "Swap3Line", "SwapBoxFill", "SwapBoxLine", "SwapFill", "SwapLine", "SwitchFill", "SwitchLine", "SwordFill", "SwordLine", "SyringeFill", "SyringeLine", "TBoxFill", "TBoxLine", "TShirt2Fill", "TShirt2Line", "TShirtAirFill", "TShirtAirLine", "TShirtFill", "TShirtLine", "Table2", "Table3", "TableAltFill", "TableAltLine", "TableFill", "TableLine", "TableView", "TabletFill", "TabletLine", "TailwindCssFill", "TailwindCssLine", "TakeawayFill", "TakeawayLine", "TaobaoFill", "TaobaoLine", "TapeFill", "TapeLine", "TargetFill", "TargetLine", "TaskFill", "TaskLine", "TaxiFill", "TaxiLine", "TaxiWifiFill", "TaxiWifiLine", "TeamFill", "TeamLine", "Telegram2Fill", "Telegram2Line", "TelegramFill", "TelegramLine", "TempColdFill", "TempColdLine", "TempHotFill", "TempHotLine", "TentFill", "TentLine", "TerminalBoxFill", "TerminalBoxLine", "TerminalFill", "TerminalLine", "TerminalWindowFill", "TerminalWindowLine", "TestTubeFill", "TestTubeLine", "Text", "TextBlock", "TextDirectionL", "TextDirectionR", "TextSnippet", "TextSpacing", "TextToSpeechFill", "TextToSpeechLine", "TextWrap", "ThermometerFill", "ThermometerLine", "ThreadsFill", "ThreadsLine", "ThumbDownFill", "ThumbDownLine", "ThumbUpFill", "ThumbUpLine", "ThunderstormsFill", "ThunderstormsLine", "Ticket2Fill", "Ticket2Line", "TicketFill", "TicketLine", "TiktokFill", "TiktokLine", "TimeFill", "TimeLine", "TimeZoneFill", "TimeZoneLine", "TimelineView", "Timer2Fill", "Timer2Line", "TimerFill", "TimerFlashFill", "TimerFlashLine", "TimerLine", "TodoFill", "TodoLine", "ToggleFill", "ToggleLine", "TokenSwapFill", "TokenSwapLine", "ToolsFill", "ToolsLine", "ToothFill", "ToothLine", "TornadoFill", "TornadoLine", "TrademarkFill", "TrademarkLine", "TrafficLightFill", "TrafficLightLine", "TrainFill", "TrainLine", "TrainWifiFill", "TrainWifiLine", "Translate", "Translate2", "TranslateAi", "TranslateAi2", "TravestiFill", "TravestiLine", "TreasureMapFill", "TreasureMapLine", "TreeFill", "TreeLine", "TrelloFill", "TrelloLine", "TriangleFill", "TriangleLine", "TriangularFlagFill", "TriangularFlagLine", "TrophyFill", "TrophyLine", "TruckFill", "TruckLine", "TumblrFill", "TumblrLine", "Tv2Fill", "Tv2Line", "TvFill", "TvLine", "TwitchFill", "TwitchLine", "TwitterFill", "TwitterLine", "TwitterXFill", "TwitterXLine", "TyphoonFill", "TyphoonLine", "UDiskFill", "UDiskLine", "UbuntuFill", "UbuntuLine", "UmbrellaFill", "UmbrellaLine", "Underline", "UninstallFill", "UninstallLine", "UnpinFill", "UnpinLine", "UnsplashFill", "UnsplashLine", "Upload2Fill", "Upload2Line", "UploadCloud2Fill", "UploadCloud2Line", "UploadCloudFill", "UploadCloudLine", "UploadFill", "UploadLine", "UpworkFill", "UpworkLine", "UsbFill", "UsbLine", "User2Fill", "User2Line", "User3Fill", "User3Line", "User4Fill", "User4Line", "User5Fill", "User5Line", "User6Fill", "User6Line", "UserAddFill", "UserAddLine", "UserCommunityFill", "UserCommunityLine", "UserFill", "UserFollowFill", "UserFollowLine", "UserForbidFill", "UserForbidLine", "UserHeartFill", "UserHeartLine", "UserLine", "UserLocationFill", "UserLocationLine", "UserMinusFill", "UserMinusLine", "UserReceived2Fill", "UserReceived2Line", "UserReceivedFill", "UserReceivedLine", "UserSearchFill", "UserSearchLine", "UserSettingsFill", "UserSettingsLine", "UserShared2Fill", "UserShared2Line", "UserSharedFill", "UserSharedLine", "UserSmileFill", "UserSmileLine", "UserStarFill", "UserStarLine", "UserUnfollowFill", "UserUnfollowLine", "UserVoiceFill", "UserVoiceLine", "VercelFill", "VercelLine", "VerifiedBadgeFill", "VerifiedBadgeLine", "VideoAddFill", "VideoAddLine", "VideoAiFill", "VideoAiLine", "VideoChatFill", "VideoChatLine", "VideoDownloadFill", "VideoDownloadLine", "VideoFill", "VideoLine", "VideoOffFill", "VideoOffLine", "VideoOnAiFill", "VideoOnAiLine", "VideoOnFill", "VideoOnLine", "VideoUploadFill", "VideoUploadLine", "Vidicon2Fill", "Vidicon2Line", "VidiconFill", "VidiconLine", "VimeoFill", "VimeoLine", "VipCrown2Fill", "VipCrown2Line", "VipCrownFill", "VipCrownLine", "VipDiamondFill", "VipDiamondLine", "VipFill", "VipLine", "VirusFill", "VirusLine", "VisaFill", "VisaLine", "VkFill", "VkLine", "VoiceAiFill", "VoiceAiLine", "VoiceRecognitionFill", "VoiceRecognitionLine", "VoiceprintFill", "VoiceprintLine", "VolumeDownFill", "VolumeDownLine", "VolumeMuteFill", "VolumeMuteLine", "VolumeOffVibrateFill", "VolumeOffVibrateLine", "VolumeUpFill", "VolumeUpLine", "VolumeVibrateFill", "VolumeVibrateLine", "VuejsFill", "VuejsLine", "WalkFill", "WalkLine", "Wallet2Fill", "Wallet2Line", "Wallet3Fill", "Wallet3Line", "WalletFill", "WalletLine", "WaterFlashFill", "WaterFlashLine", "WaterPercentFill", "WaterPercentLine", "WebcamFill", "WebcamLine", "WebhookFill", "WebhookLine", "Wechat2Fill", "Wechat2Line", "WechatChannelsFill", "WechatChannelsLine", "WechatFill", "WechatLine", "WechatPayFill", "WechatPayLine", "WeiboFill", "WeiboLine", "WeightFill", "WeightLine", "WhatsappFill", "WhatsappLine", "WheelchairFill", "WheelchairLine", "WifiFill", "WifiLine", "WifiOffFill", "WifiOffLine", "Window2Fill", "Window2Line", "WindowFill", "WindowLine", "WindowsFill", "WindowsLine", "WindyFill", "WindyLine", "WirelessChargingFill", "WirelessChargingLine", "WomenFill", "WomenLine", "WordpressFill", "WordpressLine", "WrenchFill", "WrenchLine", "WubiInput", "XboxFill", "XboxLine", "XingFill", "XingLine", "XrpFill", "XrpLine", "XtzFill", "XtzLine", "YoutubeFill", "YoutubeLine", "YuqueFill", "YuqueLine", "ZcoolFill", "ZcoolLine", "ZhihuFill", "ZhihuLine", "ZhipuAiFill", "ZhipuAiLine", "ZoomInFill", "ZoomInLine", "ZoomOutFill", "ZoomOutLine", "ZzzFill", "ZzzLine"]);
18
+ /**
19
+ * Which keys hold an icon name, per element type — read off every `IconPicker`
20
+ * the editor's inspector renders (`editor/src/trait/widgets.ts` and the rows it
21
+ * composes). A value outside `ICON_NAMES` written to one of these renders the
22
+ * default star or nothing (`RenderIconSVG` / `HasIcon`), never the icon asked
23
+ * for; `allowNone` marks a key whose picker offers "None" (`''`).
24
+ */
25
+ export const ICON_KEYS = {
26
+ "breadcrumb": [
27
+ {
28
+ "ns": "specials",
29
+ "key": "icon",
30
+ "allowNone": true
31
+ }
32
+ ],
33
+ "button": [
34
+ {
35
+ "ns": "specials",
36
+ "key": "icon"
37
+ }
38
+ ],
39
+ "chat-widget": [
40
+ {
41
+ "ns": "specials",
42
+ "key": "launcherIcon",
43
+ "allowNone": true
44
+ }
45
+ ],
46
+ "divider": [
47
+ {
48
+ "ns": "specials",
49
+ "key": "iconName"
50
+ }
51
+ ],
52
+ "form-calendar": [
53
+ {
54
+ "ns": "specials",
55
+ "key": "iconName"
56
+ }
57
+ ],
58
+ "form-number": [
59
+ {
60
+ "ns": "specials",
61
+ "key": "iconName"
62
+ }
63
+ ],
64
+ "form-step-button": [
65
+ {
66
+ "ns": "specials",
67
+ "key": "icon"
68
+ }
69
+ ],
70
+ "form-submit": [
71
+ {
72
+ "ns": "specials",
73
+ "key": "icon"
74
+ }
75
+ ],
76
+ "form-text": [
77
+ {
78
+ "ns": "specials",
79
+ "key": "iconName"
80
+ }
81
+ ],
82
+ "icon": [
83
+ {
84
+ "ns": "specials",
85
+ "key": "name"
86
+ }
87
+ ],
88
+ "list-dataset": [
89
+ {
90
+ "ns": "config",
91
+ "key": "listNavIcon",
92
+ "allowNone": true
93
+ }
94
+ ],
95
+ "list-item": [
96
+ {
97
+ "ns": "specials",
98
+ "key": "icon",
99
+ "allowNone": true
100
+ }
101
+ ],
102
+ "popup": [
103
+ {
104
+ "ns": "specials",
105
+ "key": "closeIcon",
106
+ "allowNone": true
107
+ }
108
+ ],
109
+ "text-marquee-item": [
110
+ {
111
+ "ns": "specials",
112
+ "key": "icon",
113
+ "allowNone": true
114
+ }
115
+ ]
116
+ };
@@ -536,6 +536,18 @@ function rebindPatch(doc, id, keys) {
536
536
  * (not part of this document at all).
537
537
  */
538
538
  export function duplicateNode(doc, id, guard) {
539
+ const parentId = doc.has(id) ? doc.node(id).data.parent : null;
540
+ const at = parentId && doc.has(parentId) ? doc.node(parentId).data.nodes.indexOf(id) : -1;
541
+ return copyNodeInto(doc, id, doc, parentId ?? '', at < 0 ? APPEND : at + 1, guard);
542
+ }
543
+ /**
544
+ * Copy `id` and everything under it — satellites included — out of `src` into
545
+ * `target` under `parentId` at `index`, under fresh ids. `src === target` is a
546
+ * duplicate; a different document is the editor's paste onto another page. The
547
+ * patches address `target` only, and read `src` only.
548
+ */
549
+ export function copyNodeInto(src, id, target, parentId, index, guard) {
550
+ const doc = src;
539
551
  const n = doc.node(id);
540
552
  if (id === doc.doc.root_node_id)
541
553
  throw new Error('sbuilder: cannot duplicate ROOT');
@@ -551,15 +563,24 @@ export function duplicateNode(doc, id, guard) {
551
563
  'reduce back to a reference on save. Remove the block, duplicate, then add the block ' +
552
564
  'again through its app.');
553
565
  }
554
- const parentId = n.data.parent;
555
- if (!parentId || !doc.has(parentId)) {
556
- throw new Error(`sbuilder: ${id} has no parent to be duplicated beside`);
566
+ if (!parentId || !target.has(parentId)) {
567
+ throw new Error(src === target
568
+ ? `sbuilder: ${id} has no parent to be duplicated beside`
569
+ : `sbuilder: parent "${parentId}" is not on the target page — omit parent_id to paste at the page's end, or read its ids with sb_outline.`);
570
+ }
571
+ if (src !== target) {
572
+ // Onto ANOTHER document the parent is the caller's choice, so it gets the
573
+ // checks sb_add gives one.
574
+ if (parentId !== target.doc.root_node_id)
575
+ requireContainer(target.node(parentId).data.type, parentId);
576
+ requireAllowed(target.node(parentId).data.type, n.data.type, guard);
577
+ soft(guard, () => refuseAppBlockParent(target, parentId, 'pasting into'));
557
578
  }
558
579
  // A copy lands BESIDE the original, so duplicating a repeater's template makes
559
580
  // the second child the renderer will never draw. sb_add and sb_move already
560
581
  // refuse that; duplicate is the likeliest way to reach for it, since
561
582
  // "duplicate the card" is the move a designer makes constantly.
562
- soft(guard, () => refuseSecondTemplate(doc.doc, parentId, 'Duplicating into'));
583
+ soft(guard, () => refuseSecondTemplate(target.doc, parentId, 'Duplicating into'));
563
584
  const patches = [];
564
585
  const ids = [];
565
586
  // One pass, parent-first, so a child's `parent` always names an id already
@@ -610,13 +631,7 @@ export function duplicateNode(doc, id, guard) {
610
631
  return clone.id;
611
632
  };
612
633
  const rootId = copy(id, parentId);
613
- const at = doc.node(parentId).data.nodes.indexOf(id);
614
- patches.push({
615
- op: 'insert',
616
- path: ['nodes', parentId, 'data', 'nodes'],
617
- index: at < 0 ? APPEND : at + 1,
618
- value: rootId,
619
- });
634
+ patches.push({ op: 'insert', path: ['nodes', parentId, 'data', 'nodes'], index, value: rootId });
620
635
  return { patches, ids };
621
636
  }
622
637
  export function moveNode(doc, id, newParentId, index, guard) {
@@ -106,6 +106,8 @@ export const FIX = {
106
106
  'post, blog). Off those it renders its placeholder for ever.',
107
107
  unread_value: 'Write a value the renderer reads: sb_traits_for "<type>" lists them; sb_set id "<id>" ' +
108
108
  'with one, at the same breakpoint it is stored at.',
109
+ unknown_icon: 'Write a real icon name: sb_set id "<id>", namespace <ns>, keys { "<key>": "<name>" } — the ' +
110
+ 'problem lists the nearest ones; sb_catalog_search query "icon:<word>" lists more.',
109
111
  // ADVICE, never a blocker: the snippet works. The cost is that nobody can edit
110
112
  // it in the inspector, it ignores the theme, and it is invisible to sb_review.
111
113
  custom_code_native: 'Consider rebuilding it natively: sb_catalog_search "<key>" → sb_traits_for → sb_add, ' +
@@ -0,0 +1,124 @@
1
+ import { ICON_KEYS, ICON_NAMES } from '../../catalog/icons.generated.js';
2
+ /**
3
+ * ICON NAMES — which keys hold one, whether a value is one, and the nearest real
4
+ * ones when it is not.
5
+ *
6
+ * A name outside the manifest is stored, saved and published, then drawn as the
7
+ * DEFAULT star (`RenderIconSVG` falls back to `DefaultIcon`) or as nothing
8
+ * (`button`'s `generated.HasIcon` guard) — never the icon asked for, and with no
9
+ * error at any step. A WARNING, like every vocabulary note here: the manifest can
10
+ * be older than the deployment. `''` is never flagged: it is what the "None" cell
11
+ * writes, what `form-text` seeds, and the `icon` element draws its default for it.
12
+ */
13
+ /** `true` when `<ns>.<key>` holds an icon name on this element (ICON_KEYS). */
14
+ export function isIconKey(type, ns, key) {
15
+ return ICON_KEYS[type]?.some((k) => k.ns === ns && k.key === key) ?? false;
16
+ }
17
+ /** The `<ns>.<key>` list `sb_traits_for` reports; empty when the element has none. */
18
+ export function iconKeysOf(type) {
19
+ return (ICON_KEYS[type] ?? []).map((k) => `${k.ns}.${k.key}`);
20
+ }
21
+ const norm = (s) => s.replace(/^ri-/i, '').toLowerCase().replace(/[^a-z0-9]/g, '');
22
+ const stemOf = (s) => norm(s).replace(/(fill|line)$/, '');
23
+ let index = null;
24
+ /**
25
+ * Names whose stem (lower-case, separators and the Fill/Line suffix dropped)
26
+ * equals, starts with, or contains the query's — in that order, shorter first.
27
+ * Nothing found → retried on the query's longest word, so "ShoppingBasketIcon"
28
+ * still lands near the basket.
29
+ *
30
+ * Substring ranking only — a SEARCH is a word someone chose. A misspelled NAME is
31
+ * `nearestIcons`' job, which tries edit distance first.
32
+ */
33
+ export function searchIcons(query, cap = 30, retryOnWord = true) {
34
+ index ??= [...ICON_NAMES].map((n) => [n, stemOf(n)]);
35
+ const rank = (q) => !q
36
+ ? []
37
+ : index
38
+ .map(([n, s]) => ({ n, s, r: s === q ? 3 : s.startsWith(q) ? 2 : s.includes(q) ? 1 : 0 }))
39
+ .filter((x) => x.r > 0)
40
+ .sort((a, b) => b.r - a.r || a.s.length - b.s.length || a.n.localeCompare(b.n))
41
+ .map((x) => x.n);
42
+ let hits = rank(stemOf(query));
43
+ if (!hits.length && retryOnWord) {
44
+ const words = query.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/);
45
+ const longest = words.filter((w) => w.length > 2).sort((a, b) => b.length - a.length)[0];
46
+ if (longest)
47
+ hits = rank(longest);
48
+ }
49
+ return hits.slice(0, cap);
50
+ }
51
+ /** Edit distance, capped: stops early past `max` (a name's stem is short). */
52
+ function distance(a, b, max) {
53
+ if (Math.abs(a.length - b.length) > max)
54
+ return max + 1;
55
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
56
+ for (let i = 1; i <= a.length; i++) {
57
+ const cur = [i];
58
+ let best = i;
59
+ for (let j = 1; j <= b.length; j++) {
60
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
61
+ best = Math.min(best, cur[j]);
62
+ }
63
+ if (best > max)
64
+ return max + 1;
65
+ prev = cur;
66
+ }
67
+ return prev[b.length];
68
+ }
69
+ /**
70
+ * The names a WRONG value most likely meant. A typo first ("ShoppingCartt" is
71
+ * one edit from ShoppingCart, while a word search on "shopping" ranks the shorter
72
+ * ShoppingBag ahead of it — measured on a live platform), then the search.
73
+ * Fill before Line on a tie: the platform's own default (StarFill) is a Fill.
74
+ */
75
+ export function nearestIcons(query) {
76
+ index ??= [...ICON_NAMES].map((n) => [n, stemOf(n)]);
77
+ const q = stemOf(query);
78
+ const max = Math.max(1, Math.floor(q.length / 4));
79
+ // The suffix the caller wrote wins a tie; otherwise Fill, the default's own.
80
+ const want = /line$/i.test(norm(query)) ? 'Line' : 'Fill';
81
+ const typos = q
82
+ ? index
83
+ .map(([n, s]) => ({ n, s, d: distance(q, s, max) }))
84
+ .filter((x) => x.d <= max)
85
+ .sort((a, b) => a.d - b.d ||
86
+ // ShoppingCart over ShoppingCart2 for "ShoppingCartt": a stem the query
87
+ // extends is a closer guess than a sibling the same distance away.
88
+ Number(q.startsWith(b.s) || b.s.startsWith(q)) - Number(q.startsWith(a.s) || a.s.startsWith(q)) ||
89
+ Number(b.n.endsWith(want)) - Number(a.n.endsWith(want)) ||
90
+ a.s.length - b.s.length ||
91
+ a.n.localeCompare(b.n))
92
+ .map((x) => x.n)
93
+ : [];
94
+ // A value that is a WHOLE WORD of real names ("cart" in Shopping·Cart, "user")
95
+ // means those names, not Car/Cast one edit away; a fragment inside another word
96
+ // ("hart" in c·hart) is more likely a typo (Heart). So: word hits, then typos,
97
+ // then whatever else the search finds.
98
+ const wordsOf = (n) => n.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().split(' ');
99
+ const words = q
100
+ ? index
101
+ .filter(([n, st]) => st.startsWith(q) || wordsOf(n).includes(q))
102
+ .sort(([an, as], [bn, bs]) => as.length - bs.length || Number(bn.endsWith(want)) - Number(an.endsWith(want)) || an.localeCompare(bn))
103
+ .map(([n]) => n)
104
+ : [];
105
+ const order = [...words, ...typos, ...searchIcons(query, 5)];
106
+ return [...new Set(order)].slice(0, 5);
107
+ }
108
+ /**
109
+ * The problem sentence for `value` written to `<ns>.<key>` on `type`, or null
110
+ * when the key holds no icon or the value is a real name (or `''`).
111
+ */
112
+ export function iconNote(type, ns, key, value) {
113
+ if (!isIconKey(type, ns, key) || value === '' || value === undefined || value === null)
114
+ return null;
115
+ if (typeof value === 'string' && ICON_NAMES.has(value))
116
+ return null;
117
+ const near = typeof value === 'string' ? nearestIcons(value) : [];
118
+ return {
119
+ near,
120
+ problem: `${ns}.${key} = ${JSON.stringify(value)} is not an icon name this platform has, so ${type} ` +
121
+ 'publishes the default star or no icon at all, with no error. ' +
122
+ (near.length ? `Nearest: ${near.join(', ')}.` : 'Nothing close — search with sb_catalog_search query "icon:<word>".'),
123
+ };
124
+ }
@@ -7,6 +7,7 @@ import { fill } from './findings.js';
7
7
  import { formFieldFindings } from './formdoc.js';
8
8
  import { deadNavigation, liveEventTable, PAYLOAD_NEEDS } from './navhref.js';
9
9
  import { unknownValueNote, unknownWriteNote, vocabularyFor, vocabularyForWrite } from './vocabulary.js';
10
+ import { iconNote } from './icons.js';
10
11
  /**
11
12
  * A defect somebody looking at the page would see.
12
13
  *
@@ -466,6 +467,23 @@ opts = {}) {
466
467
  const said = new Set();
467
468
  for (const [ns, slot] of layers) {
468
469
  for (const [k, v] of Object.entries(slot ?? {})) {
470
+ // An icon name the manifest lacks draws the default star or nothing.
471
+ const icon = iconNote(type, ns, k, v);
472
+ if (icon) {
473
+ if (said.has(`${ns}.${k}=${String(v)}`))
474
+ continue;
475
+ said.add(`${ns}.${k}=${String(v)}`);
476
+ out.push({
477
+ code: 'unknown_icon',
478
+ nodeId: id,
479
+ type,
480
+ problem: icon.problem,
481
+ key: `${ns}.${k}`,
482
+ fix: fill('unknown_icon', { id, ns, key: k }),
483
+ ...(inOverlay.has(id) ? { overlay: true } : {}),
484
+ });
485
+ continue;
486
+ }
469
487
  const unread = unknownWriteNote(type, ns, k, v) ?? (ns === 'config' ? unknownValueNote(k, v) : null);
470
488
  if (!unread || said.has(`${ns}.${k}=${String(v)}`))
471
489
  continue;
@@ -3,6 +3,8 @@ import { FIELD_SKIN_BY_NODE } from '../../catalog/fieldskin.generated.js';
3
3
  import { FORM_RULE_VOCAB } from '../../catalog/formrules.generated.js';
4
4
  import { fill } from './findings.js';
5
5
  import { collectsValue, formFieldFindings, formFields, nameFromLabel } from './formdoc.js';
6
+ import { ICON_KEYS } from '../../catalog/icons.generated.js';
7
+ import { iconNote } from './icons.js';
6
8
  import { animationNote, deadKeyNote, preconditionNotes, unknownValueNote, unknownWriteNote, unsupportedSettingNote, } from './vocabulary.js';
7
9
  /**
8
10
  * Keys any node may carry, read by shared helpers rather than one element:
@@ -23,15 +25,13 @@ const UNIVERSAL = {
23
25
  * stores the picked site text style as `config.textGlobalStyle`, the key
24
26
  * `schema/src/theme.ts` names for every text-bearing element). Keyed by the
25
27
  * GENERATED control name, so a key is known only where the element offers the row.
28
+ * Every icon picker's key is generated (`ICON_KEYS`) and added in `keysOf`.
26
29
  *
27
30
  * ponytail: hand-read, and only the rows the platform's own seeds exercise
28
31
  * (test/seed-sweep.test.ts). Upgrade: have codegen read every widget's
29
- * `specialKey`/`configKey` into the catalog — blocked on a catalog regenerate.
32
+ * `specialKey`/`configKey` into the catalog, as it now does for IconPicker.
30
33
  */
31
34
  const WIDGET_WRITES = {
32
- button_icon: ['specials', 'icon'],
33
- breadcrumb_icon: ['specials', 'icon'],
34
- divider_icon: ['specials', 'iconName'],
35
35
  text_global_style: ['config', 'textGlobalStyle'],
36
36
  };
37
37
  const known = new Map();
@@ -64,6 +64,8 @@ function keysOf(type) {
64
64
  for (const c of el.controls)
65
65
  if (WIDGET_WRITES[c])
66
66
  add(WIDGET_WRITES[c][0], [WIDGET_WRITES[c][1]]);
67
+ for (const k of ICON_KEYS[type] ?? [])
68
+ add(k.ns, [k.key]);
67
69
  // A control the trait registry does not describe is often an inspector row
68
70
  // named after the key it writes (`{ key: 'quickviewId', visible: false }` on
69
71
  // list-dataset), so its NAME counts as a key in either namespace.
@@ -134,6 +136,11 @@ export function writeCheck(type, namespace, keys, opts = {}) {
134
136
  '"style". Ignore this if the key is newer than this catalog.');
135
137
  continue;
136
138
  }
139
+ const icon = iconNote(type, namespace, k, v);
140
+ if (icon) {
141
+ push(`icon:${type}.${namespace}.${k}=${JSON.stringify(v)}`, 'unknown_icon', k, icon.problem, icon.near.length ? `Write one of: ${icon.near.join(', ')}.` : 'Find a name with sb_catalog_search query "icon:<word>".');
142
+ continue;
143
+ }
137
144
  const vn = type ? unknownWriteNote(type, namespace, k, v) : null;
138
145
  if (vn) {
139
146
  push(`value:${type}.${namespace}.${k}=${JSON.stringify(v)}`, 'unknown_value', k, vn, 'Write one of the listed values.');
@@ -727,10 +727,40 @@ export async function promoteGlobal(ctx, session, siteId, nodeId, kind, name, op
727
727
  /**
728
728
  * Save a section on the OPEN page as a reusable section template — the editor's
729
729
  * "Save as template". The page is untouched; `sb_template_use` places a copy.
730
+ *
731
+ * With `templateId` it REWRITES that template's content instead — the editor's
732
+ * template edit mode. The agent's way in is the full toolset: place the template
733
+ * on a page with sb_template_use, edit it there, save it back over the template.
734
+ * The PUT is fenced on the template's `rev`, read from the list (there is no
735
+ * single-template GET), so an edit somebody else saved in between is refused.
730
736
  */
731
737
  export async function saveTemplate(ctx, session, siteId, nodeId, name, opts) {
732
738
  const doc = session.current();
733
739
  const id = liftable(doc.doc, nodeId, 'template_save');
740
+ const base = `/api/sites/${encodeURIComponent(siteId)}/section-templates`;
741
+ if (opts.templateId) {
742
+ const list = (await request({ base: ctx.base, method: 'GET', path: base, token: siteToken(ctx), fetchImpl: ctx.fetchImpl }));
743
+ const t = (list.sectionTemplates ?? []).find((x) => x.id === opts.templateId);
744
+ if (!t)
745
+ throw new Error(`sbuilder: this site has no section template "${opts.templateId}" — sb_templates lists them.`);
746
+ if (t.source && t.source !== 'site') {
747
+ throw new Error(`sbuilder: template "${opts.templateId}" is a ${t.source} template, not this site's own — save a new one instead.`);
748
+ }
749
+ const document = sectionDocument(doc.doc, id);
750
+ const nodes = Object.keys(document.nodes).length;
751
+ const path = `${base}/${encodeURIComponent(opts.templateId)}/document`;
752
+ if (opts.dryRun) {
753
+ return { dry_run: true, nodes, next: `Nothing was sent. Re-call with dry_run:false to replace "${t.name ?? opts.templateId}"'s content.` };
754
+ }
755
+ await request({ base: ctx.base, method: 'PUT', path, token: siteToken(ctx), body: { document, rev: t.rev ?? 0 }, fetchImpl: ctx.fetchImpl });
756
+ return {
757
+ template: { id: opts.templateId, name: t.name ?? '' },
758
+ nodes,
759
+ next: 'Pages that placed this template earlier keep their own copy; only new placements get this content. ' +
760
+ 'A template shared with the organization goes back to review after a content change.' +
761
+ (name?.trim() ? ' `name` was not applied — a rewrite changes content only.' : ''),
762
+ };
763
+ }
734
764
  if (!name?.trim())
735
765
  throw new Error('sbuilder: action:"template_save" needs name — the template\'s name in the library.');
736
766
  const document = sectionDocument(doc.doc, id);
@@ -741,7 +771,7 @@ export async function saveTemplate(ctx, session, siteId, nodeId, name, opts) {
741
771
  const made = (await request({
742
772
  base: ctx.base,
743
773
  method: 'POST',
744
- path: `/api/sites/${encodeURIComponent(siteId)}/section-templates`,
774
+ path: base,
745
775
  token: siteToken(ctx),
746
776
  body: { name: name.trim(), document },
747
777
  fetchImpl: ctx.fetchImpl,
@@ -652,11 +652,16 @@ export function registerLiveTools(server, ctx, session) {
652
652
  });
653
653
  }
654
654
  const asset = await uploadMedia(ctx, site_id, { path, url, name, folderId: folder_id });
655
+ const font = asset.mediaType === 'font';
655
656
  return text({
656
657
  asset,
657
- next: asset.url
658
- ? `Use it: sb_set id "<node>", namespace specials, keys { "src": ${JSON.stringify(asset.url)} }`
659
- : 'Uploaded, but the server returned no url — read it back with sb_media_list.',
658
+ next: !asset.url
659
+ ? 'Uploaded, but the server returned no url — read it back with sb_media_list.'
660
+ : font
661
+ ? `A font: give it a family — POST /api/sites/{siteId}/fonts {name, source:"upload"}, then PUT ` +
662
+ `…/fonts/{groupId}/files {weight, url: ${JSON.stringify(asset.url)}, format, sizeBytes}, ` +
663
+ 'and use the family by NAME (settings fontFamily or a theme text style).'
664
+ : `Use it: sb_set id "<node>", namespace specials, keys { "src": ${JSON.stringify(asset.url)} }`,
660
665
  });
661
666
  });
662
667
  server.registerTool('sb_event', {
@@ -8,7 +8,7 @@ import { bandIds, proveLive } from '../domains/site/liveproof.js';
8
8
  import { canvasVerdict, driftOf } from '../domains/site/pagestate.js';
9
9
  import { PageDoc } from '../domains/site/document.js';
10
10
  import { canonicalRoot, PAGE_ROOT_ID } from '../domains/site/ids.js';
11
- import { addSubtree, setMany, baseOnlyKeys, moveNode, removeNode, duplicateNode, } from '../domains/site/builder.js';
11
+ import { addSubtree, setMany, baseOnlyKeys, moveNode, removeNode, duplicateNode, copyNodeInto, } from '../domains/site/builder.js';
12
12
  import { baseOnlyNote } from '../domains/site/baseonly.js';
13
13
  import { detachNote, presetIdOf, presetLayer } from '../domains/site/theme.js';
14
14
  import { inertHintsFor } from '../domains/site/inert.js';
@@ -18,7 +18,7 @@ import { skinLevelNote } from '../domains/site/fieldskin.js';
18
18
  import { siteTheme } from '../domains/site/theme-fetch.js';
19
19
  import { ensureSiteTheme } from './theme.js';
20
20
  import { request, redact, watchPageWrites, onPageSourceWrite, onGlobalWrite, onFormDocumentWrite } from '../transport/http.js';
21
- import { SPEC_GLOBAL_ID, SPEC_GLOBAL_REV, SPEC_OVERLAY_ID, SPEC_OVERLAY_REV } from '../core/tree.js';
21
+ import { SPEC_GLOBAL_ID, SPEC_GLOBAL_REV, SPEC_OVERLAY_ID, SPEC_OVERLAY_REV, subtreeIds } from '../core/tree.js';
22
22
  import { siteToken } from './credentialpick.js';
23
23
  import { validateForSave } from '../domains/site/validate.js';
24
24
  import { formDocChecks, sayChecks, specCheck, writeCheck } from '../domains/site/writecheck.js';
@@ -28,6 +28,7 @@ import { readinessGaps, READINESS_NOTICE, siteLanguage } from '../domains/site/r
28
28
  import { gatherReadiness } from '../domains/site/readiness-fetch.js';
29
29
  import { globalWarning, restampPatches, RESPONSIVE_NOTICE } from '../domains/site/traps.js';
30
30
  import { catalogBrowse, catalogMatches, searchWithTemplates, traitsFor } from '../catalog/element-search.js';
31
+ import { searchIcons } from '../domains/site/icons.js';
31
32
  import { layoutForPageName, missingUsualPages, purposeTypeForName } from '../domains/site/inventory.js';
32
33
  import { LAYOUT_PATTERNS, PATTERN_BY_ID, THEME_TOKENS, } from '../domains/site/patterns.js';
33
34
  import { tokensFromPage } from '../domains/site/importmap.js';
@@ -1364,7 +1365,7 @@ export function registerPageTools(server, ctx) {
1364
1365
  server.registerTool('sb_catalog_search', {
1365
1366
  description: 'Find an element type by what it does — or OMIT query to browse every type, the only ' +
1366
1367
  'way to meet one you would not have searched for. detail:true adds the AI hints, as ' +
1367
- 'does sb_traits_for.',
1368
+ 'does sb_traits_for. query "icon:<word>" lists icon names.',
1368
1369
  inputSchema: {
1369
1370
  query: z.string().optional(),
1370
1371
  limit: z.number().int().min(1).max(60).optional().describe('Default 8'),
@@ -1376,15 +1377,17 @@ export function registerPageTools(server, ctx) {
1376
1377
  // whole catalogue instead — see catalogBrowse for the measurement that made
1377
1378
  // this necessary: a store built with these tools used 17 element types and
1378
1379
  // hand-assembled what a dozen purpose-built ones already do.
1379
- async ({ query, limit, detail }) => text(query && query.trim()
1380
- ? searchWithTemplates(query, catalogMatches(query, { limit, detail }))
1381
- : {
1382
- elements: catalogBrowse(),
1383
- note: 'Every element type, grouped as the palette groups them. Pass a TYPE as query ' +
1384
- 'for the fields to choose by, or a CATEGORY NAME with limit 60 to read that ' +
1385
- "whole group's descriptions at once — which is how you find out what the ones " +
1386
- 'you have never used are for. sb_traits_for then has the controls and hints.',
1387
- }));
1380
+ async ({ query, limit, detail }) => text(/^\s*icon:/i.test(query ?? '')
1381
+ ? { icons: searchIcons(query.replace(/^\s*icon:/i, '').trim(), limit ?? 30) }
1382
+ : query && query.trim()
1383
+ ? searchWithTemplates(query, catalogMatches(query, { limit, detail }))
1384
+ : {
1385
+ elements: catalogBrowse(),
1386
+ note: 'Every element type, grouped as the palette groups them. Pass a TYPE as query ' +
1387
+ 'for the fields to choose by, or a CATEGORY NAME with limit 60 to read that ' +
1388
+ "whole group's descriptions at once — which is how you find out what the ones " +
1389
+ 'you have never used are for. sb_traits_for then has the controls and hints.',
1390
+ }));
1388
1391
  server.registerTool('sb_traits_for', {
1389
1392
  description: "This element's INSPECTOR, as a person sees it: tabs, groups, and every control name — " +
1390
1393
  'with what each DECLARED control writes, and the AI hints for using the element. Read ' +
@@ -1813,24 +1816,116 @@ export function registerPageTools(server, ctx) {
1813
1816
  });
1814
1817
  server.registerTool('sb_duplicate', {
1815
1818
  description: 'Copy a node and everything under it, under fresh ids, right after the original. The ' +
1816
- 'move a designer makes constantly — build one card, duplicate it twice.',
1819
+ 'move a designer makes constantly — build one card, duplicate it twice. to_page_id ' +
1820
+ '(and to_site_id) pastes the copy onto another page instead, as the editor\'s copy/paste ' +
1821
+ 'does: a cross-site copy has its images copied into the target library and references ' +
1822
+ 'that cannot travel blanked and listed; the open page becomes the target.',
1817
1823
  inputSchema: {
1818
1824
  id: z.string(),
1825
+ to_page_id: z.string().optional(),
1826
+ to_site_id: z.string().optional(),
1827
+ parent_id: z.string().optional().describe('with to_page_id: where on that page; default ROOT, before a footer'),
1828
+ index: z.number().int().min(0).optional(),
1819
1829
  dry_run: z.boolean().optional(),
1820
1830
  force: z.boolean().optional().describe('Override a render-inference guard; reported as forced'),
1821
1831
  },
1822
1832
  annotations: { readOnlyHint: false, destructiveHint: false },
1823
- }, async ({ id, dry_run, force }) => {
1833
+ }, async ({ id, to_page_id, to_site_id, parent_id, index, dry_run, force }) => {
1824
1834
  const d = session.current();
1825
1835
  const guard = { force, forced: [] };
1826
- const { patches, ids } = duplicateNode(d, id, guard);
1827
- const forced = guard.forced.length ? { forced: guard.forced } : {};
1836
+ const forcedOf = () => (guard.forced.length ? { forced: [...new Set(guard.forced)] } : {});
1837
+ if (!to_page_id) {
1838
+ if (to_site_id || parent_id || index !== undefined) {
1839
+ throw new Error('sbuilder: to_site_id, parent_id and index go with to_page_id — the page the copy lands on.');
1840
+ }
1841
+ const { patches, ids } = duplicateNode(d, id, guard);
1842
+ if (dry_run !== false) {
1843
+ const refuse = session.wouldRefuse(patches);
1844
+ return text({ dry_run: true, would_copy: ids.length, ...(refuse ? { would_refuse: refuse } : {}), ...forcedOf() });
1845
+ }
1846
+ await session.applyAndSave(patches);
1847
+ return text({ duplicated: id, into: ids[0], nodes: ids.length, rev: d.rev, ...forcedOf() });
1848
+ }
1849
+ // ONTO ANOTHER PAGE — the editor's paste (`useCanvasClipboard.ts`).
1850
+ const from = session.location();
1851
+ const toSite = to_site_id ?? from.siteId;
1852
+ const crossSite = toSite !== from.siteId;
1853
+ if (!crossSite && to_page_id === from.pageId) {
1854
+ throw new Error('sbuilder: that is the open page — omit to_page_id to duplicate in place.');
1855
+ }
1856
+ if (crossSite && ctx.apiKey) {
1857
+ // A key belongs to exactly ONE site, and every /api/sites route prefers it.
1858
+ throw new Error('sbuilder: a cross-site paste needs a session login — an API key (SB_TOKEN) opens one site only. ' +
1859
+ 'Unset SB_TOKEN and connect with SB_EMAIL/SB_PASSWORD as a member of both sites.');
1860
+ }
1861
+ // A FROZEN copy of the source: opening the target replaces the session's document.
1862
+ let src = PageDoc.from(structuredClone(d.doc));
1863
+ const nodes = subtreeIds(src.doc, id).length;
1864
+ const place = (target) => {
1865
+ const parent = parent_id ?? target.doc.root_node_id;
1866
+ const at = index ?? (parent === target.doc.root_node_id ? middleEnd(target.doc) : Number.MAX_SAFE_INTEGER);
1867
+ return copyNodeInto(src, id, target, parent, at, guard);
1868
+ };
1869
+ // EVERY REFUSAL BEFORE ANYTHING MOVES — the editor's own order
1870
+ // (useCanvasClipboard.ts `canAddNodeTree` before `reconcileClipboard`). The
1871
+ // reconcile copies images into the target library as it answers, and opening
1872
+ // the target replaces the session's page: a paste refused after either leaves
1873
+ // orphaned assets or a caller on the wrong page. Checked on the ORIGINAL
1874
+ // document, so the app-block and overlay checks see real ancestors.
1875
+ const preview = PageDoc.from((await loadSource(ctx, toSite, to_page_id)).document);
1876
+ const planned = place(preview);
1877
+ const refuse = saveRefusal(preview, planned.patches).refusal;
1828
1878
  if (dry_run !== false) {
1829
- const refuse = session.wouldRefuse(patches);
1830
- return text({ dry_run: true, would_copy: ids.length, ...(refuse ? { would_refuse: refuse } : {}), ...forced });
1879
+ return text({
1880
+ dry_run: true,
1881
+ would_copy: nodes,
1882
+ onto: { site_id: toSite, page_id: to_page_id },
1883
+ ...(crossSite ? { cross_site: 'images are copied into the target library; product, menu and page references that cannot travel are blanked and listed' } : {}),
1884
+ ...(refuse ? { would_refuse: refuse } : {}),
1885
+ ...forcedOf(),
1886
+ });
1887
+ }
1888
+ if (refuse)
1889
+ throw new Error(`sbuilder: ${refuse}`);
1890
+ let reconciled;
1891
+ if (crossSite) {
1892
+ const tree = { rootId: id, nodes: Object.fromEntries(subtreeIds(src.doc, id).map((n) => [n, src.doc.nodes[n]])) };
1893
+ const got = (await request({
1894
+ base: ctx.base,
1895
+ method: 'POST',
1896
+ path: `/api/sites/${encodeURIComponent(toSite)}/clipboard/reconcile`,
1897
+ token: siteToken(ctx),
1898
+ body: { sourceSiteId: from.siteId, tree },
1899
+ fetchImpl: ctx.fetchImpl,
1900
+ }));
1901
+ const r = got.reconcile;
1902
+ if (!r?.tree?.nodes || !r.tree.rootId || !(r.tree.rootId in r.tree.nodes)) {
1903
+ throw new Error('sbuilder: the platform answered the reconcile with no tree — nothing was pasted.');
1904
+ }
1905
+ // A holder root so the copy can read the reconciled subtree as a document.
1906
+ const holder = '__clipboard';
1907
+ const rn = structuredClone(r.tree.nodes);
1908
+ rn[r.tree.rootId].data.parent = holder;
1909
+ src = PageDoc.from({
1910
+ schema_version: 2,
1911
+ root_node_id: holder,
1912
+ nodes: { ...rn, [holder]: { id: holder, data: { type: 'root', parent: null, nodes: [r.tree.rootId] } } },
1913
+ });
1914
+ if (r.tree.rootId !== id)
1915
+ id = r.tree.rootId;
1916
+ reconciled = { copiedAssets: r.copiedAssets ?? 0, droppedRefs: r.droppedRefs ?? [] };
1831
1917
  }
1918
+ await session.open(toSite, to_page_id);
1919
+ const { patches, ids } = place(session.current());
1832
1920
  await session.applyAndSave(patches);
1833
- return text({ duplicated: id, into: ids[0], nodes: ids.length, rev: d.rev, ...forced });
1921
+ return text({
1922
+ copied: nodes,
1923
+ into: ids[0],
1924
+ onto: { site_id: toSite, page_id: to_page_id },
1925
+ open_page: `${to_page_id} — the session now edits the page the copy landed on`,
1926
+ ...(reconciled ?? {}),
1927
+ ...forcedOf(),
1928
+ });
1834
1929
  });
1835
1930
  server.registerTool('sb_templates', {
1836
1931
  description: "The store's saved section templates — designed sections a person starts from rather " +
@@ -577,6 +577,7 @@ export function registerStoreTools(server, ctx, session) {
577
577
  .optional()
578
578
  .describe('action:"menu" — the menu node; "global_promote"/"template_save" — the section'),
579
579
  global_kind: z.enum(['header', 'footer', 'custom']).optional(),
580
+ template_id: z.string().optional().describe('action:"template_save" — overwrite this template\'s content'),
580
581
  menu_id: z.string().optional(),
581
582
  kind: z
582
583
  .enum(['popup', 'quickview'])
@@ -608,7 +609,7 @@ export function registerStoreTools(server, ctx, session) {
608
609
  dry_run: z.boolean().optional(),
609
610
  },
610
611
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
611
- }, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, global_kind, relocalize, settings, dry_run, }) => {
612
+ }, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, global_kind, template_id, relocalize, settings, dry_run, }) => {
612
613
  const siteId = siteFor(ctx, given);
613
614
  if (action === 'global_attach' || action === 'global_detach') {
614
615
  // THE COMPOSED STAMP IS NEVER THE CALLER'S TO WRITE. A page references
@@ -625,7 +626,7 @@ export function registerStoreTools(server, ctx, session) {
625
626
  return text(await promoteGlobal(ctx, session, siteId, node_id, global_kind ?? 'custom', name, { dryRun: dry_run !== false }));
626
627
  }
627
628
  if (action === 'template_save') {
628
- return text(await saveTemplate(ctx, session, siteId, node_id, name, { dryRun: dry_run !== false }));
629
+ return text(await saveTemplate(ctx, session, siteId, node_id, name, { dryRun: dry_run !== false, templateId: template_id }));
629
630
  }
630
631
  if (action === 'app') {
631
632
  if (!app_key) {
@@ -47,6 +47,12 @@ const TYPE_BY_EXT = {
47
47
  '.mp4': 'video/mp4',
48
48
  '.webm': 'video/webm',
49
49
  '.mov': 'video/quicktime',
50
+ // A site's own font files (`server/internal/media/ingest.go fontExtensions`) —
51
+ // the platform gates fonts on the extension and stores these canonical types.
52
+ '.woff2': 'font/woff2',
53
+ '.woff': 'font/woff',
54
+ '.ttf': 'font/ttf',
55
+ '.otf': 'font/otf',
50
56
  };
51
57
  function typeForName(name) {
52
58
  const dot = name.lastIndexOf('.');
@@ -158,7 +164,7 @@ export async function uploadMedia(ctx, siteId, source) {
158
164
  // `application/octet-stream` for a PNG is ordinary, and it is exactly the
159
165
  // value the platform refuses — so "the server declared a type" is not the
160
166
  // question; "the server declared a type that identifies the file" is.
161
- const usable = declared.startsWith('image/') || declared.startsWith('video/');
167
+ const usable = declared.startsWith('image/') || declared.startsWith('video/') || declared.startsWith('font/');
162
168
  const type = (usable ? declared : '') || typeForName(filename) || declared;
163
169
  const form = new FormData();
164
170
  // THE BLOB'S TYPE IS THE UPLOAD'S CONTENT TYPE, and omitting it broke every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.80.0",
3
+ "version": "0.81.1",
4
4
  "description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
5
5
  "mcpName": "io.github.vuluu2k/sbuilder-mcp",
6
6
  "type": "module",