Skip to main content

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)

SignalCountSource
lucide-react JSX call sites in src/3,275grep -rE "from 'lucide-react'" src | wc -l
Existing <AppIcon> usages in src/28grep -rE "<AppIcon" src | wc -l
<AppIcon> w/ hard-coded tone class2grep -rEo "<AppIcon[^>]*text-(success|warning|destructive|info|muted-foreground|primary|accent|foreground)" src | wc -l

Lucide imports by bucket

BucketFiles importing lucide-reactFiles using <AppIcon>
src/shared/ui181
src/shared/components1210
src/platform/**7560
src/cores/**2,4710
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 0 — Tooling guardrails ✅

  • ESLint rule northsight/prefer-appicon-tone flags hard-coded tone classes on <AppIcon>.
  • Dry-run codemod report at reports/codemod-appicon-tone-dryrun.json.
  • Domain-status mapping domainStatusToIconTone() in src/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"] === 0 and byBucket["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 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.
  • Audit call sites: grep -rE "tone=\"(success|warning|destructive|info)\"" src and confirm any status-driven tones use the helper.
  • Replace hand-rolled status === 'approved' ? 'success' : ... ternaries with domainStatusToIconTone(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: Use variant="solid" on terminal status badges and hero glyphs.
  • EmptyState, ErrorState, SuccessState hero icons → variant="solid".
  • Toast leading icons (sonner custom 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 (Button leading icons) and section headers → weight="bold".
  • Everything else stays default (regular).

3. Coverage targets

BucketApprox filesAppIcon targetHard-coded tone class targetNotes
src/shared/ui18100%0Foundation; no exceptions.
src/shared/components12100%0Inc. EmptyState, ErrorState, StatusBadge.
src/platform/**75695%0Allow raw Lucide in icon registries (stepIcons, moduleIcons).
src/cores/**2,47190%≤ 5Allow raw Lucide in module-icon configs only.
tests/**, src/**/__tests__/**n/aexcludedexcludedTests 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:
  1. 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" />
    
  2. 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).
  3. Resolve variant: solid for terminal badges / hero glyphs / toast leads, otherwise default outline.
  4. Resolve weight: bold for CTAs / section headers, thin for dense table chrome, otherwise default regular.
  5. Accessibility: decorative? leave default (aria-hidden="true"). Meaningful (icon-only button)? add decorative={false} + aria-label="...".
  6. Drop redundant sizing (h-*, w-*) in favor of size="xs|sm|md|lg".
  7. Strip the tone class from className so caller className only carries layout/animation utilities.
  8. Re-run lint locally for the changed file:
    npx eslint --fix path/to/changed/file.tsx
    

5. Verification steps

Per phase / sub-batch

  • npm run lint — must pass with zero new northsight/prefer-appicon-tone warnings in scope.
  • npm run typecheck — must pass.
  • npm run test -- iconAppIcon unit tests must remain green.
  • Re-run the codemod in dry-run mode and confirm byBucket counts for the phase drop to 0:
    node /tmp/codemod-appicon-tone.mjs --dry-run > reports/codemod-appicon-tone-dryrun.json
    jq '.byBucket' reports/codemod-appicon-tone-dryrun.json
    

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-specs noting 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 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