Documentation Index Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Goal: Move every existing icon call site in src/ over to the AppIcon API
(tone / variant / weight), replacing raw <svg> / direct lucide-react
JSX and hard-coded text-* color classes with the unified wrapper from
src/shared/ui/icon.tsx.
Status: In progress (Phase 0 complete).
Owner: Design Systems.
Cross-links: ICON_GUIDE.md · SEMANTIC_COLORS.md · 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 new AppIcon props.
Baseline (captured 2026-04-21)
Signal Count Source lucide-react JSX call sites in src/3,275 grep -rE "from 'lucide-react'" src | wc -lExisting <AppIcon> usages in src/ 28 grep -rE "<AppIcon" src | wc -l<AppIcon> w/ hard-coded tone class2 grep -rEo "<AppIcon[^>]*text-(success|warning|destructive|info|muted-foreground|primary|accent|foreground)" src | wc -l
Lucide imports by bucket
Bucket Files importing lucide-react Files using <AppIcon> src/shared/ui18 1 src/shared/components12 10 src/platform/**756 0 src/cores/**2,471 0
Re-run the baseline before starting each phase:
grep -rE "from 'lucide-react'" src | wc -l
grep -rE "<AppIcon" src | wc -l
grep -rEo "<AppIcon[^>]*text-(success|warning|destructive|info|muted-foreground|primary|accent|foreground)" src | wc -l
2. Migration phases
Each phase ships as its own PR so reviewers can verify visually. Land in order.
Phase 1 — src/shared/ui and src/shared/components
Target: 100% AppIcon, 0 hard-coded tone classes.
Target: 95% AppIcon (allow raw Lucide only inside AppIcon itself + icon registries like WIZARD_STEP_ICONS, MODULE_ICONS, stepIcons); 0 hard-coded tone classes.
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 with LucideIcon typed values.
Verification block per core.
Phase 4 — Domain-status surfaces
Target: Every status badge / pill / workflow card resolves its tone via domainStatusToIconTone(status) instead of an ad-hoc switch.
Phase 5 — Solid variant sweep
Target: Use variant="solid" on terminal status badges and hero glyphs.
Phase 6 — Weight sweep
Target: Establish a weight rhythm.
3. Coverage targets
Bucket Approx files AppIcon target Hard-coded tone class target Notes src/shared/ui18 100% 0 Foundation; no exceptions. src/shared/components12 100% 0 Inc. EmptyState, ErrorState, StatusBadge. src/platform/**756 95% 0 Allow raw Lucide in icon registries (stepIcons, moduleIcons). src/cores/**2,471 90% ≤ 5 Allow raw Lucide in module-icon configs only. tests/**, src/**/__tests__/**n/a excluded excluded Tests stay verbatim.
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 :
// before
import { CheckCircle2 } from 'lucide-react' ;
< CheckCircle2 className = "h-4 w-4 text-success" />
// after
import { AppIcon } from '@/shared/ui/icon' ;
import { CheckCircle2 } from 'lucide-react' ;
< AppIcon icon = { CheckCircle2 } size = "sm" tone = "success" />
Resolve tone from 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).
Resolve variant : solid for terminal badges / hero glyphs / toast leads, otherwise default outline.
Resolve weight : bold for CTAs / section headers, thin for dense table chrome, otherwise default regular.
Accessibility : decorative? leave default (aria-hidden="true"). Meaningful (icon-only button)? add decorative={false} + aria-label="...".
Drop redundant sizing (h-*, w-*) in favor of size="xs|sm|md|lg".
Strip the tone class from className so caller className only carries layout/animation utilities.
Re-run lint locally for the changed file:
npx eslint --fix path/to/changed/file.tsx
5. Verification steps
Per phase / sub-batch
Final acceptance (after Phase 6)
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 byBucket slice).
Screenshots of 2 representative surfaces (light + dark mode preferred).
Lint warning count diff.
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 AppIcon props.
No changes to icon registries (WIZARD_STEP_ICONS, MODULE_ICONS, core stepIcons).
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