Context
PR #1015 (SUP-342) migrated the sidebar's animated icons to Solar bold-duotone (@iconify-json/solar via @iconify/tailwind4). The rest of the app still uses Lucide via the <.icon name={...} /> component.
We want everything on Solar bold-duotone for a single visual language. These icons don't need animations - just static SVG rendering with the existing <.icon> API preserved so we don't have to touch every call site.
Scope
~98 distinct Lucide icon names used across the codebase via <.icon name={...} />. Full list captured from a grep of lib/:
arrow_down, arrow_left, arrow_right, arrow_up, arrow_up_down, arrow_up_right,
badge_check, banknote, blocks, calculator, calendar, calendar_clock, calendar_days,
chart_bar, chart_line, chart_no_axes_column, check, check_check, check_circle,
chevron_down, chevron_left, chevron_right, chevrons_up_down, circle_alert,
circle_check, circle_help, circle_minus, circle_plus, circle_user_round, circle_x,
clipboard, clipboard_copy, clipboard_list, clock, cloud_download, coins, copy,
copy_minus, credit_card, crown, download, ellipsis_vertical, eye, eye_off,
file_text, folder_tree, gift, hand_coins, history, hourglass, inbox, info, landmark,
link, loader, lock, log_out, menu, message_square, monitor, moon, more_horizontal,
party_popper, pause, pen, pen_line, pencil, play, plug, plus, receipt, receipt_text,
refresh_cw, repeat, rotate_ccw, send, settings, shield_alert, shield_check, siren,
sliders_horizontal, smile, sparkles, square_pen, sun, tag, tags, terminal, ticket,
trending_up, triangle_alert, unlink, unplug, user, wallet, x, zap
Proposed approach
Path A: rewire <.icon> to render Solar SVGs at compile time.
- Create
Visor.SolarIconsmodule, modeled afterVisorWeb.Components.AnimatedIcons. - The module reads
assets/node_modules/@iconify-json/solar/icons.jsonat compile time via@external_resource+Jason.decode!/1. - Maintain a
@mappings :: %{atom() => String.t()}table mapping each Lucide name above to a Solar slug (e.g.:credit_card => "card",:circle_alert => "danger-circle"). Most map directly; ~15-20 need judgment. - Metaprogramming generates one
def icon_name(assigns)per mapping, embedding the Solar SVG body inline withcurrentColorfill. - Update
VisorWeb.CoreComponents.icon/1to dispatch toVisor.SolarIconsinstead ofLucideicons. Keep the samename/class/restAPI so no call sites change. - For Lucide names with no clean Solar match, pick the closest bold-duotone variant. Document choices in module attrs for design review.
Out of scope
- Animations (those stay on the sidebar via
AnimatedIcons). - Removing the
lucide_iconshex dep (keep until we're sure nothing else uses it). - React Native / mobile icon migration.
Risks
- Solar bold-duotone is heavier than Lucide stroke icons. Cards/badges/empty states may need padding or weight tweaks. Spot-check the top 10 most-used screens after migration (dashboard, transactions, categories, recurring, accounts, budgets, goals, settings, auth, onboarding).
- Some Lucide names have no clean Solar 1:1. Possible candidates for design review:
:check_check(double check),:chevrons_up_down(double chevron),:rotate_ccw,:square_pen,:plug/:unplug,:cloud_download,:copy_minus,:folder_tree,:hourglass,:landmark,:monitor,:party_popper,:sliders_horizontal,:terminal.
Acceptance criteria
<.icon name={:foo}>renders a Solar bold-duotone SVG for every name in the list above.mix compileandmix testpass.- Visual QA confirms no obvious regressions on the top screens.
- PR description includes the full mapping table for designer sign-off.
Related
- Parent work: PR #1015 (SUP-342) - sidebar animated icons + Iconify Tailwind plugin scaffold.