Phosphor icons: resolve by name via a generated map (T-314)
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) <noreply@anthropic.com>
This commit is contained in:
@@ -564,7 +564,7 @@ class _ClaudeComposerState extends State<ClaudeComposer> {
|
||||
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<ClaudeComposer> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -288,7 +288,7 @@ class _ConversationCardState extends State<ConversationCard> {
|
||||
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,
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ class _TeamChatSidebarState extends State<TeamChatSidebar> {
|
||||
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<TeamChatPane> {
|
||||
),
|
||||
child: _interrupt
|
||||
? Center(
|
||||
child: ClideIcon(PhosphorIcons.check, size: 9, color: tokens.globalFocus),
|
||||
child: ClideIcon(PhosphorIcons.byName('check'), size: 9, color: tokens.globalFocus),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -157,7 +157,7 @@ class _DecisionsViewState extends State<DecisionsView> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -61,7 +61,7 @@ class _ThemeSwitcherStatusItemState extends State<ThemeSwitcherStatusItem> {
|
||||
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.
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -166,7 +166,7 @@ class _TicketsViewState extends State<TicketsView> {
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user