Goal: Move every existing icon call site inStatus: In progress (Phase 0 complete). Owner: Design Systems. Cross-links: ICON_GUIDE.md · SEMANTIC_COLORS.md ·src/over to theAppIconAPI (tone/variant/weight), replacing raw<svg>/ directlucide-reactJSX and hard-codedtext-*color classes with the unified wrapper fromsrc/shared/ui/icon.tsx.
reports/codemod-appicon-tone-dryrun.json
1. Scope & baseline
The migration is a pure UI refactor. No DB / runtime contract changes, no PHI/PII implications, no newAppIcon props.
Baseline (captured 2026-04-21)
Lucide imports by bucket
Re-run the baseline before starting each phase:
2. Migration phases
Each phase ships as its own PR so reviewers can verify visually. Land in order.Phase 0 — Tooling guardrails ✅
- ESLint rule
northsight/prefer-appicon-toneflags hard-coded tone classes on<AppIcon>. - Dry-run codemod report at
reports/codemod-appicon-tone-dryrun.json. - Domain-status mapping
domainStatusToIconTone()insrc/shared/lib/semantic-colors.ts. - Solid-variant + weight + tone documented in
docs/development/ICON_GUIDE.md.
Phase 1 — src/shared/ui and src/shared/components
Target: 100% AppIcon, 0 hard-coded tone classes.
- Inventory:
grep -rl "from 'lucide-react'" src/shared/ui src/shared/components - Convert each file per §4 per-call-site checklist.
- Apply codemod findings from
reports/codemod-appicon-tone-dryrun.json(2 hits today). - Re-run dry-run codemod; confirm
byBucket["shared/ui"] === 0andbyBucket["shared/components"] === 0. - Verification block (see §5).
Phase 2 — src/platform/**
Target: 95% AppIcon (allow raw Lucide only inside AppIcon itself + icon registries like WIZARD_STEP_ICONS, MODULE_ICONS, stepIcons); 0 hard-coded tone classes.
- Inventory:
grep -rl "from 'lucide-react'" src/platform | wc -l(baseline: 756). - Skip-list: any file matching
*/icons/*,*-icons.ts,*StepIcons*,*ModuleIcons*,src/shared/ui/icon.tsx. - Sub-batches by surface area (suggested 100-file chunks):
-
src/platform/wizards/** -
src/platform/forms/** -
src/platform/table-v2/** -
src/platform/navigation/** -
src/platform/dashboard/** - Remainder.
-
- Verification block per sub-batch.
Phase 3 — src/cores/**
Target: 90% AppIcon (allow raw Lucide in module-icon configs only); ≤ 5 hard-coded color occurrences.
- Inventory:
grep -rl "from 'lucide-react'" src/cores | wc -l(baseline: 2,471). - Land per core in this order (smallest blast radius first):
it,lo,gr,fm,fw,ce,rh,cl,pm,fa,hr. - Skip-list:
*/icons/*,*-icons.ts, module config maps withLucideIcontyped values. - Verification block per core.
Phase 4 — Domain-status surfaces
Target: Every status badge / pill / workflow card resolves its tone viadomainStatusToIconTone(status) instead of an ad-hoc switch.
- Audit call sites:
grep -rE "tone=\"(success|warning|destructive|info)\"" srcand confirm any status-driven tones use the helper. - Replace hand-rolled
status === 'approved' ? 'success' : ...ternaries withdomainStatusToIconTone(status). - Surfaces to sweep:
StatusBadge,EntityStatusPill, workflow node cards, lead/contact stage chips, billing claim status, encounter status, contract lifecycle.
Phase 5 — Solid variant sweep
Target: Usevariant="solid" on terminal status badges and hero glyphs.
-
EmptyState,ErrorState,SuccessStatehero icons →variant="solid". - Toast leading icons (
sonnercustom icons) →variant="solid". - Terminal status badges (paid, approved, completed, denied, cancelled) →
variant="solid". - Inline list-row status (in-flight, draft) stay outline.
Phase 6 — Weight sweep
Target: Establish a weight rhythm.- Dense table chrome (sort carets, row affordances, kebab menus) →
weight="thin". - Primary CTAs (
Buttonleading icons) and section headers →weight="bold". - Everything else stays default (
regular).
3. Coverage targets
Definition of “AppIcon coverage”:
(<AppIcon> usages) / (<AppIcon> usages + raw Lucide JSX call sites) per bucket.
Compute via grep -rE "<AppIcon" {bucket} and grep -rE "<[A-Z][A-Za-z0-9]*\s+[^>]*className=" {bucket} filtered to identifiers imported from lucide-react. Approximation acceptable; the codemod report is the source of truth.
4. Per-call-site checklist
Apply to every Lucide JSX usage you touch:-
Replace JSX:
-
Resolve
tonefrom one of:- Explicit semantic intent (
success,warning,destructive,info,muted,neutral,primary,accent). domainStatusToIconTone(status)for status-driven tones.- Context (e.g.
tone="muted"for dense table chrome).
- Explicit semantic intent (
-
Resolve
variant:solidfor terminal badges / hero glyphs / toast leads, otherwise default outline. -
Resolve
weight:boldfor CTAs / section headers,thinfor dense table chrome, otherwise defaultregular. -
Accessibility: decorative? leave default (
aria-hidden="true"). Meaningful (icon-only button)? adddecorative={false}+aria-label="...". -
Drop redundant sizing (
h-*,w-*) in favor ofsize="xs|sm|md|lg". -
Strip the tone class from
classNameso callerclassNameonly carries layout/animation utilities. -
Re-run lint locally for the changed file:
5. Verification steps
Per phase / sub-batch
-
npm run lint— must pass with zero newnorthsight/prefer-appicon-tonewarnings in scope. -
npm run typecheck— must pass. -
npm run test -- icon—AppIconunit tests must remain green. -
Re-run the codemod in dry-run mode and confirm
byBucketcounts for the phase drop to 0:
Final acceptance (after Phase 6)
-
npm run audit:hardcoded-colors— no new hits on icon className regex. -
npm run validate— full gate green (format + typecheck + lint + build). - Visual spot check on three pages per core (list, detail, dialog) using the Preview URL.
- Update
mem://design/v3-visual-identity-and-component-specsnoting the migration is complete. - Mark this checklist’s phases as
✅.
6. Roll-out & rollback
- Roll-out: Land per-bucket in separate PRs (Shared → Platform → Cores → Domain-status → Solid → Weight) so reviewers can verify visually.
- Each PR includes:
- Before/after dry-run JSON diff for its bucket (paste relevant
byBucketslice). - Screenshots of 2 representative surfaces (light + dark mode preferred).
- Lint warning count diff.
- Before/after dry-run JSON diff for its bucket (paste relevant
- Rollback: Each bucket is a pure refactor; revert the PR. No DB / runtime contract changes.
7. Out of scope
- No new icon tokens, no new
AppIconprops. - No changes to icon registries (
WIZARD_STEP_ICONS,MODULE_ICONS, corestepIcons). - No automatic rewrite of raw
<svg>(manual review only — too rare to script safely). - No PHI/PII handling implications; pure UI refactor.
- No changes to test files (
tests/**,src/**/__tests__/**).
8. References
docs/development/ICON_GUIDE.md— usage patterns, tone/variant/weight examples.docs/development/SEMANTIC_COLORS.md— token catalog and PF-95 theming.reports/codemod-appicon-tone-dryrun.json— current codemod findings.src/shared/ui/icon.tsx—AppIconsource of truth.src/shared/lib/semantic-colors.ts—domainStatusToIconTone()mapping.