From 3833a41a9e6d4090d3311d8a430d065dd3d8a1f8 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 10 Jun 2026 12:46:51 +0200 Subject: [PATCH] Phosphor icons: resolve by name via a generated map (T-314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 49 hand-maintained named consts with one generated label→codepoint map (phosphor_glyphs.g.dart, 1512 glyphs from the glyph table via tool/gen_phosphor_glyphs.dart). Feature code now references glyphs by their exact kebab-case name — PhosphorIcons.byName('folder') — with no raw codepoints; this also lets a Lua extension name an icon without crossing the FFI boundary with a codepoint. byName is total: an unknown name degrades to the `placeholder` box so the bug is visible (it's a real error), while phosphor_glyphs_test asserts every byName('...') literal in lib/ resolves — recovering the typo check a const gave. Migrated the 89 call sites. Adds EmptyIconPainter for an intentional blank that still reserves the icon box; ClideFilterBox gains showIcon to keep the slot aligned when blank. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/ui-design/references/icons.md | 38 +- .pql/changelog/ticket_history/2026-06.sql | 1 + .pql/changelog/tickets/2026-06.sql | 1 + lib/app.dart | 36 +- lib/builtin/claude/src/claude_composer.dart | 4 +- .../claude/src/claude_meta_sidebar.dart | 14 +- lib/builtin/claude/src/conversation_card.dart | 2 +- lib/builtin/claude/src/conversation_view.dart | 2 +- lib/builtin/claude/src/extension.dart | 2 +- lib/builtin/claude/src/image_thumbnail.dart | 2 +- .../claude/src/permission_mode_control.dart | 8 +- lib/builtin/claude/src/team_chat_sidebar.dart | 4 +- lib/builtin/decisions/src/decisions_view.dart | 2 +- lib/builtin/decisions/src/extension.dart | 4 +- lib/builtin/files/src/extension.dart | 2 +- lib/builtin/git/src/extension.dart | 2 +- lib/builtin/git/src/git_status_item.dart | 2 +- lib/builtin/markdown/src/extension.dart | 2 +- lib/builtin/pql/src/extension.dart | 2 +- lib/builtin/search/src/extension.dart | 2 +- lib/builtin/shared/reader_chrome.dart | 10 +- .../theme_picker/src/theme_status_item.dart | 2 +- lib/builtin/tickets/src/extension.dart | 4 +- lib/builtin/tickets/src/tickets_view.dart | 2 +- lib/builtin/welcome/src/welcome_view.dart | 10 +- lib/widgets/src/clide_accordion.dart | 2 +- lib/widgets/src/clide_column_hat.dart | 2 +- lib/widgets/src/clide_filter_box.dart | 27 +- lib/widgets/src/clide_icon.dart | 17 + lib/widgets/src/clide_lightbox.dart | 2 +- lib/widgets/src/clide_toast.dart | 10 +- lib/widgets/src/icons/phosphor.dart | 69 +- lib/widgets/src/icons/phosphor_glyphs.g.dart | 1522 +++++++++++++++++ test/app_collapse_toggle_test.dart | 18 +- .../claude/team_chat_sidebar_test.dart | 2 +- test/widgets/src/phosphor_glyphs_test.dart | 47 + test/widgets/zero_coverage_widgets_test.dart | 16 +- tool/gen_phosphor_glyphs.dart | 48 + 38 files changed, 1779 insertions(+), 163 deletions(-) create mode 100644 lib/widgets/src/icons/phosphor_glyphs.g.dart create mode 100644 test/widgets/src/phosphor_glyphs_test.dart create mode 100644 tool/gen_phosphor_glyphs.dart diff --git a/.claude/skills/ui-design/references/icons.md b/.claude/skills/ui-design/references/icons.md index 2792a2f1..627781d2 100644 --- a/.claude/skills/ui-design/references/icons.md +++ b/.claude/skills/ui-design/references/icons.md @@ -6,32 +6,30 @@ The app bundles Phosphor Icons (v2.0.8, MIT) as TTF fonts at `assets/fonts/phosphor/` (regular, bold, fill weights). **Glyph reference:** [`phosphor-glyphs.md`](phosphor-glyphs.md) — the full -1512-glyph table (codepoint · kebab name · Pascal name) with an **In clide** -column flagging the ~45 already wired into `PhosphorIcons`. Read/grep this to -pick a glyph; reach for an already-defined one first. (It mirrors -`assets/fonts/phosphor/codepoints.csv`; regenerate with -`python3 .claude/skills/ui-design/scripts/gen-phosphor-glyphs.py` after a font -bump.) - -### Adding an icon - -Find the codepoint in [`phosphor-glyphs.md`](phosphor-glyphs.md), then add a -`static const` entry to `PhosphorIcons` in -`lib/widgets/src/icons/phosphor.dart`: - -```dart -static const arrowClockwise = PhosphorIconPainter(0xe036); -``` - -Only add icons we actually use — don't bulk-import the full set. +1512-glyph table (codepoint · kebab name · Pascal name). Grep it for the exact +kebab name; **all 1512 are available** — no per-icon wiring (T-314). ### Using an icon +Reference a glyph by its **exact kebab-case name** — no codepoints in feature +code; the label→codepoint table lives only in the generated +`lib/widgets/src/icons/phosphor_glyphs.g.dart`: + ```dart -ClideIcon(PhosphorIcons.arrowClockwise, size: 13) +ClideIcon(PhosphorIcons.byName('arrow-clockwise'), size: 13) ``` -Or as a `TabContribution` icon field: `icon: PhosphorIcons.lightbulb`. +Or as a `TabContribution` icon field: `icon: PhosphorIcons.byName('lightbulb')`. + +- An **unknown name** is a bug — it degrades to the `placeholder` error box so + the mistake is visible. `phosphor_glyphs_test` asserts every `byName('…')` + literal in `lib/` resolves, recovering the typo check a const would give. +- For an **intentional blank** that still reserves the icon's box (alignment), + use `const EmptyIconPainter()` — *not* a missing name. +- Regenerate the map after a font bump: `dart run tool/gen_phosphor_glyphs.dart`. + +**Bold weight:** pass `family: 'Phosphor-Bold'` to `PhosphorIconPainter`. +**Fill weight:** `family: 'Phosphor-Fill'`. **Bold weight:** pass `family: 'Phosphor-Bold'` to `PhosphorIconPainter`. **Fill weight:** `family: 'Phosphor-Fill'`. diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index ad94ec4a..9a23ea19 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -2810,3 +2810,4 @@ The LABEL is the bridge between the two surfaces: Claude attaches a label to eac - Each icon renders at multiple sizes: a hero at the existing clideFontWelcomeBanner (52) token plus one sample at each inline font-size token (badge 11 -> body 15), sized off the typography tokens and labelled so the reviewer sees legibility at real UI sizes. - The card is display-only (no inline selection); selection happens in the interaction zone (convo box) via a Claude-offered choice list whose options reuse the per-icon labels from the card. - Unknown/invalid icon names fail with a clear user error, not a blank glyph.', NULL, '2026-06-10 10:44:06', '2026-06-10 10:44:06', '2026-06-10 10:44:06', NULL, 'a55e979e042abaf7b0758b5dbba183f7', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB23Z52EVGQ4ZDAQ5BVAJ13R', 'status', 'ready', 'done', NULL, '2026-06-10 10:46:25', '2026-06-10 10:46:25', '2026-06-10 10:46:25', NULL, '4f76b8c659b36db90ac957a42c610d6f', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 7ce554a4..21ffbf6b 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -1715,3 +1715,4 @@ The LABEL is the bridge between the two surfaces: Claude attaches a label to eac - Each icon renders at multiple sizes: a hero at the existing clideFontWelcomeBanner (52) token plus one sample at each inline font-size token (badge 11 -> body 15), sized off the typography tokens and labelled so the reviewer sees legibility at real UI sizes. - The card is display-only (no inline selection); selection happens in the interaction zone (convo box) via a Claude-offered choice list whose options reuse the per-icon labels from the card. - Unknown/invalid icon names fail with a clear user error, not a blank glyph.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 10:20:54', '2026-06-10 10:44:06', NULL, '9a25fe730b81f5192fb4142d66fbff85', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB23Z52EVGQ4ZDAQ5BVAJ13R', 'task', '06FB0TNQM5TWC00GW0P3X02HZW', 'Phosphor icons: single label→codepoint map; reference glyphs by name, not codepoint', 'Replace the 49 hand-maintained named consts in lib/widgets/src/icons/phosphor.dart with ONE generated const Map of all 1512 Phosphor glyphs (exact kebab-case upstream names → codepoint). Code references glyphs via PhosphorIcons.byName(''folder'') — no raw 0xNNNN in feature code; codepoints live only in the generated map. Migrate the ~32 call sites (by codepoint, not guessed names). Add a test that scans lib/ for byName(''...'') literals and asserts each exists in the map, recovering the compile-time typo safety the named consts gave. Map is regeneratable from .claude/skills/ui-design/references/phosphor-glyphs.md. Foundation for the glyph preview card (T-313).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 10:24:29', '2026-06-10 10:46:25', NULL, 'aa95db9de3811c6058836e661dd304e8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/lib/app.dart b/lib/app.dart index bd8975ed..72891114 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -395,7 +395,7 @@ class _RightHatContent extends StatelessWidget { return Row(children: [ _WinBtn(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens), _WinBtn(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens), - _WinBtn(icon: PhosphorIcons.xMark, onTap: wc.close, tokens: tokens, isClose: true), + _WinBtn(icon: PhosphorIcons.byName('x'), onTap: wc.close, tokens: tokens, isClose: true), ]); } } @@ -452,7 +452,7 @@ class _ProjectSwitcherButton extends StatelessWidget { children: [ ClideText(label, fontSize: 12, color: hovered ? tokens.globalForeground : tokens.chromeForeground, fontFamily: clideMonoFamily), const SizedBox(width: 4), - ClideIcon(PhosphorIcons.caretDown, size: 8, color: tokens.chromeForeground), + ClideIcon(PhosphorIcons.byName('caret-down'), size: 8, color: tokens.chromeForeground), ], ), ); @@ -591,7 +591,7 @@ class _RecentProjectRow extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: Row( children: [ - ClideIcon(PhosphorIcons.folder, size: 14, color: tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('folder'), size: 14, color: tokens.globalTextMuted), const SizedBox(width: 8), Expanded( child: Column( @@ -608,7 +608,7 @@ class _RecentProjectRow extends StatelessWidget { muted: true, fontSize: 12, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis), ), ClideText(' · ', muted: true, fontSize: 12), - ClideIcon(PhosphorIcons.gitBranch, size: 10, color: tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('git-branch'), size: 10, color: tokens.globalTextMuted), const SizedBox(width: 3), ClideText(project.branch!, muted: true, fontSize: 12, fontFamily: clideMonoFamily), ], @@ -920,7 +920,7 @@ class _RevealedTab extends StatelessWidget { tooltip: 'Close', builder: (_, hovered, __) => Padding( padding: const EdgeInsets.all(6), - child: ClideIcon(PhosphorIcons.xMark, size: 12, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), + child: ClideIcon(PhosphorIcons.byName('x'), size: 12, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ), @@ -1088,20 +1088,20 @@ class _BottomRail extends StatelessWidget { if (t.icon is ClideIconPainter) return t.icon as ClideIconPainter; if (slot == Slots.sidebar) { return switch (t.id) { - 'files.tree' => PhosphorIcons.folder, - 'git.panel' => PhosphorIcons.gitBranch, - 'pql.panel' => PhosphorIcons.magnifyingGlass, - 'problems.panel' => PhosphorIcons.warningCircle, - 'decisions.panel' => PhosphorIcons.lightbulb, - 'tickets.panel' => PhosphorIcons.ticket, - _ => PhosphorIcons.circlesFour, + 'files.tree' => PhosphorIcons.byName('folder'), + 'git.panel' => PhosphorIcons.byName('git-branch'), + 'pql.panel' => PhosphorIcons.byName('magnifying-glass'), + 'problems.panel' => PhosphorIcons.byName('warning-circle'), + 'decisions.panel' => PhosphorIcons.byName('lightbulb'), + 'tickets.panel' => PhosphorIcons.byName('ticket'), + _ => PhosphorIcons.byName('circles-four'), }; } return switch (t.id) { - 'markdown.viewer' => PhosphorIcons.eye, - 'graph.view' => PhosphorIcons.graph, - 'pql.backlinks' => PhosphorIcons.link, - _ => PhosphorIcons.circlesFour, + 'markdown.viewer' => PhosphorIcons.byName('eye'), + 'graph.view' => PhosphorIcons.byName('graph'), + 'pql.backlinks' => PhosphorIcons.byName('link'), + _ => PhosphorIcons.byName('circles-four'), }; } } @@ -1136,8 +1136,8 @@ class StatusbarCollapseToggle extends StatelessWidget { // The chevron points the DIRECTION OF THE ACTION: collapsing tucks the pane // toward its own edge, expanding brings it back toward the centre. final icon = _isSidebar - ? (collapsed ? PhosphorIcons.caretLineRight : PhosphorIcons.caretLineLeft) - : (collapsed ? PhosphorIcons.caretLineLeft : PhosphorIcons.caretLineRight); + ? (collapsed ? PhosphorIcons.byName('caret-line-right') : PhosphorIcons.byName('caret-line-left')) + : (collapsed ? PhosphorIcons.byName('caret-line-left') : PhosphorIcons.byName('caret-line-right')); final what = _isSidebar ? 'sidebar' : 'context panel'; return SizedBox( width: 24, diff --git a/lib/builtin/claude/src/claude_composer.dart b/lib/builtin/claude/src/claude_composer.dart index c2a9b6ad..b7fdf909 100644 --- a/lib/builtin/claude/src/claude_composer.dart +++ b/lib/builtin/claude/src/claude_composer.dart @@ -564,7 +564,7 @@ class _ClaudeComposerState extends State { onTap: () => _removeAttachment(a), child: MouseRegion( cursor: SystemMouseCursors.click, - child: ClideIcon(PhosphorIcons.xMark, size: 12, color: theme.globalTextMuted), + child: ClideIcon(PhosphorIcons.byName('x'), size: 12, color: theme.globalTextMuted), ), ), ), @@ -580,6 +580,6 @@ class _ClaudeComposerState extends State { if (a.isImage) { return ImageThumbnail(path: a.path, size: 44); } - return ClideIcon(PhosphorIcons.fileText, size: 18, color: theme.globalTextMuted); + return ClideIcon(PhosphorIcons.byName('file-text'), size: 18, color: theme.globalTextMuted); } } diff --git a/lib/builtin/claude/src/claude_meta_sidebar.dart b/lib/builtin/claude/src/claude_meta_sidebar.dart index c30f564e..e5165dd2 100644 --- a/lib/builtin/claude/src/claude_meta_sidebar.dart +++ b/lib/builtin/claude/src/claude_meta_sidebar.dart @@ -902,14 +902,14 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { children: [ // Show / hide _IconButton( - painter: isVisible ? PhosphorIcons.eye : PhosphorIcons.eyeSlash, + painter: isVisible ? PhosphorIcons.byName('eye') : PhosphorIcons.byName('eye-slash'), tooltip: isVisible ? 'Hide pane' : 'Show pane', color: tokens.globalTextMuted, onTap: () => isVisible ? widget.orchestrator!.hide(managed.id) : widget.orchestrator!.show(managed.id), ), // Mute / unmute _IconButton( - painter: isMuted ? PhosphorIcons.eyeSlash : PhosphorIcons.eye, + painter: isMuted ? PhosphorIcons.byName('eye-slash') : PhosphorIcons.byName('eye'), // NOTE: We use eye/eyeSlash as stand-ins until a dedicated speaker // icon is added to PhosphorIcons (no speaker codepoint yet). // The semantic tooltip still says mute/unmute so AT users are clear. @@ -919,21 +919,21 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { ), // Inject message _IconButton( - painter: PhosphorIcons.chatCircle, + painter: PhosphorIcons.byName('chat-circle'), tooltip: 'Inject message', color: isInjecting ? tokens.globalFocus : tokens.globalTextMuted, onTap: () => widget.onToggleInject(widget.member.name), ), // Fork session (T-172): branch into a new pane without touching the original. _IconButton( - painter: PhosphorIcons.gitBranch, + painter: PhosphorIcons.byName('git-branch'), tooltip: 'Fork session', color: tokens.globalTextMuted, onTap: () => widget.onFork(widget.member.name), ), // Close session _IconButton( - painter: PhosphorIcons.xMark, + painter: PhosphorIcons.byName('x'), tooltip: 'Close session', color: tokens.globalTextMuted, onTap: () => widget.onClose(widget.member.name), @@ -958,7 +958,7 @@ class _AgentRosterRowState extends State<_AgentRosterRow> { ), const SizedBox(width: 4), _IconButton( - painter: PhosphorIcons.xMark, + painter: PhosphorIcons.byName('x'), tooltip: 'Cancel', color: tokens.globalTextMuted, onTap: () => widget.onToggleInject(widget.member.name), @@ -1186,7 +1186,7 @@ class _TaskRow extends StatelessWidget { // Reassign: cycle to the next roster member. if (broker != null && broker!.members.length > 1) _IconButton( - painter: PhosphorIcons.arrowClockwise, + painter: PhosphorIcons.byName('arrow-clockwise'), tooltip: 'Reassign task', color: tokens.globalTextMuted, onTap: () => _reassign(context), diff --git a/lib/builtin/claude/src/conversation_card.dart b/lib/builtin/claude/src/conversation_card.dart index cb6e5967..5426e414 100644 --- a/lib/builtin/claude/src/conversation_card.dart +++ b/lib/builtin/claude/src/conversation_card.dart @@ -288,7 +288,7 @@ class _ConversationCardState extends State { builder: (_, hovered, pressed) => Padding( padding: const EdgeInsets.only(right: 6), child: ClideIcon( - _collapsed ? PhosphorIcons.caretRight : PhosphorIcons.caretDown, + _collapsed ? PhosphorIcons.byName('caret-right') : PhosphorIcons.byName('caret-down'), size: 12, color: tokens.globalTextMuted, ), diff --git a/lib/builtin/claude/src/conversation_view.dart b/lib/builtin/claude/src/conversation_view.dart index 466dc54d..5f2997d4 100644 --- a/lib/builtin/claude/src/conversation_view.dart +++ b/lib/builtin/claude/src/conversation_view.dart @@ -535,7 +535,7 @@ class _ConversationTurn extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - ClideIcon(PhosphorIcons.image, size: 16, color: tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('image'), size: 16, color: tokens.globalTextMuted), const SizedBox(width: 8), Flexible( child: ClideText('could not load $path', fontSize: clideFontMeta, color: tokens.globalTextMuted, maxLines: 1), diff --git a/lib/builtin/claude/src/extension.dart b/lib/builtin/claude/src/extension.dart index 9ff900ed..b990423c 100644 --- a/lib/builtin/claude/src/extension.dart +++ b/lib/builtin/claude/src/extension.dart @@ -296,7 +296,7 @@ class ClaudeExtension extends ClideExtension { id: 'claude.meta', slot: Slots.sidebar, title: 'Activity', - icon: PhosphorIcons.robot, + icon: PhosphorIcons.byName('robot'), priority: 60, build: (_) => const ClaudeMetaSidebar(), ), diff --git a/lib/builtin/claude/src/image_thumbnail.dart b/lib/builtin/claude/src/image_thumbnail.dart index 00da2980..1261e033 100644 --- a/lib/builtin/claude/src/image_thumbnail.dart +++ b/lib/builtin/claude/src/image_thumbnail.dart @@ -36,7 +36,7 @@ Widget _placeholder(BuildContext context, double size) { height: size, color: t.panelBackground, alignment: Alignment.center, - child: ClideIcon(PhosphorIcons.image, size: size * 0.45, color: t.globalTextMuted), + child: ClideIcon(PhosphorIcons.byName('image'), size: size * 0.45, color: t.globalTextMuted), ); } diff --git a/lib/builtin/claude/src/permission_mode_control.dart b/lib/builtin/claude/src/permission_mode_control.dart index 31175396..1f0befe1 100644 --- a/lib/builtin/claude/src/permission_mode_control.dart +++ b/lib/builtin/claude/src/permission_mode_control.dart @@ -19,13 +19,13 @@ import 'package:flutter/widgets.dart'; ClideIconPainter permissionModeIcon(String mode) { switch (mode) { case 'acceptEdits': - return PhosphorIcons.pencilSimple; + return PhosphorIcons.byName('pencil-simple'); case 'plan': - return PhosphorIcons.listChecks; + return PhosphorIcons.byName('list-checks'); case 'bypassPermissions': - return PhosphorIcons.shieldWarning; + return PhosphorIcons.byName('shield-warning'); default: - return PhosphorIcons.shieldCheck; + return PhosphorIcons.byName('shield-check'); } } diff --git a/lib/builtin/claude/src/team_chat_sidebar.dart b/lib/builtin/claude/src/team_chat_sidebar.dart index 95f4c0b9..530f97f7 100644 --- a/lib/builtin/claude/src/team_chat_sidebar.dart +++ b/lib/builtin/claude/src/team_chat_sidebar.dart @@ -150,7 +150,7 @@ class _TeamChatSidebarState extends State { builder: (ctx, hovered, _) => Padding( padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 1), child: ClideIcon( - PhosphorIcons.arrowsOutSimple, + PhosphorIcons.byName('arrows-out-simple'), size: 10, color: hovered ? tokens.globalForeground : tokens.globalTextMuted, ), @@ -383,7 +383,7 @@ class _TeamChatPaneState extends State { ), child: _interrupt ? Center( - child: ClideIcon(PhosphorIcons.check, size: 9, color: tokens.globalFocus), + child: ClideIcon(PhosphorIcons.byName('check'), size: 9, color: tokens.globalFocus), ) : null, ), diff --git a/lib/builtin/decisions/src/decisions_view.dart b/lib/builtin/decisions/src/decisions_view.dart index ca1a0236..37df264c 100644 --- a/lib/builtin/decisions/src/decisions_view.dart +++ b/lib/builtin/decisions/src/decisions_view.dart @@ -157,7 +157,7 @@ class _DecisionsViewState extends State { onTap: _refreshing ? null : _refresh, tooltip: 'Refresh decisions', builder: (ctx, hovered, _) => - ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('arrow-clockwise'), size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ], diff --git a/lib/builtin/decisions/src/extension.dart b/lib/builtin/decisions/src/extension.dart index d3c20ba2..9dfed878 100644 --- a/lib/builtin/decisions/src/extension.dart +++ b/lib/builtin/decisions/src/extension.dart @@ -43,14 +43,14 @@ class DecisionsExtension extends ClideExtension { title: 'Decisions', titleKey: 'tab.title', i18nNamespace: id, - icon: PhosphorIcons.lightbulb, + icon: PhosphorIcons.byName('lightbulb'), build: (_) => const DecisionsView(), ), TabContribution( id: 'decisions.detail', slot: Slots.contextPanel, title: 'Decision', - icon: PhosphorIcons.lightbulb, + icon: PhosphorIcons.byName('lightbulb'), build: (_) => const DecisionDetailView(), ), ]; diff --git a/lib/builtin/files/src/extension.dart b/lib/builtin/files/src/extension.dart index 59b6be34..24111ab1 100644 --- a/lib/builtin/files/src/extension.dart +++ b/lib/builtin/files/src/extension.dart @@ -23,7 +23,7 @@ class FilesExtension extends ClideExtension { id: 'files.tree', slot: Slots.sidebar, title: 'Files', - icon: PhosphorIcons.folder, + icon: PhosphorIcons.byName('folder'), titleKey: 'tab.title', i18nNamespace: id, priority: -100, diff --git a/lib/builtin/git/src/extension.dart b/lib/builtin/git/src/extension.dart index ec9b523e..b6603620 100644 --- a/lib/builtin/git/src/extension.dart +++ b/lib/builtin/git/src/extension.dart @@ -20,7 +20,7 @@ class GitExtension extends ClideExtension { id: 'git.panel', slot: Slots.sidebar, title: 'Git', - icon: PhosphorIcons.gitBranch, + icon: PhosphorIcons.byName('git-branch'), titleKey: 'tab.title', i18nNamespace: id, priority: -80, diff --git a/lib/builtin/git/src/git_status_item.dart b/lib/builtin/git/src/git_status_item.dart index fb334fc7..ec9c757c 100644 --- a/lib/builtin/git/src/git_status_item.dart +++ b/lib/builtin/git/src/git_status_item.dart @@ -190,7 +190,7 @@ class _BranchPickerState extends State<_BranchPicker> { onTap: () => widget.onDismiss(), child: MouseRegion( cursor: SystemMouseCursors.click, - child: ClideIcon(PhosphorIcons.xMark, size: 12, color: tokens.globalTextMuted), + child: ClideIcon(PhosphorIcons.byName('x'), size: 12, color: tokens.globalTextMuted), ), ), ], diff --git a/lib/builtin/markdown/src/extension.dart b/lib/builtin/markdown/src/extension.dart index cc24dcf0..f32abf58 100644 --- a/lib/builtin/markdown/src/extension.dart +++ b/lib/builtin/markdown/src/extension.dart @@ -23,7 +23,7 @@ class MarkdownExtension extends ClideExtension { id: 'markdown.viewer', slot: Slots.contextPanel, title: 'Markdown', - icon: PhosphorIcons.fileText, + icon: PhosphorIcons.byName('file-text'), build: (_) => const MarkdownViewer(), ), ]; diff --git a/lib/builtin/pql/src/extension.dart b/lib/builtin/pql/src/extension.dart index 4b89b2fe..bfeaf838 100644 --- a/lib/builtin/pql/src/extension.dart +++ b/lib/builtin/pql/src/extension.dart @@ -21,7 +21,7 @@ class PqlExtension extends ClideExtension { id: 'pql.backlinks', slot: Slots.contextPanel, title: 'Links', - icon: PhosphorIcons.link, + icon: PhosphorIcons.byName('link'), priority: -80, build: (_) => const BacklinksView(), ), diff --git a/lib/builtin/search/src/extension.dart b/lib/builtin/search/src/extension.dart index 1268fc14..55a15ab0 100644 --- a/lib/builtin/search/src/extension.dart +++ b/lib/builtin/search/src/extension.dart @@ -23,7 +23,7 @@ class SearchExtension extends ClideExtension { id: 'search.findInFiles', slot: Slots.sidebar, title: 'Search', - icon: PhosphorIcons.magnifyingGlass, + icon: PhosphorIcons.byName('magnifying-glass'), priority: -90, build: (_) => const SearchPanelView(), ), diff --git a/lib/builtin/shared/reader_chrome.dart b/lib/builtin/shared/reader_chrome.dart index 051ea059..d045360f 100644 --- a/lib/builtin/shared/reader_chrome.dart +++ b/lib/builtin/shared/reader_chrome.dart @@ -51,7 +51,7 @@ class ReaderActionBar extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ _ActionButton( - painter: PhosphorIcons.caretLeft, + painter: PhosphorIcons.byName('caret-left'), tooltip: 'Back', enabled: canGoBack, onTap: canGoBack ? onBack : null, @@ -59,7 +59,7 @@ class ReaderActionBar extends StatelessWidget { ), const SizedBox(width: 2), _ActionButton( - painter: PhosphorIcons.caretRight, + painter: PhosphorIcons.byName('caret-right'), tooltip: 'Forward', enabled: canGoForward, onTap: canGoForward ? onForward : null, @@ -68,7 +68,7 @@ class ReaderActionBar extends StatelessWidget { if (hasPinned) ...[ const SizedBox(width: 2), _ActionButton( - painter: PhosphorIcons.arrowUUpLeft, + painter: PhosphorIcons.byName('arrow-u-up-left'), tooltip: 'Jump to pin', enabled: true, onTap: onJumpToPin, @@ -78,7 +78,7 @@ class ReaderActionBar extends StatelessWidget { if (onEdit != null) ...[ const SizedBox(width: 4), _ActionButton( - painter: PhosphorIcons.pencilSimple, + painter: PhosphorIcons.byName('pencil-simple'), tooltip: 'Edit in editor', enabled: true, onTap: onEdit, @@ -103,7 +103,7 @@ class ReaderPinButton extends StatelessWidget { @override Widget build(BuildContext context) { return _ActionButton( - painter: PhosphorIcons.pushPin, + painter: PhosphorIcons.byName('push-pin'), tooltip: pinned ? 'Unpin' : 'Pin', enabled: onTap != null, active: pinned, diff --git a/lib/builtin/theme_picker/src/theme_status_item.dart b/lib/builtin/theme_picker/src/theme_status_item.dart index 85973c56..a3302148 100644 --- a/lib/builtin/theme_picker/src/theme_status_item.dart +++ b/lib/builtin/theme_picker/src/theme_status_item.dart @@ -61,7 +61,7 @@ class _ThemeSwitcherStatusItemState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - ClideIcon(PhosphorIcons.palette, size: 13, color: tokens.statusBarForeground), + ClideIcon(PhosphorIcons.byName('palette'), size: 13, color: tokens.statusBarForeground), const SizedBox(width: 6), // The status bar is all-lowercase; the proper-case name stays // in the Semantics label for screen readers. diff --git a/lib/builtin/tickets/src/extension.dart b/lib/builtin/tickets/src/extension.dart index a3b09dd5..eba7106c 100644 --- a/lib/builtin/tickets/src/extension.dart +++ b/lib/builtin/tickets/src/extension.dart @@ -45,14 +45,14 @@ class TicketsExtension extends ClideExtension { title: 'Tickets', titleKey: 'tab.title', i18nNamespace: id, - icon: PhosphorIcons.ticket, + icon: PhosphorIcons.byName('ticket'), build: (_) => const TicketsView(), ), TabContribution( id: 'tickets.detail', slot: Slots.contextPanel, title: 'Ticket', - icon: PhosphorIcons.ticket, + icon: PhosphorIcons.byName('ticket'), build: (_) => const TicketDetailView(), ), ]; diff --git a/lib/builtin/tickets/src/tickets_view.dart b/lib/builtin/tickets/src/tickets_view.dart index 86d5db91..dd05af50 100644 --- a/lib/builtin/tickets/src/tickets_view.dart +++ b/lib/builtin/tickets/src/tickets_view.dart @@ -166,7 +166,7 @@ class _TicketsViewState extends State { onTap: _refreshing ? null : _refresh, tooltip: 'Refresh tickets', builder: (ctx, hovered, _) => - ClideIcon(PhosphorIcons.arrowClockwise, size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('arrow-clockwise'), size: 13, color: hovered ? tokens.globalForeground : tokens.globalTextMuted), ), ), ], diff --git a/lib/builtin/welcome/src/welcome_view.dart b/lib/builtin/welcome/src/welcome_view.dart index 986b777e..6152af3f 100644 --- a/lib/builtin/welcome/src/welcome_view.dart +++ b/lib/builtin/welcome/src/welcome_view.dart @@ -167,21 +167,21 @@ class _StartColumn extends StatelessWidget { ClideText('START', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily), const SizedBox(height: 20), _ActionRow( - icon: PhosphorIcons.folder, + icon: PhosphorIcons.byName('folder'), label: 'Open folder…', shortcut: '⌘O', tokens: tokens, onTap: () => _openFolder(context), ), _ActionRow( - icon: PhosphorIcons.gitBranch, + icon: PhosphorIcons.byName('git-branch'), label: 'Clone from git…', shortcut: '⌘G', tokens: tokens, onTap: () {}, ), _ActionRow( - icon: PhosphorIcons.chatCircle, + icon: PhosphorIcons.byName('chat-circle'), label: 'Start a Claude session', shortcut: '⌘C', tokens: tokens, @@ -332,7 +332,7 @@ class _RecentRow extends StatelessWidget { muted: true, fontSize: clideFontMeta, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)), if (project.branch != null) ...[ ClideText(' · ', muted: true, fontSize: clideFontMeta), - ClideIcon(PhosphorIcons.gitBranch, size: 11, color: tokens.globalTextMuted), + ClideIcon(PhosphorIcons.byName('git-branch'), size: 11, color: tokens.globalTextMuted), const SizedBox(width: 3), Flexible( child: ClideText(project.branch!, @@ -384,7 +384,7 @@ class _StickyToggle extends StatelessWidget { border: Border.all(color: hovered || sticky ? tokens.panelActiveBorder : tokens.globalBorder), borderRadius: BorderRadius.circular(3), ), - child: sticky ? ClideIcon(PhosphorIcons.check, size: 12, color: tokens.buttonForeground) : null, + child: sticky ? ClideIcon(PhosphorIcons.byName('check'), size: 12, color: tokens.buttonForeground) : null, ), ), ); diff --git a/lib/widgets/src/clide_accordion.dart b/lib/widgets/src/clide_accordion.dart index 1e426e33..8c8d2be2 100644 --- a/lib/widgets/src/clide_accordion.dart +++ b/lib/widgets/src/clide_accordion.dart @@ -36,7 +36,7 @@ class ClideAccordion extends StatelessWidget { padding: const EdgeInsets.only(left: 4, top: 10, bottom: 4), child: Row( children: [ - ClideIcon(expanded ? PhosphorIcons.caretDown : PhosphorIcons.caretRight, size: 10, color: tokens.globalTextMuted), + ClideIcon(expanded ? PhosphorIcons.byName('caret-down') : PhosphorIcons.byName('caret-right'), size: 10, color: tokens.globalTextMuted), const SizedBox(width: 6), if (leading != null) ...[leading!, const SizedBox(width: 6)], ClideText('$label · $count', diff --git a/lib/widgets/src/clide_column_hat.dart b/lib/widgets/src/clide_column_hat.dart index 722c65a4..907f9c0e 100644 --- a/lib/widgets/src/clide_column_hat.dart +++ b/lib/widgets/src/clide_column_hat.dart @@ -94,7 +94,7 @@ class _RightContent extends StatelessWidget { children: [ _WinButton(icon: const PhosphorIconPainter(0xe32a), onTap: wc.minimize, tokens: tokens), _WinButton(icon: const PhosphorIconPainter(0xe45e), onTap: wc.toggleMaximize, tokens: tokens), - _WinButton(icon: PhosphorIcons.xMark, onTap: wc.close, tokens: tokens, isClose: true), + _WinButton(icon: PhosphorIcons.byName('x'), onTap: wc.close, tokens: tokens, isClose: true), ], ); } diff --git a/lib/widgets/src/clide_filter_box.dart b/lib/widgets/src/clide_filter_box.dart index f99c490e..4d29b4f8 100644 --- a/lib/widgets/src/clide_filter_box.dart +++ b/lib/widgets/src/clide_filter_box.dart @@ -15,7 +15,8 @@ class ClideFilterBox extends StatefulWidget { this.hint = 'Filter…', this.debounce = const Duration(milliseconds: 200), this.onSubmitted, - this.icon = PhosphorIcons.magnifyingGlass, + this.icon, + this.showIcon = true, this.address, }); @@ -24,10 +25,16 @@ class ClideFilterBox extends StatefulWidget { final Duration debounce; final ValueChanged? onSubmitted; - /// Leading glyph. Defaults to the search glass; pass null for inputs - /// that aren't searches (e.g. a replace or glob field). + /// Leading glyph. Defaults to the search glass (resolved at build, since + /// [PhosphorIcons.byName] isn't const); pass a custom painter to override it, + /// or set [showIcon] = false for inputs that aren't searches (a replace/glob + /// field). final ClideIconPainter? icon; + /// Whether to draw a leading glyph. False draws a blank one but keeps the + /// slot's space, so non-search fields stay aligned with search ones. + final bool showIcon; + /// Makes this box addressable from the CLI (D-6 parity, T-270). When set, /// the box listens on the MessageBus `filter.set` channel for its address /// — so `clide ui filter
` drives it exactly as a UI @@ -123,10 +130,14 @@ class _ClideFilterBoxState extends State { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), child: Row( children: [ - if (widget.icon != null) ...[ - ClideIcon(widget.icon!, size: 13, color: tokens.globalTextMuted), - const SizedBox(width: 6), - ], + // The slot is always reserved (size + gap) so search and non-search + // boxes align; showIcon: false just draws a blank glyph (T-314). + ClideIcon( + widget.showIcon ? (widget.icon ?? PhosphorIcons.byName('magnifying-glass')) : const EmptyIconPainter(), + size: 13, + color: tokens.globalTextMuted, + ), + const SizedBox(width: 6), Expanded( child: Stack( alignment: Alignment.centerLeft, @@ -160,7 +171,7 @@ class _ClideFilterBoxState extends State { onTap: _clear, child: MouseRegion( cursor: SystemMouseCursors.click, - child: ClideIcon(PhosphorIcons.xMark, size: 11, color: tokens.globalTextMuted), + child: ClideIcon(PhosphorIcons.byName('x'), size: 11, color: tokens.globalTextMuted), ), ), ], diff --git a/lib/widgets/src/clide_icon.dart b/lib/widgets/src/clide_icon.dart index 4c560c9f..104e2369 100644 --- a/lib/widgets/src/clide_icon.dart +++ b/lib/widgets/src/clide_icon.dart @@ -11,6 +11,23 @@ abstract class ClideIconPainter { void paint(Canvas canvas, Color color); } +/// Draws nothing, but [ClideIcon] still reserves its `size×size` box — so a +/// caller can keep the icon slot for alignment without showing a glyph. This is +/// the *intentional* empty (e.g. a non-search filter field), distinct from an +/// unknown glyph name, which renders a visible `placeholder` error box (T-314). +class EmptyIconPainter extends ClideIconPainter { + const EmptyIconPainter(); + + @override + void paint(Canvas canvas, Color color) {} + + @override + bool operator ==(Object other) => other is EmptyIconPainter; + + @override + int get hashCode => (EmptyIconPainter).hashCode; +} + class ClideIcon extends StatelessWidget { const ClideIcon( this.painter, { diff --git a/lib/widgets/src/clide_lightbox.dart b/lib/widgets/src/clide_lightbox.dart index ba153258..8bf48788 100644 --- a/lib/widgets/src/clide_lightbox.dart +++ b/lib/widgets/src/clide_lightbox.dart @@ -114,7 +114,7 @@ class _ClideLightboxState extends State { Positioned( top: 8, right: 8, - child: _IconChip(icon: PhosphorIcons.xMark, label: 'close', onTap: widget.onDismiss, tokens: tokens), + child: _IconChip(icon: PhosphorIcons.byName('x'), label: 'close', onTap: widget.onDismiss, tokens: tokens), ), Positioned( bottom: 8, diff --git a/lib/widgets/src/clide_toast.dart b/lib/widgets/src/clide_toast.dart index 999cd2d7..1d342823 100644 --- a/lib/widgets/src/clide_toast.dart +++ b/lib/widgets/src/clide_toast.dart @@ -12,13 +12,13 @@ import 'package:flutter/widgets.dart'; ({Color color, ClideIconPainter icon}) _styleFor(ToastSeverity s, SurfaceTokens t) { switch (s) { case ToastSeverity.success: - return (color: t.statusSuccess, icon: PhosphorIcons.checkCircle); + return (color: t.statusSuccess, icon: PhosphorIcons.byName('check-circle')); case ToastSeverity.warning: - return (color: t.statusWarning, icon: PhosphorIcons.warningCircle); + return (color: t.statusWarning, icon: PhosphorIcons.byName('warning-circle')); case ToastSeverity.error: - return (color: t.statusError, icon: PhosphorIcons.warningCircle); + return (color: t.statusError, icon: PhosphorIcons.byName('warning-circle')); case ToastSeverity.info: - return (color: t.statusInfo, icon: PhosphorIcons.circlesFour); + return (color: t.statusInfo, icon: PhosphorIcons.byName('circles-four')); } } @@ -87,7 +87,7 @@ class _ClideToastState extends State { child: ClideTappable( onTap: widget.onDismiss, builder: (ctx, hovered, _) => ClideIcon( - PhosphorIcons.xMark, + PhosphorIcons.byName('x'), size: clideIconStandard, color: hovered ? tokens.globalForeground : tokens.globalTextMuted, ), diff --git a/lib/widgets/src/icons/phosphor.dart b/lib/widgets/src/icons/phosphor.dart index de5022ea..82e54806 100644 --- a/lib/widgets/src/icons/phosphor.dart +++ b/lib/widgets/src/icons/phosphor.dart @@ -1,6 +1,7 @@ import 'dart:ui' as ui; import 'package:clide/widgets/src/clide_icon.dart'; +import 'package:clide/widgets/src/icons/phosphor_glyphs.g.dart'; class PhosphorIconPainter extends ClideIconPainter { const PhosphorIconPainter(this.codePoint, {this.family = 'Phosphor'}); @@ -27,55 +28,23 @@ class PhosphorIconPainter extends ClideIconPainter { int get hashCode => Object.hash(codePoint, family); } +/// Phosphor glyphs, resolved by their exact kebab-case name (e.g. `folder`, +/// `folder-simple`, `git-branch`). The full label→codepoint table is the +/// generated [kPhosphorGlyphs]; raw codepoints live only there, so feature code +/// reads `PhosphorIcons.byName('folder')` rather than `0xe24a` (T-314). A +/// string key also lets a Lua extension name an icon without crossing the FFI +/// boundary with a codepoint. abstract class PhosphorIcons { - static const folder = PhosphorIconPainter(0xe24a); - static const fileText = PhosphorIconPainter(0xe23a); - static const gitBranch = PhosphorIconPainter(0xe278); - static const gitCommit = PhosphorIconPainter(0xe27a); - static const gitDiff = PhosphorIconPainter(0xe27c); - static const gitPullRequest = PhosphorIconPainter(0xe282); - static const magnifyingGlass = PhosphorIconPainter(0xe30c); - static const terminal = PhosphorIconPainter(0xe47e); - static const terminalWindow = PhosphorIconPainter(0xeae8); - static const code = PhosphorIconPainter(0xe1bc); - static const codeBlock = PhosphorIconPainter(0xeafe); - static const pencilSimple = PhosphorIconPainter(0xe3b4); - static const eye = PhosphorIconPainter(0xe220); - static const eyeSlash = PhosphorIconPainter(0xe224); - static const arrowClockwise = PhosphorIconPainter(0xe036); - static const arrowsOutSimple = PhosphorIconPainter(0xe0a6); - static const arrowsInSimple = PhosphorIconPainter(0xe09e); - static const list = PhosphorIconPainter(0xe2f0); - static const listChecks = PhosphorIconPainter(0xeadc); - static const gear = PhosphorIconPainter(0xe270); - static const puzzlePiece = PhosphorIconPainter(0xe596); - static const keyboard = PhosphorIconPainter(0xe2d8); - static const palette = PhosphorIconPainter(0xe6c8); - static const warning = PhosphorIconPainter(0xe4e0); - static const warningCircle = PhosphorIconPainter(0xe4e2); - static const shieldCheck = PhosphorIconPainter(0xe40c); - static const shieldWarning = PhosphorIconPainter(0xe412); - static const check = PhosphorIconPainter(0xe182); - static const checkCircle = PhosphorIconPainter(0xe184); - static const caretLeft = PhosphorIconPainter(0xe138); - static const caretRight = PhosphorIconPainter(0xe13a); - static const caretDown = PhosphorIconPainter(0xe136); - static const caretUp = PhosphorIconPainter(0xe13c); - // Chevron-with-edge-line: reads as "collapse to / expand from the edge" (T-294). - static const caretLineLeft = PhosphorIconPainter(0xe132); - static const caretLineRight = PhosphorIconPainter(0xe130); - static const graph = PhosphorIconPainter(0xeb58); - static const treeStructure = PhosphorIconPainter(0xe67c); - static const image = PhosphorIconPainter(0xe2ca); - static const link = PhosphorIconPainter(0xe2e2); - static const pushPin = PhosphorIconPainter(0xe3e2); - static const arrowUUpLeft = PhosphorIconPainter(0xe08a); - static const chatCircle = PhosphorIconPainter(0xe168); - static const robot = PhosphorIconPainter(0xe762); - static const ticket = PhosphorIconPainter(0xe490); - static const lightbulb = PhosphorIconPainter(0xe2dc); - static const notepad = PhosphorIconPainter(0xe63e); - static const bookOpen = PhosphorIconPainter(0xe0e6); - static const xMark = PhosphorIconPainter(0xe4f6); - static const circlesFour = PhosphorIconPainter(0xe190); + /// Shown when a name doesn't resolve. An unresolved name is a real error — a + /// wrong or missing label — so the fallback is `placeholder`: the box reads + /// as a render error and surfaces the bug honestly, rather than masking it as + /// an intentional "unknown" the way a `question` mark would. + static const String fallbackName = 'placeholder'; + + /// Resolve a glyph by its kebab-case [name]. A total function: an unknown + /// name degrades to [fallbackName] so a bad value (a typo, a stale setting, a + /// Lua extension's string) never crashes the UI. Typos in clide's own call + /// sites are caught at CI by `phosphor_glyphs_test`, which asserts every + /// `byName('…')` literal in `lib/` exists in the map. + static PhosphorIconPainter byName(String name) => PhosphorIconPainter(kPhosphorGlyphs[name] ?? kPhosphorGlyphs[fallbackName]!); } diff --git a/lib/widgets/src/icons/phosphor_glyphs.g.dart b/lib/widgets/src/icons/phosphor_glyphs.g.dart new file mode 100644 index 00000000..77f4d350 --- /dev/null +++ b/lib/widgets/src/icons/phosphor_glyphs.g.dart @@ -0,0 +1,1522 @@ +// GENERATED by tool/gen_phosphor_glyphs.dart — do not edit by hand. +// Source: .claude/skills/ui-design/references/phosphor-glyphs.md (1512 glyphs). +// +// The single label→codepoint lookup for the bundled Phosphor font. Code +// references glyphs by their exact kebab-case name via PhosphorIcons.byName; +// raw codepoints live only here. +library; + +const Map kPhosphorGlyphs = { + 'acorn': 0xeb9a, + 'address-book': 0xe6f8, + 'address-book-tabs': 0xee4e, + 'air-traffic-control': 0xecd8, + 'airplane': 0xe002, + 'airplane-in-flight': 0xe4fe, + 'airplane-landing': 0xe502, + 'airplane-takeoff': 0xe504, + 'airplane-taxiing': 0xe500, + 'airplane-tilt': 0xe5d6, + 'airplay': 0xe004, + 'alarm': 0xe006, + 'alien': 0xe8a6, + 'align-bottom': 0xe506, + 'align-bottom-simple': 0xeb0c, + 'align-center-horizontal': 0xe50a, + 'align-center-horizontal-simple': 0xeb0e, + 'align-center-vertical': 0xe50c, + 'align-center-vertical-simple': 0xeb10, + 'align-left': 0xe50e, + 'align-left-simple': 0xeaee, + 'align-right': 0xe510, + 'align-right-simple': 0xeb12, + 'align-top': 0xe512, + 'align-top-simple': 0xeb14, + 'amazon-logo': 0xe96c, + 'ambulance': 0xe572, + 'anchor': 0xe514, + 'anchor-simple': 0xe5d8, + 'android-logo': 0xe008, + 'angle': 0xe7bc, + 'angular-logo': 0xeb80, + 'aperture': 0xe00a, + 'app-store-logo': 0xe974, + 'app-window': 0xe5da, + 'apple-logo': 0xe516, + 'apple-podcasts-logo': 0xeb96, + 'approximate-equals': 0xedaa, + 'archive': 0xe00c, + 'armchair': 0xe012, + 'arrow-arc-left': 0xe014, + 'arrow-arc-right': 0xe016, + 'arrow-bend-double-up-left': 0xe03a, + 'arrow-bend-double-up-right': 0xe03c, + 'arrow-bend-down-left': 0xe018, + 'arrow-bend-down-right': 0xe01a, + 'arrow-bend-left-down': 0xe01c, + 'arrow-bend-left-up': 0xe01e, + 'arrow-bend-right-down': 0xe020, + 'arrow-bend-right-up': 0xe022, + 'arrow-bend-up-left': 0xe024, + 'arrow-bend-up-right': 0xe026, + 'arrow-circle-down': 0xe028, + 'arrow-circle-down-left': 0xe02a, + 'arrow-circle-down-right': 0xe02c, + 'arrow-circle-left': 0xe05a, + 'arrow-circle-right': 0xe02e, + 'arrow-circle-up': 0xe030, + 'arrow-circle-up-left': 0xe032, + 'arrow-circle-up-right': 0xe034, + 'arrow-clockwise': 0xe036, + 'arrow-counter-clockwise': 0xe038, + 'arrow-down': 0xe03e, + 'arrow-down-left': 0xe040, + 'arrow-down-right': 0xe042, + 'arrow-elbow-down-left': 0xe044, + 'arrow-elbow-down-right': 0xe046, + 'arrow-elbow-left': 0xe048, + 'arrow-elbow-left-down': 0xe04a, + 'arrow-elbow-left-up': 0xe04c, + 'arrow-elbow-right': 0xe04e, + 'arrow-elbow-right-down': 0xe050, + 'arrow-elbow-right-up': 0xe052, + 'arrow-elbow-up-left': 0xe054, + 'arrow-elbow-up-right': 0xe056, + 'arrow-fat-down': 0xe518, + 'arrow-fat-left': 0xe51a, + 'arrow-fat-line-down': 0xe51c, + 'arrow-fat-line-left': 0xe51e, + 'arrow-fat-line-right': 0xe520, + 'arrow-fat-line-up': 0xe522, + 'arrow-fat-lines-down': 0xe524, + 'arrow-fat-lines-left': 0xe526, + 'arrow-fat-lines-right': 0xe528, + 'arrow-fat-lines-up': 0xe52a, + 'arrow-fat-right': 0xe52c, + 'arrow-fat-up': 0xe52e, + 'arrow-left': 0xe058, + 'arrow-line-down': 0xe05c, + 'arrow-line-down-left': 0xe05e, + 'arrow-line-down-right': 0xe060, + 'arrow-line-left': 0xe062, + 'arrow-line-right': 0xe064, + 'arrow-line-up': 0xe066, + 'arrow-line-up-left': 0xe068, + 'arrow-line-up-right': 0xe06a, + 'arrow-right': 0xe06c, + 'arrow-square-down': 0xe06e, + 'arrow-square-down-left': 0xe070, + 'arrow-square-down-right': 0xe072, + 'arrow-square-in': 0xe5dc, + 'arrow-square-left': 0xe074, + 'arrow-square-out': 0xe5de, + 'arrow-square-right': 0xe076, + 'arrow-square-up': 0xe078, + 'arrow-square-up-left': 0xe07a, + 'arrow-square-up-right': 0xe07c, + 'arrow-u-down-left': 0xe07e, + 'arrow-u-down-right': 0xe080, + 'arrow-u-left-down': 0xe082, + 'arrow-u-left-up': 0xe084, + 'arrow-u-right-down': 0xe086, + 'arrow-u-right-up': 0xe088, + 'arrow-u-up-left': 0xe08a, + 'arrow-u-up-right': 0xe08c, + 'arrow-up': 0xe08e, + 'arrow-up-left': 0xe090, + 'arrow-up-right': 0xe092, + 'arrows-clockwise': 0xe094, + 'arrows-counter-clockwise': 0xe096, + 'arrows-down-up': 0xe098, + 'arrows-horizontal': 0xeb06, + 'arrows-in': 0xe09a, + 'arrows-in-cardinal': 0xe09c, + 'arrows-in-line-horizontal': 0xe530, + 'arrows-in-line-vertical': 0xe532, + 'arrows-in-simple': 0xe09e, + 'arrows-left-right': 0xe0a0, + 'arrows-merge': 0xed3e, + 'arrows-out': 0xe0a2, + 'arrows-out-cardinal': 0xe0a4, + 'arrows-out-line-horizontal': 0xe534, + 'arrows-out-line-vertical': 0xe536, + 'arrows-out-simple': 0xe0a6, + 'arrows-split': 0xed3c, + 'arrows-vertical': 0xeb04, + 'article': 0xe0a8, + 'article-medium': 0xe5e0, + 'article-ny-times': 0xe5e2, + 'asclepius': 0xee34, + 'asterisk': 0xe0aa, + 'asterisk-simple': 0xe832, + 'at': 0xe0ac, + 'atom': 0xe5e4, + 'avocado': 0xee04, + 'axe': 0xe9fc, + 'baby': 0xe774, + 'baby-carriage': 0xe818, + 'backpack': 0xe922, + 'backspace': 0xe0ae, + 'bag': 0xe0b0, + 'bag-simple': 0xe5e6, + 'balloon': 0xe76c, + 'bandaids': 0xe0b2, + 'bank': 0xe0b4, + 'barbell': 0xe0b6, + 'barcode': 0xe0b8, + 'barn': 0xec72, + 'barricade': 0xe948, + 'baseball': 0xe71a, + 'baseball-cap': 0xea28, + 'baseball-helmet': 0xee4a, + 'basket': 0xe964, + 'basketball': 0xe724, + 'bathtub': 0xe81e, + 'battery-charging': 0xe0ba, + 'battery-charging-vertical': 0xe0bc, + 'battery-empty': 0xe0be, + 'battery-full': 0xe0c0, + 'battery-high': 0xe0c2, + 'battery-low': 0xe0c4, + 'battery-medium': 0xe0c6, + 'battery-plus': 0xe808, + 'battery-plus-vertical': 0xec50, + 'battery-vertical-empty': 0xe7c6, + 'battery-vertical-full': 0xe7c4, + 'battery-vertical-high': 0xe7c2, + 'battery-vertical-low': 0xe7be, + 'battery-vertical-medium': 0xe7c0, + 'battery-warning': 0xe0c8, + 'battery-warning-vertical': 0xe0ca, + 'beach-ball': 0xed24, + 'beanie': 0xea2a, + 'bed': 0xe0cc, + 'beer-bottle': 0xe7b0, + 'beer-stein': 0xeb62, + 'behance-logo': 0xe7f4, + 'bell': 0xe0ce, + 'bell-ringing': 0xe5e8, + 'bell-simple': 0xe0d0, + 'bell-simple-ringing': 0xe5ea, + 'bell-simple-slash': 0xe0d2, + 'bell-simple-z': 0xe5ec, + 'bell-slash': 0xe0d4, + 'bell-z': 0xe5ee, + 'belt': 0xea2c, + 'bezier-curve': 0xeb00, + 'bicycle': 0xe0d6, + 'binary': 0xee60, + 'binoculars': 0xea64, + 'biohazard': 0xe9e0, + 'bird': 0xe72c, + 'blueprint': 0xeda0, + 'bluetooth': 0xe0da, + 'bluetooth-connected': 0xe0dc, + 'bluetooth-slash': 0xe0de, + 'bluetooth-x': 0xe0e0, + 'boat': 0xe786, + 'bomb': 0xee0a, + 'bone': 0xe7f2, + 'book': 0xe0e2, + 'book-bookmark': 0xe0e4, + 'book-open': 0xe0e6, + 'book-open-text': 0xe8f2, + 'book-open-user': 0xede0, + 'bookmark': 0xe0e8, + 'bookmark-simple': 0xe0ea, + 'bookmarks': 0xe0ec, + 'bookmarks-simple': 0xe5f0, + 'books': 0xe758, + 'boot': 0xecca, + 'boules': 0xe722, + 'bounding-box': 0xe6ce, + 'bowl-food': 0xeaa4, + 'bowl-steam': 0xe8e4, + 'bowling-ball': 0xea34, + 'box-arrow-down': 0xe00e, + 'box-arrow-up': 0xee54, + 'boxing-glove': 0xea36, + 'brackets-angle': 0xe862, + 'brackets-curly': 0xe860, + 'brackets-round': 0xe864, + 'brackets-square': 0xe85e, + 'brain': 0xe74e, + 'brandy': 0xe6b4, + 'bread': 0xe81c, + 'bridge': 0xea68, + 'briefcase': 0xe0ee, + 'briefcase-metal': 0xe5f2, + 'broadcast': 0xe0f2, + 'broom': 0xec54, + 'browser': 0xe0f4, + 'browsers': 0xe0f6, + 'bug': 0xe5f4, + 'bug-beetle': 0xe5f6, + 'bug-droid': 0xe5f8, + 'building': 0xe100, + 'building-apartment': 0xe0fe, + 'building-office': 0xe0fe, + 'buildings': 0xe102, + 'bulldozer': 0xec6c, + 'bus': 0xe106, + 'butterfly': 0xea6e, + 'cable-car': 0xe49c, + 'cactus': 0xe918, + 'cake': 0xe780, + 'calculator': 0xe538, + 'calendar': 0xe108, + 'calendar-blank': 0xe10a, + 'calendar-check': 0xe712, + 'calendar-dot': 0xe7b2, + 'calendar-dots': 0xe7b4, + 'calendar-heart': 0xe8b0, + 'calendar-minus': 0xea14, + 'calendar-plus': 0xe714, + 'calendar-slash': 0xea12, + 'calendar-star': 0xe8b2, + 'calendar-x': 0xe10c, + 'call-bell': 0xe7de, + 'camera': 0xe10e, + 'camera-plus': 0xec58, + 'camera-rotate': 0xe7a4, + 'camera-slash': 0xe110, + 'campfire': 0xe9d8, + 'car': 0xe112, + 'car-battery': 0xee30, + 'car-profile': 0xe8cc, + 'car-simple': 0xe114, + 'cardholder': 0xe5fa, + 'cards': 0xe0f8, + 'cards-three': 0xee50, + 'caret-circle-double-down': 0xe116, + 'caret-circle-double-left': 0xe118, + 'caret-circle-double-right': 0xe11a, + 'caret-circle-double-up': 0xe11c, + 'caret-circle-down': 0xe11e, + 'caret-circle-left': 0xe120, + 'caret-circle-right': 0xe122, + 'caret-circle-up': 0xe124, + 'caret-circle-up-down': 0xe13e, + 'caret-double-down': 0xe126, + 'caret-double-left': 0xe128, + 'caret-double-right': 0xe12a, + 'caret-double-up': 0xe12c, + 'caret-down': 0xe136, + 'caret-left': 0xe138, + 'caret-line-down': 0xe134, + 'caret-line-left': 0xe132, + 'caret-line-right': 0xe130, + 'caret-line-up': 0xe12e, + 'caret-right': 0xe13a, + 'caret-up': 0xe13c, + 'caret-up-down': 0xe140, + 'carrot': 0xed38, + 'cash-register': 0xed80, + 'cassette-tape': 0xed2e, + 'castle-turret': 0xe9d0, + 'cat': 0xe748, + 'cell-signal-full': 0xe142, + 'cell-signal-high': 0xe144, + 'cell-signal-low': 0xe146, + 'cell-signal-medium': 0xe148, + 'cell-signal-none': 0xe14a, + 'cell-signal-slash': 0xe14c, + 'cell-signal-x': 0xe14e, + 'cell-tower': 0xebaa, + 'certificate': 0xe766, + 'chair': 0xe950, + 'chalkboard': 0xe5fc, + 'chalkboard-simple': 0xe5fe, + 'chalkboard-teacher': 0xe600, + 'champagne': 0xeaca, + 'charging-station': 0xe8d0, + 'chart-bar': 0xe150, + 'chart-bar-horizontal': 0xe152, + 'chart-donut': 0xeaa6, + 'chart-line': 0xe154, + 'chart-line-down': 0xe8b6, + 'chart-line-up': 0xe156, + 'chart-pie': 0xe158, + 'chart-pie-slice': 0xe15a, + 'chart-polar': 0xeaa8, + 'chart-scatter': 0xeaac, + 'chat': 0xe15c, + 'chat-centered': 0xe160, + 'chat-centered-dots': 0xe164, + 'chat-centered-slash': 0xe162, + 'chat-centered-text': 0xe166, + 'chat-circle': 0xe168, + 'chat-circle-dots': 0xe16c, + 'chat-circle-slash': 0xe16a, + 'chat-circle-text': 0xe16e, + 'chat-dots': 0xe170, + 'chat-slash': 0xe15e, + 'chat-teardrop': 0xe172, + 'chat-teardrop-dots': 0xe176, + 'chat-teardrop-slash': 0xe174, + 'chat-teardrop-text': 0xe178, + 'chat-text': 0xe17a, + 'chats': 0xe17c, + 'chats-circle': 0xe17e, + 'chats-teardrop': 0xe180, + 'check': 0xe182, + 'check-circle': 0xe184, + 'check-fat': 0xeba6, + 'check-square': 0xe186, + 'check-square-offset': 0xe188, + 'checkerboard': 0xe8c4, + 'checks': 0xe53a, + 'cheers': 0xea4a, + 'cheese': 0xe9fe, + 'chef-hat': 0xed8e, + 'cherries': 0xe830, + 'church': 0xecea, + 'cigarette': 0xed90, + 'cigarette-slash': 0xed92, + 'circle': 0xe18a, + 'circle-dashed': 0xe602, + 'circle-half': 0xe18c, + 'circle-half-tilt': 0xe18e, + 'circle-notch': 0xeb44, + 'circles-four': 0xe190, + 'circles-three': 0xe192, + 'circles-three-plus': 0xe194, + 'circuitry': 0xe9c2, + 'city': 0xea6a, + 'clipboard': 0xe196, + 'clipboard-text': 0xe198, + 'clock': 0xe19a, + 'clock-afternoon': 0xe19c, + 'clock-clockwise': 0xe19e, + 'clock-countdown': 0xed2c, + 'clock-counter-clockwise': 0xe1a0, + 'clock-user': 0xedec, + 'closed-captioning': 0xe1a4, + 'cloud': 0xe1aa, + 'cloud-arrow-down': 0xe1ac, + 'cloud-arrow-up': 0xe1ae, + 'cloud-check': 0xe1b0, + 'cloud-fog': 0xe53c, + 'cloud-lightning': 0xe1b2, + 'cloud-moon': 0xe53e, + 'cloud-rain': 0xe1b4, + 'cloud-slash': 0xe1b6, + 'cloud-snow': 0xe1b8, + 'cloud-sun': 0xe540, + 'cloud-warning': 0xea98, + 'cloud-x': 0xea96, + 'clover': 0xedc8, + 'club': 0xe1ba, + 'coat-hanger': 0xe7fe, + 'coda-logo': 0xe7ce, + 'code': 0xe1bc, + 'code-block': 0xeafe, + 'code-simple': 0xe1be, + 'codepen-logo': 0xe978, + 'codesandbox-logo': 0xea06, + 'coffee': 0xe1c2, + 'coffee-bean': 0xe1c0, + 'coin': 0xe60e, + 'coin-vertical': 0xeb48, + 'coins': 0xe78e, + 'columns': 0xe546, + 'columns-plus-left': 0xe544, + 'columns-plus-right': 0xe542, + 'command': 0xe1c4, + 'compass': 0xe1c8, + 'compass-rose': 0xe1c6, + 'compass-tool': 0xea0e, + 'computer-tower': 0xe548, + 'confetti': 0xe81a, + 'contactless-payment': 0xed42, + 'control': 0xeca6, + 'cookie': 0xe6ca, + 'cooking-pot': 0xe764, + 'copy': 0xe1ca, + 'copy-simple': 0xe1cc, + 'copyleft': 0xe86a, + 'copyright': 0xe54a, + 'corners-in': 0xe1ce, + 'corners-out': 0xe1d0, + 'couch': 0xe7f6, + 'court-basketball': 0xee36, + 'cow': 0xeabe, + 'cowboy-hat': 0xed12, + 'cpu': 0xe610, + 'crane': 0xed48, + 'crane-tower': 0xed48, + 'credit-card': 0xe1d2, + 'cricket': 0xee12, + 'crop': 0xe1d4, + 'cross': 0xe8a0, + 'crosshair': 0xe1d6, + 'crosshair-simple': 0xe1d8, + 'crown': 0xe614, + 'crown-cross': 0xee5e, + 'crown-simple': 0xe616, + 'cube': 0xe1da, + 'cube-focus': 0xed0a, + 'cube-transparent': 0xec7c, + 'currency-btc': 0xe618, + 'currency-circle-dollar': 0xe54c, + 'currency-cny': 0xe54e, + 'currency-dollar': 0xe550, + 'currency-dollar-simple': 0xe552, + 'currency-eth': 0xeada, + 'currency-eur': 0xe554, + 'currency-gbp': 0xe556, + 'currency-inr': 0xe558, + 'currency-jpy': 0xe55a, + 'currency-krw': 0xe55c, + 'currency-kzt': 0xec4c, + 'currency-ngn': 0xeb52, + 'currency-rub': 0xe55e, + 'cursor': 0xe1dc, + 'cursor-click': 0xe7c8, + 'cursor-text': 0xe7d8, + 'cylinder': 0xe8fc, + 'database': 0xe1de, + 'desk': 0xed16, + 'desktop': 0xe560, + 'desktop-tower': 0xe562, + 'detective': 0xe83e, + 'dev-to-logo': 0xed0e, + 'device-mobile': 0xe1e0, + 'device-mobile-camera': 0xe1e2, + 'device-mobile-slash': 0xee46, + 'device-mobile-speaker': 0xe1e4, + 'device-rotate': 0xedf2, + 'device-tablet': 0xe1e6, + 'device-tablet-camera': 0xe1e8, + 'device-tablet-speaker': 0xe1ea, + 'devices': 0xeba4, + 'diamond': 0xe1ec, + 'diamonds-four': 0xe8f4, + 'dice-five': 0xe1ee, + 'dice-four': 0xe1f0, + 'dice-one': 0xe1f2, + 'dice-six': 0xe1f4, + 'dice-three': 0xe1f6, + 'dice-two': 0xe1f8, + 'disc': 0xe564, + 'disco-ball': 0xed98, + 'discord-logo': 0xe61a, + 'divide': 0xe1fa, + 'dna': 0xe924, + 'dog': 0xe74a, + 'door': 0xe61c, + 'door-open': 0xe7e6, + 'dot': 0xecde, + 'dot-outline': 0xece0, + 'dots-nine': 0xe1fc, + 'dots-six': 0xe794, + 'dots-six-vertical': 0xeae2, + 'dots-three': 0xe1fe, + 'dots-three-circle': 0xe200, + 'dots-three-circle-vertical': 0xe202, + 'dots-three-outline': 0xe204, + 'dots-three-outline-vertical': 0xe206, + 'dots-three-vertical': 0xe208, + 'download': 0xe20a, + 'download-simple': 0xe20c, + 'dress': 0xea7e, + 'dresser': 0xe94e, + 'dribbble-logo': 0xe20e, + 'drone': 0xed74, + 'drop': 0xe210, + 'drop-half': 0xe566, + 'drop-half-bottom': 0xeb40, + 'drop-simple': 0xee32, + 'drop-slash': 0xe954, + 'dropbox-logo': 0xe7d0, + 'ear': 0xe70c, + 'ear-slash': 0xe70e, + 'egg': 0xe812, + 'egg-crack': 0xeb64, + 'eject': 0xe212, + 'eject-simple': 0xe6ae, + 'elevator': 0xecc0, + 'empty': 0xedbc, + 'engine': 0xea80, + 'envelope': 0xe214, + 'envelope-open': 0xe216, + 'envelope-simple': 0xe218, + 'envelope-simple-open': 0xe21a, + 'equalizer': 0xebbc, + 'equals': 0xe21c, + 'eraser': 0xe21e, + 'escalator-down': 0xecba, + 'escalator-up': 0xecbc, + 'exam': 0xe742, + 'exclamation-mark': 0xee44, + 'exclude': 0xe882, + 'exclude-square': 0xe880, + 'export': 0xeaf0, + 'eye': 0xe220, + 'eye-closed': 0xe222, + 'eye-slash': 0xe224, + 'eyedropper': 0xe568, + 'eyedropper-sample': 0xeac4, + 'eyeglasses': 0xe7ba, + 'eyes': 0xee5c, + 'face-mask': 0xe56a, + 'facebook-logo': 0xe226, + 'factory': 0xe760, + 'faders': 0xe228, + 'faders-horizontal': 0xe22a, + 'fallout-shelter': 0xe9de, + 'fan': 0xe9f2, + 'farm': 0xec70, + 'fast-forward': 0xe6a6, + 'fast-forward-circle': 0xe22c, + 'feather': 0xe9c0, + 'fediverse-logo': 0xed66, + 'figma-logo': 0xe22e, + 'file': 0xe230, + 'file-archive': 0xeb2a, + 'file-arrow-down': 0xe232, + 'file-arrow-up': 0xe61e, + 'file-audio': 0xea20, + 'file-c': 0xeb32, + 'file-c-sharp': 0xeb30, + 'file-cloud': 0xe95e, + 'file-code': 0xe914, + 'file-cpp': 0xeb2e, + 'file-css': 0xeb34, + 'file-csv': 0xeb1c, + 'file-dashed': 0xe704, + 'file-doc': 0xeb1e, + 'file-html': 0xeb38, + 'file-image': 0xea24, + 'file-ini': 0xeb32, + 'file-jpg': 0xeb1a, + 'file-js': 0xeb24, + 'file-jsx': 0xeb3a, + 'file-lock': 0xe95c, + 'file-magnifying-glass': 0xe238, + 'file-md': 0xed50, + 'file-minus': 0xe234, + 'file-pdf': 0xe702, + 'file-plus': 0xe236, + 'file-png': 0xeb18, + 'file-ppt': 0xeb20, + 'file-py': 0xeb2c, + 'file-rs': 0xeb28, + 'file-sql': 0xed4e, + 'file-svg': 0xed08, + 'file-text': 0xe23a, + 'file-ts': 0xeb26, + 'file-tsx': 0xeb3c, + 'file-txt': 0xeb32, + 'file-video': 0xea22, + 'file-vue': 0xeb3e, + 'file-x': 0xe23c, + 'file-xls': 0xeb22, + 'file-zip': 0xe958, + 'files': 0xe710, + 'film-reel': 0xe8c0, + 'film-script': 0xeb50, + 'film-slate': 0xe8c2, + 'film-strip': 0xe792, + 'fingerprint': 0xe23e, + 'fingerprint-simple': 0xe240, + 'finn-the-human': 0xe56c, + 'fire': 0xe242, + 'fire-extinguisher': 0xe9e8, + 'fire-simple': 0xe620, + 'fire-truck': 0xe574, + 'first-aid': 0xe56e, + 'first-aid-kit': 0xe570, + 'fish': 0xe728, + 'fish-simple': 0xe72a, + 'flag': 0xe244, + 'flag-banner': 0xe622, + 'flag-banner-fold': 0xecf2, + 'flag-checkered': 0xea38, + 'flag-pennant': 0xecf0, + 'flame': 0xe624, + 'flashlight': 0xe246, + 'flask': 0xe79e, + 'flip-horizontal': 0xed6a, + 'flip-vertical': 0xed6c, + 'floppy-disk': 0xe248, + 'floppy-disk-back': 0xeaf4, + 'flow-arrow': 0xe6ec, + 'flower': 0xe75e, + 'flower-lotus': 0xe6cc, + 'flower-tulip': 0xeacc, + 'flying-saucer': 0xeb4a, + 'folder': 0xe24a, + 'folder-dashed': 0xe8f8, + 'folder-lock': 0xea3c, + 'folder-minus': 0xe254, + 'folder-open': 0xe256, + 'folder-plus': 0xe258, + 'folder-simple': 0xe25a, + 'folder-simple-dashed': 0xec2a, + 'folder-simple-lock': 0xeb5e, + 'folder-simple-minus': 0xe25c, + 'folder-simple-plus': 0xe25e, + 'folder-simple-star': 0xec2e, + 'folder-simple-user': 0xeb60, + 'folder-star': 0xea86, + 'folder-user': 0xeb46, + 'folders': 0xe260, + 'football': 0xe718, + 'football-helmet': 0xee4c, + 'footprints': 0xea88, + 'fork-knife': 0xe262, + 'four-k': 0xea5c, + 'frame-corners': 0xe626, + 'framer-logo': 0xe264, + 'function': 0xebe4, + 'funnel': 0xe266, + 'funnel-simple': 0xe268, + 'funnel-simple-x': 0xe26a, + 'funnel-x': 0xe26c, + 'game-controller': 0xe26e, + 'garage': 0xecd6, + 'gas-can': 0xe8ce, + 'gas-pump': 0xe768, + 'gauge': 0xe628, + 'gavel': 0xea32, + 'gear': 0xe270, + 'gear-fine': 0xe87c, + 'gear-six': 0xe272, + 'gender-female': 0xe6e0, + 'gender-intersex': 0xe6e6, + 'gender-male': 0xe6e2, + 'gender-neuter': 0xe6ea, + 'gender-nonbinary': 0xe6e4, + 'gender-transgender': 0xe6e8, + 'ghost': 0xe62a, + 'gif': 0xe274, + 'gift': 0xe276, + 'git-branch': 0xe278, + 'git-commit': 0xe27a, + 'git-diff': 0xe27c, + 'git-fork': 0xe27e, + 'git-merge': 0xe280, + 'git-pull-request': 0xe282, + 'github-logo': 0xe576, + 'gitlab-logo': 0xe694, + 'gitlab-logo-simple': 0xe696, + 'globe': 0xe288, + 'globe-hemisphere-east': 0xe28a, + 'globe-hemisphere-west': 0xe28c, + 'globe-simple': 0xe28e, + 'globe-simple-x': 0xe284, + 'globe-stand': 0xe290, + 'globe-x': 0xe286, + 'goggles': 0xecb4, + 'golf': 0xea3e, + 'goodreads-logo': 0xed10, + 'google-cardboard-logo': 0xe7b6, + 'google-chrome-logo': 0xe976, + 'google-drive-logo': 0xe8f6, + 'google-logo': 0xe292, + 'google-photos-logo': 0xeb92, + 'google-play-logo': 0xe294, + 'google-podcasts-logo': 0xeb94, + 'gps': 0xedd8, + 'gps-fix': 0xedd6, + 'gps-slash': 0xedd4, + 'gradient': 0xeb42, + 'graduation-cap': 0xe62c, + 'grains': 0xec68, + 'grains-slash': 0xec6a, + 'graph': 0xeb58, + 'graphics-card': 0xe612, + 'greater-than': 0xedc4, + 'greater-than-or-equal': 0xeda2, + 'grid-four': 0xe296, + 'grid-nine': 0xec8c, + 'guitar': 0xea8a, + 'hair-dryer': 0xea66, + 'hamburger': 0xe790, + 'hammer': 0xe80e, + 'hand': 0xe298, + 'hand-arrow-down': 0xea4e, + 'hand-arrow-up': 0xee5a, + 'hand-coins': 0xea8c, + 'hand-deposit': 0xee82, + 'hand-eye': 0xea4c, + 'hand-fist': 0xe57a, + 'hand-grabbing': 0xe57c, + 'hand-heart': 0xe810, + 'hand-palm': 0xe57e, + 'hand-peace': 0xe7cc, + 'hand-pointing': 0xe29a, + 'hand-soap': 0xe630, + 'hand-swipe-left': 0xec94, + 'hand-swipe-right': 0xec92, + 'hand-tap': 0xec90, + 'hand-waving': 0xe580, + 'hand-withdraw': 0xee80, + 'handbag': 0xe29c, + 'handbag-simple': 0xe62e, + 'hands-clapping': 0xe6a0, + 'hands-praying': 0xecc8, + 'handshake': 0xe582, + 'hard-drive': 0xe29e, + 'hard-drives': 0xe2a0, + 'hard-hat': 0xed46, + 'hash': 0xe2a2, + 'hash-straight': 0xe2a4, + 'head-circuit': 0xe7d4, + 'headlights': 0xe6fe, + 'headphones': 0xe2a6, + 'headset': 0xe584, + 'heart': 0xe2a8, + 'heart-break': 0xebe8, + 'heart-half': 0xec48, + 'heart-straight': 0xe2aa, + 'heart-straight-break': 0xeb98, + 'heartbeat': 0xe2ac, + 'hexagon': 0xe2ae, + 'high-definition': 0xea8e, + 'high-heel': 0xe8e8, + 'highlighter': 0xec76, + 'highlighter-circle': 0xe632, + 'hockey': 0xec86, + 'hoodie': 0xecd0, + 'horse': 0xe2b0, + 'hospital': 0xe844, + 'hourglass': 0xe2b2, + 'hourglass-high': 0xe2b4, + 'hourglass-low': 0xe2b6, + 'hourglass-medium': 0xe2b8, + 'hourglass-simple': 0xe2ba, + 'hourglass-simple-high': 0xe2bc, + 'hourglass-simple-low': 0xe2be, + 'hourglass-simple-medium': 0xe2c0, + 'house': 0xe2c2, + 'house-line': 0xe2c4, + 'house-simple': 0xe2c6, + 'hurricane': 0xe88e, + 'ice-cream': 0xe804, + 'identification-badge': 0xe6f6, + 'identification-card': 0xe2c8, + 'image': 0xe2ca, + 'image-broken': 0xe7a8, + 'image-square': 0xe2cc, + 'images': 0xe836, + 'images-square': 0xe834, + 'infinity': 0xe634, + 'info': 0xe2ce, + 'instagram-logo': 0xe2d0, + 'intersect': 0xe2d2, + 'intersect-square': 0xe87a, + 'intersect-three': 0xecc4, + 'intersection': 0xedba, + 'invoice': 0xee42, + 'island': 0xee06, + 'jar': 0xe7e0, + 'jar-label': 0xe7e0, + 'jeep': 0xe2d4, + 'joystick': 0xea5e, + 'kanban': 0xeb54, + 'key': 0xe2d6, + 'key-return': 0xe782, + 'keyboard': 0xe2d8, + 'keyhole': 0xea78, + 'knife': 0xe636, + 'ladder': 0xe9e4, + 'ladder-simple': 0xec26, + 'lamp': 0xe638, + 'lamp-pendant': 0xee2e, + 'laptop': 0xe586, + 'lasso': 0xedc6, + 'lastfm-logo': 0xe842, + 'layout': 0xe6d6, + 'leaf': 0xe2da, + 'lectern': 0xe95a, + 'lego': 0xe8c6, + 'lego-smiley': 0xe8c6, + 'less-than': 0xedac, + 'less-than-or-equal': 0xeda4, + 'letter-circle-h': 0xebf8, + 'letter-circle-p': 0xec08, + 'letter-circle-v': 0xec14, + 'lifebuoy': 0xe63a, + 'lightbulb': 0xe2dc, + 'lightbulb-filament': 0xe63c, + 'lighthouse': 0xe9f6, + 'lightning': 0xe2de, + 'lightning-a': 0xea84, + 'lightning-slash': 0xe2e0, + 'line-segment': 0xe6d2, + 'line-segments': 0xe6d4, + 'line-vertical': 0xed70, + 'link': 0xe2e2, + 'link-break': 0xe2e4, + 'link-simple': 0xe2e6, + 'link-simple-break': 0xe2e8, + 'link-simple-horizontal': 0xe2ea, + 'link-simple-horizontal-break': 0xe2ec, + 'linkedin-logo': 0xe2ee, + 'linktree-logo': 0xedee, + 'linux-logo': 0xeb02, + 'list': 0xe2f0, + 'list-bullets': 0xe2f2, + 'list-checks': 0xeadc, + 'list-dashes': 0xe2f4, + 'list-heart': 0xebde, + 'list-magnifying-glass': 0xebe0, + 'list-numbers': 0xe2f6, + 'list-plus': 0xe2f8, + 'list-star': 0xebdc, + 'lock': 0xe2fa, + 'lock-key': 0xe2fe, + 'lock-key-open': 0xe300, + 'lock-laminated': 0xe302, + 'lock-laminated-open': 0xe304, + 'lock-open': 0xe306, + 'lock-simple': 0xe308, + 'lock-simple-open': 0xe30a, + 'lockers': 0xecb8, + 'log': 0xed82, + 'magic-wand': 0xe6b6, + 'magnet': 0xe680, + 'magnet-straight': 0xe682, + 'magnifying-glass': 0xe30c, + 'magnifying-glass-minus': 0xe30e, + 'magnifying-glass-plus': 0xe310, + 'mailbox': 0xec1e, + 'map-pin': 0xe316, + 'map-pin-area': 0xee3a, + 'map-pin-line': 0xe318, + 'map-pin-plus': 0xe314, + 'map-pin-simple': 0xee3e, + 'map-pin-simple-area': 0xee3c, + 'map-pin-simple-line': 0xee38, + 'map-trifold': 0xe31a, + 'markdown-logo': 0xe508, + 'marker-circle': 0xe640, + 'martini': 0xe31c, + 'mask-happy': 0xe9f4, + 'mask-sad': 0xeb9e, + 'mastodon-logo': 0xed68, + 'math-operations': 0xe31e, + 'matrix-logo': 0xed64, + 'medal': 0xe320, + 'medal-military': 0xecfc, + 'medium-logo': 0xe322, + 'megaphone': 0xe324, + 'megaphone-simple': 0xe642, + 'member-of': 0xedc2, + 'memory': 0xe9c4, + 'messenger-logo': 0xe6d8, + 'meta-logo': 0xed02, + 'meteor': 0xe9ba, + 'metronome': 0xec8e, + 'microphone': 0xe326, + 'microphone-slash': 0xe328, + 'microphone-stage': 0xe75c, + 'microscope': 0xec7a, + 'microsoft-excel-logo': 0xeb6c, + 'microsoft-outlook-logo': 0xeb70, + 'microsoft-powerpoint-logo': 0xeace, + 'microsoft-teams-logo': 0xeb66, + 'microsoft-word-logo': 0xeb6a, + 'minus': 0xe32a, + 'minus-circle': 0xe32c, + 'minus-square': 0xed4c, + 'money': 0xe588, + 'money-wavy': 0xee68, + 'monitor': 0xe32e, + 'monitor-arrow-up': 0xe58a, + 'monitor-play': 0xe58c, + 'moon': 0xe330, + 'moon-stars': 0xe58e, + 'moped': 0xe824, + 'moped-front': 0xe822, + 'mosque': 0xecee, + 'motorcycle': 0xe80a, + 'mountains': 0xe7ae, + 'mouse': 0xe33a, + 'mouse-left-click': 0xe334, + 'mouse-middle-click': 0xe338, + 'mouse-right-click': 0xe336, + 'mouse-scroll': 0xe332, + 'mouse-simple': 0xe644, + 'music-note': 0xe33c, + 'music-note-simple': 0xe33e, + 'music-notes': 0xe340, + 'music-notes-minus': 0xee0c, + 'music-notes-plus': 0xeb7c, + 'music-notes-simple': 0xe342, + 'navigation-arrow': 0xeade, + 'needle': 0xe82e, + 'network': 0xedde, + 'network-slash': 0xeddc, + 'network-x': 0xedda, + 'newspaper': 0xe344, + 'newspaper-clipping': 0xe346, + 'not-equals': 0xeda6, + 'not-member-of': 0xedae, + 'not-subset-of': 0xedb0, + 'not-superset-of': 0xedb2, + 'notches': 0xed3a, + 'note': 0xe348, + 'note-blank': 0xe34a, + 'note-pencil': 0xe34c, + 'notebook': 0xe34e, + 'notepad': 0xe63e, + 'notification': 0xe6fa, + 'notion-logo': 0xe9a0, + 'nuclear-plant': 0xed7c, + 'number-circle-eight': 0xe352, + 'number-circle-five': 0xe358, + 'number-circle-four': 0xe35e, + 'number-circle-nine': 0xe364, + 'number-circle-one': 0xe36a, + 'number-circle-seven': 0xe370, + 'number-circle-six': 0xe376, + 'number-circle-three': 0xe37c, + 'number-circle-two': 0xe382, + 'number-circle-zero': 0xe388, + 'number-eight': 0xe350, + 'number-five': 0xe356, + 'number-four': 0xe35c, + 'number-nine': 0xe362, + 'number-one': 0xe368, + 'number-seven': 0xe36e, + 'number-six': 0xe374, + 'number-square-eight': 0xe354, + 'number-square-five': 0xe35a, + 'number-square-four': 0xe360, + 'number-square-nine': 0xe366, + 'number-square-one': 0xe36c, + 'number-square-seven': 0xe372, + 'number-square-six': 0xe378, + 'number-square-three': 0xe37e, + 'number-square-two': 0xe384, + 'number-square-zero': 0xe38a, + 'number-three': 0xe37a, + 'number-two': 0xe380, + 'number-zero': 0xe386, + 'numpad': 0xe3c8, + 'nut': 0xe38c, + 'ny-times-logo': 0xe646, + 'octagon': 0xe38e, + 'office-chair': 0xea46, + 'onigiri': 0xee2c, + 'open-ai-logo': 0xe7d2, + 'option': 0xe8a8, + 'orange': 0xee40, + 'orange-slice': 0xed36, + 'oven': 0xed8c, + 'package': 0xe390, + 'paint-brush': 0xe6f0, + 'paint-brush-broad': 0xe590, + 'paint-brush-household': 0xe6f2, + 'paint-bucket': 0xe392, + 'paint-roller': 0xe6f4, + 'palette': 0xe6c8, + 'panorama': 0xeaa2, + 'pants': 0xec88, + 'paper-plane': 0xe394, + 'paper-plane-right': 0xe396, + 'paper-plane-tilt': 0xe398, + 'paperclip': 0xe39a, + 'paperclip-horizontal': 0xe592, + 'parachute': 0xea7c, + 'paragraph': 0xe960, + 'parallelogram': 0xecc6, + 'park': 0xecb2, + 'password': 0xe752, + 'path': 0xe39c, + 'patreon-logo': 0xe98a, + 'pause': 0xe39e, + 'pause-circle': 0xe3a0, + 'paw-print': 0xe648, + 'paypal-logo': 0xe98c, + 'peace': 0xe3a2, + 'pen': 0xe3aa, + 'pen-nib': 0xe3ac, + 'pen-nib-straight': 0xe64a, + 'pencil': 0xe3ae, + 'pencil-circle': 0xe3b0, + 'pencil-line': 0xe3b2, + 'pencil-ruler': 0xe906, + 'pencil-simple': 0xe3b4, + 'pencil-simple-line': 0xebc6, + 'pencil-simple-slash': 0xecf6, + 'pencil-slash': 0xecf8, + 'pentagon': 0xec7e, + 'pentagram': 0xec5c, + 'pepper': 0xe94a, + 'percent': 0xe3b6, + 'person': 0xe3a8, + 'person-arms-spread': 0xecfe, + 'person-simple': 0xe72e, + 'person-simple-bike': 0xe734, + 'person-simple-circle': 0xee58, + 'person-simple-hike': 0xed54, + 'person-simple-run': 0xe730, + 'person-simple-ski': 0xe71c, + 'person-simple-snowboard': 0xe71e, + 'person-simple-swim': 0xe736, + 'person-simple-tai-chi': 0xed5c, + 'person-simple-throw': 0xe732, + 'person-simple-walk': 0xe73a, + 'perspective': 0xebe6, + 'phone': 0xe3b8, + 'phone-call': 0xe3ba, + 'phone-disconnect': 0xe3bc, + 'phone-incoming': 0xe3be, + 'phone-list': 0xe3cc, + 'phone-outgoing': 0xe3c0, + 'phone-pause': 0xe3ca, + 'phone-plus': 0xec56, + 'phone-slash': 0xe3c2, + 'phone-transfer': 0xe3c6, + 'phone-x': 0xe3c4, + 'phosphor-logo': 0xe3ce, + 'pi': 0xec80, + 'piano-keys': 0xe9c8, + 'picnic-table': 0xee26, + 'picture-in-picture': 0xe64c, + 'piggy-bank': 0xea04, + 'pill': 0xe700, + 'ping-pong': 0xea42, + 'pint-glass': 0xedd0, + 'pinterest-logo': 0xe64e, + 'pinwheel': 0xeb9c, + 'pipe': 0xed86, + 'pipe-wrench': 0xed88, + 'pix-logo': 0xecc2, + 'pizza': 0xe796, + 'placeholder': 0xe650, + 'planet': 0xe652, + 'plant': 0xebae, + 'play': 0xe3d0, + 'play-circle': 0xe3d2, + 'play-pause': 0xe8be, + 'playlist': 0xe6aa, + 'plug': 0xe946, + 'plug-charging': 0xeb5c, + 'plugs': 0xeb56, + 'plugs-connected': 0xeb5a, + 'plus': 0xe3d4, + 'plus-circle': 0xe3d6, + 'plus-minus': 0xe3d8, + 'plus-square': 0xed4a, + 'poker-chip': 0xe594, + 'police-car': 0xec4a, + 'polygon': 0xe6d0, + 'popcorn': 0xeb4e, + 'popsicle': 0xebbe, + 'potted-plant': 0xec22, + 'power': 0xe3da, + 'prescription': 0xe7a2, + 'presentation': 0xe654, + 'presentation-chart': 0xe656, + 'printer': 0xe3dc, + 'prohibit': 0xe3de, + 'prohibit-inset': 0xe3e0, + 'projector-screen': 0xe658, + 'projector-screen-chart': 0xe65a, + 'pulse': 0xe000, + 'push-pin': 0xe3e2, + 'push-pin-simple': 0xe65c, + 'push-pin-simple-slash': 0xe65e, + 'push-pin-slash': 0xe3e4, + 'puzzle-piece': 0xe596, + 'qr-code': 0xe3e6, + 'question': 0xe3e8, + 'question-mark': 0xe3e8, + 'queue': 0xe6ac, + 'quotes': 0xe660, + 'rabbit': 0xeac2, + 'racquet': 0xee02, + 'radical': 0xe3ea, + 'radio': 0xe77e, + 'radio-button': 0xeb08, + 'radioactive': 0xe9dc, + 'rainbow': 0xe598, + 'rainbow-cloud': 0xe59a, + 'ranking': 0xed62, + 'read-cv-logo': 0xed0c, + 'receipt': 0xe3ec, + 'receipt-x': 0xed40, + 'record': 0xe3ee, + 'rectangle': 0xe3f0, + 'rectangle-dashed': 0xe3f2, + 'recycle': 0xe75a, + 'reddit-logo': 0xe59c, + 'repeat': 0xe3f6, + 'repeat-once': 0xe3f8, + 'replit-logo': 0xeb8a, + 'resize': 0xed6e, + 'rewind': 0xe6a8, + 'rewind-circle': 0xe3fa, + 'road-horizon': 0xe838, + 'robot': 0xe762, + 'rocket': 0xe3fc, + 'rocket-launch': 0xe3fe, + 'rows': 0xe5a2, + 'rows-plus-bottom': 0xe59e, + 'rows-plus-top': 0xe5a0, + 'rss': 0xe400, + 'rss-simple': 0xe402, + 'rug': 0xea1a, + 'ruler': 0xe6b8, + 'sailboat': 0xe78a, + 'scales': 0xe750, + 'scan': 0xebb6, + 'scan-smiley': 0xebb4, + 'scissors': 0xeae0, + 'scooter': 0xe820, + 'screencast': 0xe404, + 'screwdriver': 0xe86e, + 'scribble': 0xe806, + 'scribble-loop': 0xe662, + 'scroll': 0xeb7a, + 'seal': 0xe604, + 'seal-check': 0xe606, + 'seal-percent': 0xe60a, + 'seal-question': 0xe608, + 'seal-warning': 0xe60c, + 'seat': 0xeb8e, + 'seatbelt': 0xedfe, + 'security-camera': 0xeca4, + 'selection': 0xe69a, + 'selection-all': 0xe746, + 'selection-background': 0xeaf8, + 'selection-foreground': 0xeaf6, + 'selection-inverse': 0xe744, + 'selection-plus': 0xe69c, + 'selection-slash': 0xe69e, + 'shapes': 0xec5e, + 'share': 0xe406, + 'share-fat': 0xed52, + 'share-network': 0xe408, + 'shield': 0xe40a, + 'shield-check': 0xe40c, + 'shield-checkered': 0xe708, + 'shield-chevron': 0xe40e, + 'shield-plus': 0xe706, + 'shield-slash': 0xe410, + 'shield-star': 0xec34, + 'shield-warning': 0xe412, + 'shipping-container': 0xe78c, + 'shirt-folded': 0xea92, + 'shooting-star': 0xecfa, + 'shopping-bag': 0xe416, + 'shopping-bag-open': 0xe418, + 'shopping-cart': 0xe41e, + 'shopping-cart-simple': 0xe420, + 'shovel': 0xe9e6, + 'shower': 0xe776, + 'shrimp': 0xeab4, + 'shuffle': 0xe422, + 'shuffle-angular': 0xe424, + 'shuffle-simple': 0xe426, + 'sidebar': 0xeab6, + 'sidebar-simple': 0xec24, + 'sigma': 0xeab8, + 'sign-in': 0xe428, + 'sign-out': 0xe42a, + 'signature': 0xebac, + 'signpost': 0xe89c, + 'sim-card': 0xe664, + 'siren': 0xe9b8, + 'sketch-logo': 0xe42c, + 'skip-back': 0xe5a4, + 'skip-back-circle': 0xe42e, + 'skip-forward': 0xe5a6, + 'skip-forward-circle': 0xe430, + 'skull': 0xe916, + 'skype-logo': 0xe8dc, + 'slack-logo': 0xe5a8, + 'sliders': 0xe432, + 'sliders-horizontal': 0xe434, + 'slideshow': 0xed32, + 'smiley': 0xe436, + 'smiley-angry': 0xec62, + 'smiley-blank': 0xe438, + 'smiley-meh': 0xe43a, + 'smiley-melting': 0xee56, + 'smiley-nervous': 0xe43c, + 'smiley-sad': 0xe43e, + 'smiley-sticker': 0xe440, + 'smiley-wink': 0xe666, + 'smiley-x-eyes': 0xe442, + 'snapchat-logo': 0xe668, + 'sneaker': 0xe80c, + 'sneaker-move': 0xed60, + 'snowflake': 0xe5aa, + 'soccer-ball': 0xe716, + 'sock': 0xecce, + 'solar-panel': 0xed7a, + 'solar-roof': 0xed7a, + 'sort-ascending': 0xe444, + 'sort-descending': 0xe446, + 'soundcloud-logo': 0xe8de, + 'spade': 0xe448, + 'sparkle': 0xe6a2, + 'speaker-hifi': 0xea08, + 'speaker-high': 0xe44a, + 'speaker-low': 0xe44c, + 'speaker-none': 0xe44e, + 'speaker-simple-high': 0xe450, + 'speaker-simple-low': 0xe452, + 'speaker-simple-none': 0xe454, + 'speaker-simple-slash': 0xe456, + 'speaker-simple-x': 0xe458, + 'speaker-slash': 0xe45a, + 'speaker-x': 0xe45c, + 'speedometer': 0xee74, + 'sphere': 0xee66, + 'spinner': 0xe66a, + 'spinner-ball': 0xee28, + 'spinner-gap': 0xe66c, + 'spiral': 0xe9fa, + 'split-horizontal': 0xe872, + 'split-vertical': 0xe876, + 'spotify-logo': 0xe66e, + 'spray-bottle': 0xe7e4, + 'square': 0xe45e, + 'square-half': 0xe462, + 'square-half-bottom': 0xeb16, + 'square-logo': 0xe690, + 'square-split-horizontal': 0xe870, + 'square-split-vertical': 0xe874, + 'squares-four': 0xe464, + 'stack': 0xe466, + 'stack-minus': 0xedf4, + 'stack-overflow-logo': 0xeb78, + 'stack-plus': 0xedf6, + 'stack-simple': 0xe468, + 'stairs': 0xe8ec, + 'stamp': 0xea48, + 'standard-definition': 0xea90, + 'star': 0xe46a, + 'star-and-crescent': 0xecf4, + 'star-four': 0xe6a4, + 'star-half': 0xe70a, + 'star-of-david': 0xe89e, + 'steam-logo': 0xead4, + 'steering-wheel': 0xe9ac, + 'steps': 0xecbe, + 'stethoscope': 0xe7ea, + 'sticker': 0xe5ac, + 'stool': 0xea44, + 'stop': 0xe46c, + 'stop-circle': 0xe46e, + 'storefront': 0xe470, + 'strategy': 0xea3a, + 'stripe-logo': 0xe698, + 'student': 0xe73e, + 'subset-of': 0xedc0, + 'subset-proper-of': 0xedb6, + 'subtitles': 0xe1a8, + 'subtitles-slash': 0xe1a6, + 'subtract': 0xebd6, + 'subtract-square': 0xebd4, + 'subway': 0xe498, + 'suitcase': 0xe5ae, + 'suitcase-rolling': 0xe9b0, + 'suitcase-simple': 0xe5b0, + 'sun': 0xe472, + 'sun-dim': 0xe474, + 'sun-horizon': 0xe5b6, + 'sunglasses': 0xe816, + 'superset-of': 0xedb8, + 'superset-proper-of': 0xedb4, + 'swap': 0xe83c, + 'swatches': 0xe5b8, + 'swimming-pool': 0xecb6, + 'sword': 0xe5ba, + 'synagogue': 0xecec, + 'syringe': 0xe968, + 't-shirt': 0xe670, + 'table': 0xe476, + 'tabs': 0xe778, + 'tag': 0xe478, + 'tag-chevron': 0xe672, + 'tag-simple': 0xe47a, + 'target': 0xe47c, + 'taxi': 0xe902, + 'tea-bag': 0xe8e6, + 'telegram-logo': 0xe5bc, + 'television': 0xe754, + 'television-simple': 0xeae6, + 'tennis-ball': 0xe720, + 'tent': 0xe8ba, + 'terminal': 0xe47e, + 'terminal-window': 0xeae8, + 'test-tube': 0xe7a0, + 'text-a-underline': 0xed34, + 'text-aa': 0xe6ee, + 'text-align-center': 0xe480, + 'text-align-justify': 0xe482, + 'text-align-left': 0xe484, + 'text-align-right': 0xe486, + 'text-b': 0xe5be, + 'text-columns': 0xec96, + 'text-h': 0xe6ba, + 'text-h-five': 0xe6c4, + 'text-h-four': 0xe6c2, + 'text-h-one': 0xe6bc, + 'text-h-six': 0xe6c6, + 'text-h-three': 0xe6c0, + 'text-h-two': 0xe6be, + 'text-indent': 0xea1e, + 'text-italic': 0xe5c0, + 'text-outdent': 0xea1c, + 'text-strikethrough': 0xe5c2, + 'text-subscript': 0xec98, + 'text-superscript': 0xec9a, + 'text-t': 0xe48a, + 'text-t-slash': 0xe488, + 'text-underline': 0xe5c4, + 'textbox': 0xeb0a, + 'thermometer': 0xe5c6, + 'thermometer-cold': 0xe5c8, + 'thermometer-hot': 0xe5ca, + 'thermometer-simple': 0xe5cc, + 'threads-logo': 0xed9e, + 'three-d': 0xea5a, + 'thumbs-down': 0xe48c, + 'thumbs-up': 0xe48e, + 'ticket': 0xe490, + 'tidal-logo': 0xed1c, + 'tiktok-logo': 0xeaf2, + 'tilde': 0xeda8, + 'timer': 0xe492, + 'tip-jar': 0xe7e0, + 'tipi': 0xed30, + 'tire': 0xedd2, + 'toggle-left': 0xe674, + 'toggle-right': 0xe676, + 'toilet': 0xe79a, + 'toilet-paper': 0xe79c, + 'toolbox': 0xeca0, + 'tooth': 0xe9cc, + 'tornado': 0xe88c, + 'tote': 0xe494, + 'tote-simple': 0xe678, + 'towel': 0xede6, + 'tractor': 0xec6e, + 'trademark': 0xe9f0, + 'trademark-registered': 0xe3f4, + 'traffic-cone': 0xe9a8, + 'traffic-sign': 0xe67a, + 'traffic-signal': 0xe9aa, + 'train': 0xe496, + 'train-regional': 0xe49e, + 'train-simple': 0xe4a0, + 'tram': 0xe9ec, + 'translate': 0xe4a2, + 'trash': 0xe4a6, + 'trash-simple': 0xe4a8, + 'tray': 0xe4aa, + 'tray-arrow-down': 0xe010, + 'tray-arrow-up': 0xee52, + 'treasure-chest': 0xede2, + 'tree': 0xe6da, + 'tree-evergreen': 0xe6dc, + 'tree-palm': 0xe91a, + 'tree-structure': 0xe67c, + 'tree-view': 0xee48, + 'trend-down': 0xe4ac, + 'trend-up': 0xe4ae, + 'triangle': 0xe4b0, + 'triangle-dashed': 0xe4b2, + 'trolley': 0xe5b2, + 'trolley-suitcase': 0xe5b4, + 'trophy': 0xe67e, + 'truck': 0xe4b4, + 'truck-trailer': 0xe4b6, + 'tumblr-logo': 0xe8d4, + 'twitch-logo': 0xe5ce, + 'twitter-logo': 0xe4ba, + 'umbrella': 0xe684, + 'umbrella-simple': 0xe686, + 'union': 0xedbe, + 'unite': 0xe87e, + 'unite-square': 0xe878, + 'upload': 0xe4be, + 'upload-simple': 0xe4c0, + 'usb': 0xe956, + 'user': 0xe4c2, + 'user-check': 0xeafa, + 'user-circle': 0xe4c4, + 'user-circle-check': 0xec38, + 'user-circle-dashed': 0xec36, + 'user-circle-gear': 0xe4c6, + 'user-circle-minus': 0xe4c8, + 'user-circle-plus': 0xe4ca, + 'user-focus': 0xe6fc, + 'user-gear': 0xe4cc, + 'user-list': 0xe73c, + 'user-minus': 0xe4ce, + 'user-plus': 0xe4d0, + 'user-rectangle': 0xe4d2, + 'user-sound': 0xeca8, + 'user-square': 0xe4d4, + 'user-switch': 0xe756, + 'users': 0xe4d6, + 'users-four': 0xe68c, + 'users-three': 0xe68e, + 'van': 0xe826, + 'vault': 0xe76e, + 'vector-three': 0xee62, + 'vector-two': 0xee64, + 'vibrate': 0xe4d8, + 'video': 0xe740, + 'video-camera': 0xe4da, + 'video-camera-slash': 0xe4dc, + 'video-conference': 0xedce, + 'vignette': 0xeba2, + 'vinyl-record': 0xecac, + 'virtual-reality': 0xe7b8, + 'virus': 0xe7d6, + 'visor': 0xee2a, + 'voicemail': 0xe4de, + 'volleyball': 0xe726, + 'wall': 0xe688, + 'wallet': 0xe68a, + 'warehouse': 0xecd4, + 'warning': 0xe4e0, + 'warning-circle': 0xe4e2, + 'warning-diamond': 0xe7fc, + 'warning-octagon': 0xe4e4, + 'washing-machine': 0xede8, + 'watch': 0xe4e6, + 'wave-sawtooth': 0xea9c, + 'wave-sine': 0xea9a, + 'wave-square': 0xea9e, + 'wave-triangle': 0xeaa0, + 'waveform': 0xe802, + 'waveform-slash': 0xe800, + 'waves': 0xe6de, + 'webcam': 0xe9b2, + 'webcam-slash': 0xecdc, + 'webhooks-logo': 0xecae, + 'wechat-logo': 0xe8d2, + 'whatsapp-logo': 0xe5d0, + 'wheelchair': 0xe4e8, + 'wheelchair-motion': 0xe89a, + 'wifi-high': 0xe4ea, + 'wifi-low': 0xe4ec, + 'wifi-medium': 0xe4ee, + 'wifi-none': 0xe4f0, + 'wifi-slash': 0xe4f2, + 'wifi-x': 0xe4f4, + 'wind': 0xe5d2, + 'windmill': 0xe9f8, + 'windows-logo': 0xe692, + 'wine': 0xe6b2, + 'wrench': 0xe5d4, + 'x': 0xe4f6, + 'x-circle': 0xe4f8, + 'x-logo': 0xe4bc, + 'x-square': 0xe4fa, + 'yarn': 0xed9a, + 'yin-yang': 0xe92a, + 'youtube-logo': 0xe4fc, +}; diff --git a/test/app_collapse_toggle_test.dart b/test/app_collapse_toggle_test.dart index 37bf0770..159e5447 100644 --- a/test/app_collapse_toggle_test.dart +++ b/test/app_collapse_toggle_test.dart @@ -38,17 +38,17 @@ void main() { // Sidebar open → action is collapse (leftward) → caret-line-left. await tester.pumpWidget(_host(f, const StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: false, visible: true))); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineLeft), findsOneWidget); + expect(_icon(PhosphorIcons.byName('caret-line-left')), findsOneWidget); // Sidebar collapsed → action is expand (rightward) → caret-line-right. await tester.pumpWidget(_host(f, const StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: true, visible: true))); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineRight), findsOneWidget); + expect(_icon(PhosphorIcons.byName('caret-line-right')), findsOneWidget); // The context toggle mirrors it: open → collapse rightward, collapsed → expand leftward. await tester.pumpWidget(_host(f, const StatusbarCollapseToggle(slot: Slots.contextPanel, collapsed: false, visible: true))); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineRight), findsOneWidget); + expect(_icon(PhosphorIcons.byName('caret-line-right')), findsOneWidget); }); testWidgets('the chevron flips live when the collapsed state changes', (tester) async { @@ -62,19 +62,19 @@ void main() { }), )); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineLeft), findsOneWidget); + expect(_icon(PhosphorIcons.byName('caret-line-left')), findsOneWidget); setOuter(() => collapsed = true); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineRight), findsOneWidget, reason: 'rebuilds and flips on state change'); - expect(_icon(PhosphorIcons.caretLineLeft), findsNothing); + expect(_icon(PhosphorIcons.byName('caret-line-right')), findsOneWidget, reason: 'rebuilds and flips on state change'); + expect(_icon(PhosphorIcons.byName('caret-line-left')), findsNothing); }); testWidgets('a hidden pane reserves the cell but shows no chevron', (tester) async { await tester.pumpWidget(_host(f, const StatusbarCollapseToggle(slot: Slots.sidebar, collapsed: false, visible: false))); await tester.pump(); - expect(_icon(PhosphorIcons.caretLineLeft), findsNothing); - expect(_icon(PhosphorIcons.caretLineRight), findsNothing); + expect(_icon(PhosphorIcons.byName('caret-line-left')), findsNothing); + expect(_icon(PhosphorIcons.byName('caret-line-right')), findsNothing); }); testWidgets('tapping fires the matching collapse command', (tester) async { @@ -90,7 +90,7 @@ void main() { await tester.pump(); expect(f.services.arrangement.isCollapsed(Slots.sidebar), isFalse); - await tester.tap(_icon(PhosphorIcons.caretLineLeft)); + await tester.tap(_icon(PhosphorIcons.byName('caret-line-left'))); await tester.pump(); expect(f.services.arrangement.isCollapsed(Slots.sidebar), isTrue); diff --git a/test/builtin/claude/team_chat_sidebar_test.dart b/test/builtin/claude/team_chat_sidebar_test.dart index 9b61fe18..20aa2920 100644 --- a/test/builtin/claude/team_chat_sidebar_test.dart +++ b/test/builtin/claude/team_chat_sidebar_test.dart @@ -368,7 +368,7 @@ void main() { expect(find.text('Interrupt'), findsOneWidget); // The tickbox is implemented as a Container that is empty when unchecked // and shows a check icon when checked. Verify by checking that the check - // icon (PhosphorIcons.check) is NOT rendered when unchecked. + // icon (PhosphorIcons.byName('check')) is NOT rendered when unchecked. // We use the Semantics widget's checked property which maps to isChecked. final interruptFinder = find.byWidgetPredicate((w) => w is Semantics && w.properties.label == 'Interrupt target session'); // Widget exists in tree. diff --git a/test/widgets/src/phosphor_glyphs_test.dart b/test/widgets/src/phosphor_glyphs_test.dart new file mode 100644 index 00000000..f2b4fe43 --- /dev/null +++ b/test/widgets/src/phosphor_glyphs_test.dart @@ -0,0 +1,47 @@ +/// Guards the Phosphor label→codepoint indirection (T-314). The named consts +/// are gone; code resolves glyphs by string via [PhosphorIcons.byName]. A +/// string key loses the compiler's typo check, so this test recovers it: every +/// `byName('…')` string literal in `lib/` must exist in the generated map — +/// across all call sites, executed or not. +library; + +import 'dart:io'; + +import 'package:clide/widgets/src/icons/phosphor_glyphs.g.dart'; +import 'package:clide/widgets/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('the generated glyph map covers the full Phosphor set', () { + expect(kPhosphorGlyphs.length, greaterThanOrEqualTo(1500)); + expect(kPhosphorGlyphs.containsKey('folder'), isTrue); + // The fallback glyph must itself resolve, or byName would throw on a miss. + expect(kPhosphorGlyphs.containsKey(PhosphorIcons.fallbackName), isTrue); + }); + + test('every byName(\'…\') literal in lib/ resolves to a real glyph', () { + final call = RegExp(r"byName\(\s*'([a-z0-9-]+)'\s*\)"); + final missing = {}; // label -> first file it appears in + for (final entity in Directory('lib').listSync(recursive: true)) { + if (entity is! File || !entity.path.endsWith('.dart')) continue; + for (final m in call.allMatches(entity.readAsStringSync())) { + final label = m.group(1)!; + if (!kPhosphorGlyphs.containsKey(label)) { + missing.putIfAbsent(label, () => entity.path); + } + } + } + expect(missing, isEmpty, reason: 'unknown Phosphor glyph name(s) in byName(): $missing'); + }); + + test('byName resolves a known name to its codepoint', () { + expect(PhosphorIcons.byName('folder').codePoint, kPhosphorGlyphs['folder']); + expect(PhosphorIcons.byName('caret-line-left').codePoint, kPhosphorGlyphs['caret-line-left']); + }); + + test('byName degrades an unknown name to the placeholder fallback (never throws)', () { + final fallback = kPhosphorGlyphs[PhosphorIcons.fallbackName]; + expect(PhosphorIcons.byName('definitely-not-a-real-glyph').codePoint, fallback); + expect(PhosphorIcons.fallbackName, 'placeholder'); + }); +} diff --git a/test/widgets/zero_coverage_widgets_test.dart b/test/widgets/zero_coverage_widgets_test.dart index 39a7b4f1..2dde5f5d 100644 --- a/test/widgets/zero_coverage_widgets_test.dart +++ b/test/widgets/zero_coverage_widgets_test.dart @@ -296,11 +296,13 @@ void main() { testWidgets('renders the hint as a placeholder (shown empty, hidden once typed); icon optional', (tester) async { await tester.pumpWidget(harness( f, - ClideFilterBox(onChanged: (_) {}, hint: 'Replace', icon: null), + ClideFilterBox(onChanged: (_) {}, hint: 'Replace', showIcon: false), )); - // Placeholder visible while empty; no leading search glyph (icon: null). + // Placeholder visible while empty; the icon slot is kept for alignment but + // draws a blank glyph (showIcon: false → EmptyIconPainter). expect(find.text('Replace'), findsOneWidget); - expect(find.byType(ClideIcon), findsNothing); + expect(find.byType(ClideIcon), findsOneWidget); + expect(tester.widget(find.byType(ClideIcon)).painter, isA()); // Typing hides the placeholder. await tester.enterText(find.byType(EditableText), 'x'); await tester.pump(); @@ -349,9 +351,9 @@ void main() { await tester.pumpWidget(harness( f, ClideIconRail( - items: const [ - ClideIconRailItem(id: 'a', icon: PhosphorIcons.folder, tooltip: 'A'), - ClideIconRailItem(id: 'b', icon: PhosphorIcons.gitBranch, tooltip: 'B'), + items: [ + ClideIconRailItem(id: 'a', icon: PhosphorIcons.byName('folder'), tooltip: 'A'), + ClideIconRailItem(id: 'b', icon: PhosphorIcons.byName('git-branch'), tooltip: 'B'), ], activeId: 'a', onSelect: (id) => selected = id, @@ -373,7 +375,7 @@ void main() { height: 30, child: ClideIconRail( items: [ - for (var i = 0; i < 10; i++) ClideIconRailItem(id: '$i', icon: PhosphorIcons.folder, tooltip: 'Tab $i'), + for (var i = 0; i < 10; i++) ClideIconRailItem(id: '$i', icon: PhosphorIcons.byName('folder'), tooltip: 'Tab $i'), ], activeId: '0', onSelect: (_) {}, diff --git a/tool/gen_phosphor_glyphs.dart b/tool/gen_phosphor_glyphs.dart new file mode 100644 index 00000000..e8082cf8 --- /dev/null +++ b/tool/gen_phosphor_glyphs.dart @@ -0,0 +1,48 @@ +// Regenerates lib/widgets/src/icons/phosphor_glyphs.g.dart from the Phosphor +// glyph table. Run from the repo root: +// +// dart run tool/gen_phosphor_glyphs.dart +// +// Source of truth is the glyph table shipped with the ui-design skill — each +// row is `| `0xNNNN` | kebab-name | PascalName | |`. We keep only the codepoint +// + the exact kebab-case upstream name, which is what code references via +// PhosphorIcons.byName('…'). No Flutter imports — plain Dart, runnable with +// `dart run`. +import 'dart:io'; + +const _src = '.claude/skills/ui-design/references/phosphor-glyphs.md'; +const _out = 'lib/widgets/src/icons/phosphor_glyphs.g.dart'; + +void main() { + final rows = File(_src).readAsLinesSync(); + // `| `0xe24a` | folder | Folder | |` + final re = RegExp(r'^\|\s*`(0x[0-9a-fA-F]+)`\s*\|\s*([a-z0-9-]+)\s*\|'); + final glyphs = {}; + for (final line in rows) { + final m = re.firstMatch(line); + if (m == null) continue; + final code = int.parse(m.group(1)!.substring(2), radix: 16); + glyphs[m.group(2)!] = code; + } + if (glyphs.length < 1000) { + stderr.writeln('refusing to write: only ${glyphs.length} glyphs parsed from $_src'); + exit(1); + } + final names = glyphs.keys.toList()..sort(); + final b = StringBuffer() + ..writeln('// GENERATED by tool/gen_phosphor_glyphs.dart — do not edit by hand.') + ..writeln('// Source: $_src (${glyphs.length} glyphs).') + ..writeln('//') + ..writeln('// The single label→codepoint lookup for the bundled Phosphor font. Code') + ..writeln('// references glyphs by their exact kebab-case name via PhosphorIcons.byName;') + ..writeln('// raw codepoints live only here.') + ..writeln('library;') + ..writeln() + ..writeln('const Map kPhosphorGlyphs = {'); + for (final n in names) { + b.writeln(" '$n': 0x${glyphs[n]!.toRadixString(16)},"); + } + b.writeln('};'); + File(_out).writeAsStringSync(b.toString()); + stdout.writeln('wrote $_out with ${glyphs.length} glyphs'); +}