diff --git a/.planning/STATE.md b/.planning/STATE.md
index ed666d7..d1efc99 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,9 +2,9 @@
gsd_state_version: 1.0
milestone: v1.1
milestone_name: Fixes & Polish
-status: executing
+status: completed
stopped_at: Completed 06-03 display component icon migration
-last_updated: "2026-03-15T16:59:16.000Z"
+last_updated: "2026-03-15T17:04:22.268Z"
last_activity: 2026-03-15 -- Completed 06-03 display component icon migration
progress:
total_phases: 3
diff --git a/.planning/phases/06-category-icons/06-VERIFICATION.md b/.planning/phases/06-category-icons/06-VERIFICATION.md
new file mode 100644
index 0000000..a3916b8
--- /dev/null
+++ b/.planning/phases/06-category-icons/06-VERIFICATION.md
@@ -0,0 +1,112 @@
+---
+phase: 06-category-icons
+verified: 2026-03-15T17:10:00Z
+status: passed
+score: 16/16 must-haves verified
+re_verification: false
+---
+
+# Phase 6: Category Icons Verification Report
+
+**Phase Goal:** Categories use clean Lucide icons instead of emoji
+**Verified:** 2026-03-15T17:10:00Z
+**Status:** PASSED
+**Re-verification:** No — initial verification
+
+## Goal Achievement
+
+### Observable Truths
+
+| # | Truth | Status | Evidence |
+|----|-------|--------|----------|
+| 1 | Database schema uses `icon` column (not `emoji`) on categories table with default `package` | VERIFIED | `src/db/schema.ts` line 6: `icon: text("icon").notNull().default("package")` |
+| 2 | Zod schemas validate `icon` field as string (Lucide icon name) instead of `emoji` | VERIFIED | `src/shared/schemas.ts` lines 19, 25: `icon: z.string().min(1).max(50)` in both create and update schemas |
+| 3 | All server services reference `categories.icon` and return `categoryIcon` | VERIFIED | All 5 services confirmed: item.service.ts:22, thread.service.ts:25+70, setup.service.ts:60, totals.service.ts:12 |
+| 4 | Curated icon data with ~80-120 gear-relevant Lucide icons is available for the picker | VERIFIED | `src/client/lib/iconData.tsx` contains 119 icons (8 groups); grep count = 129 `name:` entries (includes group headers) |
+| 5 | A LucideIcon render component exists for displaying icons by name string | VERIFIED | `src/client/lib/iconData.tsx` lines 237-249: `export function LucideIcon` with kebab-to-PascalCase conversion and Package fallback |
+| 6 | Existing emoji data in the database is migrated to equivalent Lucide icon names | VERIFIED | `drizzle/0001_rename_emoji_to_icon.sql`: ALTER TABLE RENAME COLUMN + CASE UPDATE for 12 emoji mappings |
+| 7 | User can open an icon picker popover and browse Lucide icons organized by group tabs | VERIFIED | `src/client/components/IconPicker.tsx` (243 lines): portal popover, 8 group tabs with LucideIcon, 6-column icon grid |
+| 8 | User can search icons by name/keyword and results filter in real time | VERIFIED | `IconPicker.tsx` lines 96-113: `useMemo` filtering by `name.includes(q)` and `keywords.some(kw => kw.includes(q))` |
+| 9 | User can select a Lucide icon when creating a new category inline (CategoryPicker) | VERIFIED | `CategoryPicker.tsx` lines 232-239: IconPicker rendered in inline create flow with `newCategoryIcon` state |
+| 10 | User can select a Lucide icon when editing a category (CategoryHeader) | VERIFIED | `CategoryHeader.tsx` line 51: `` in edit mode |
+| 11 | User can select a Lucide icon during onboarding category creation | VERIFIED | `OnboardingWizard.tsx` lines 5, 16, 44: imports IconPicker, uses `categoryIcon` state, passes `icon: categoryIcon` to mutate |
+| 12 | Category picker combobox shows Lucide icon + name for each category | VERIFIED | `CategoryPicker.tsx` lines 143-150, 208-213: LucideIcon prefix in closed input and in each dropdown list item |
+| 13 | Item cards display category Lucide icon in placeholder area and category badge | VERIFIED | `ItemCard.tsx` lines 75, 95: LucideIcon at size 36 in placeholder, size 14 in category badge |
+| 14 | Candidate cards display category Lucide icon in placeholder and badge | VERIFIED | `CandidateCard.tsx` lines 45, 65: same pattern as ItemCard |
+| 15 | Thread cards display Lucide icon next to category name | VERIFIED | `ThreadCard.tsx` line 70: `` |
+| 16 | Old EmojiPicker.tsx and emojiData.ts files are deleted, zero emoji references remain in src/ | VERIFIED | Both files confirmed deleted; grep of `src/` for `categoryEmoji`, `EmojiPicker`, `emojiData` returns zero results |
+
+**Score:** 16/16 truths verified
+
+### Required Artifacts
+
+| Artifact | Expected | Status | Details |
+|----------|----------|--------|---------|
+| `src/db/schema.ts` | Categories table with icon column | VERIFIED | `icon: text("icon").notNull().default("package")` — no `emoji` column |
+| `src/shared/schemas.ts` | Category Zod schemas with icon field | VERIFIED | `icon: z.string().min(1).max(50)` in createCategorySchema and updateCategorySchema |
+| `src/client/lib/iconData.tsx` | Curated icon groups and LucideIcon component | VERIFIED | Exports `iconGroups` (8 groups, 119 icons), `LucideIcon`, `EMOJI_TO_ICON_MAP` |
+| `tests/helpers/db.ts` | Test helper with icon column | VERIFIED | `icon TEXT NOT NULL DEFAULT 'package'` at line 14; seed uses `icon: "package"` |
+| `src/client/components/IconPicker.tsx` | Lucide icon picker popover component | VERIFIED | 243 lines; portal-based popover with search, group tabs, icon grid |
+| `src/client/components/CategoryPicker.tsx` | Updated category combobox with icon display | VERIFIED | Contains `LucideIcon`, `IconPicker`, `data-icon-picker` exclusion in click-outside handler |
+| `src/client/components/CategoryHeader.tsx` | Category header with icon display and IconPicker for editing | VERIFIED | Contains `IconPicker` and `LucideIcon`; `icon` prop (not `emoji`) |
+| `src/client/components/ItemCard.tsx` | Item card with Lucide icon display | VERIFIED | Contains `categoryIcon` prop and `LucideIcon` at 36px and 14px |
+| `src/client/components/ThreadCard.tsx` | Thread card with Lucide icon display | VERIFIED | Contains `categoryIcon` prop and `LucideIcon` at 16px |
+| `drizzle/0001_rename_emoji_to_icon.sql` | Migration with data conversion | VERIFIED | ALTER TABLE RENAME COLUMN + emoji-to-icon CASE UPDATE |
+
+### Key Link Verification
+
+| From | To | Via | Status | Details |
+|------|----|-----|--------|---------|
+| `src/db/schema.ts` | `src/shared/types.ts` | Drizzle `$inferSelect` | VERIFIED | `type Category = typeof categories.$inferSelect` — picks up `icon` field automatically |
+| `src/shared/schemas.ts` | `src/server/routes/categories.ts` | Zod validation | VERIFIED | `createCategorySchema` and `updateCategorySchema` imported and used as validators |
+| `src/client/lib/iconData.tsx` | `src/client/components/IconPicker.tsx` | import | VERIFIED | `import { iconGroups, LucideIcon } from "../lib/iconData"` at line 3 |
+| `src/client/components/IconPicker.tsx` | `src/client/components/CategoryPicker.tsx` | import | VERIFIED | `import { IconPicker } from "./IconPicker"` at line 7 |
+| `src/client/components/IconPicker.tsx` | `src/client/components/CategoryHeader.tsx` | import | VERIFIED | `import { IconPicker } from "./IconPicker"` at line 5 |
+| `src/client/components/ItemCard.tsx` | `src/client/lib/iconData.tsx` | import LucideIcon | VERIFIED | `import { LucideIcon } from "../lib/iconData"` at line 2 |
+| `src/client/routes/collection/index.tsx` | `src/client/components/CategoryHeader.tsx` | icon prop | VERIFIED | `icon={categoryIcon}` at line 145 |
+
+### Requirements Coverage
+
+| Requirement | Source Plan | Description | Status | Evidence |
+|-------------|------------|-------------|--------|----------|
+| CAT-01 | 06-02 | User can select a Lucide icon when creating/editing a category (icon picker) | SATISFIED | IconPicker component exists and is wired into CategoryPicker, CategoryHeader, and OnboardingWizard |
+| CAT-02 | 06-03 | Category icons display as Lucide icons throughout the app (cards, headers, lists) | SATISFIED | ItemCard, CandidateCard, ThreadCard, ItemPicker, CategoryHeader all render LucideIcon with categoryIcon prop |
+| CAT-03 | 06-01 | Existing emoji categories are migrated to equivalent Lucide icons | SATISFIED | Migration SQL `0001_rename_emoji_to_icon.sql` renames column and converts emoji values to icon names |
+
+### Anti-Patterns Found
+
+| File | Line | Pattern | Severity | Impact |
+|------|------|---------|----------|--------|
+| `src/client/routes/collection/index.tsx` | 64 | `
🎒
` emoji in empty state | Info | Decorative emoji in the gear collection empty state (not a category icon) — outside phase scope |
+
+The single emoji found is a decorative `🎒` in the collection empty state UI — it is not a category icon and is not part of the data model. Zero `categoryEmoji`, `EmojiPicker`, or `emojiData` references remain.
+
+### Human Verification Required
+
+#### 1. IconPicker Popover Visual Layout
+
+**Test:** Navigate to any category create/edit flow (CategoryPicker inline create, or CategoryHeader edit mode). Click the icon trigger button.
+**Expected:** Popover opens below the trigger with a search input at top, 8 group tab icons, and a 6-column icon grid. Clicking a group tab switches the icon set. Typing in search filters icons in real time. Clicking an icon selects it and closes the popover.
+**Why human:** Portal-based popover positioning and interactive search filtering cannot be confirmed by static analysis.
+
+#### 2. Onboarding Icon Selection
+
+**Test:** Clear the `onboardingComplete` setting (or use a fresh DB) and walk through onboarding step 2.
+**Expected:** "Icon (optional)" label appears above an IconPicker trigger button (not an EmojiPicker). Selecting an icon and creating the category persists the icon name in the database.
+**Why human:** End-to-end flow through a stateful wizard; requires runtime execution.
+
+#### 3. Category Filter Dropdown (Known Limitation)
+
+**Test:** Navigate to collection > planning tab. Check the category filter dropdown (top-right of the planning view).
+**Expected:** The dropdown shows category names only (no icons). This is a confirmed known limitation documented in the 06-02 SUMMARY — native HTML `