diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index aee1912..12b4119 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -63,10 +63,12 @@ Plans:
1. User can browse and select a Lucide icon from a picker when creating or editing a category
2. Category icons render as Lucide icons everywhere they appear (cards, headers, lists, dashboard)
3. Existing emoji-based categories display as equivalent Lucide icons without manual user intervention
-**Plans**: TBD
+**Plans**: 3 plans
Plans:
-- [ ] 06-01: TBD
+- [ ] 06-01-PLAN.md โ Backend schema migration (emoji to icon), install lucide-react, create icon data and LucideIcon component
+- [ ] 06-02-PLAN.md โ Build IconPicker component, update category create/edit components
+- [ ] 06-03-PLAN.md โ Update all display components to Lucide icons, delete old emoji code
## Progress
@@ -80,4 +82,4 @@ Phases execute in numeric order: 4 -> 5 -> 6
| 3. Setups and Dashboard | v1.0 | 3/3 | Complete | 2026-03-15 |
| 4. Database & Planning Fixes | v1.1 | 1/2 | In progress | - |
| 5. Image Handling | 2/2 | Complete | 2026-03-15 | - |
-| 6. Category Icons | v1.1 | 0/? | Not started | - |
+| 6. Category Icons | v1.1 | 0/3 | Not started | - |
diff --git a/.planning/phases/06-category-icons/06-01-PLAN.md b/.planning/phases/06-category-icons/06-01-PLAN.md
new file mode 100644
index 0000000..3e5e30f
--- /dev/null
+++ b/.planning/phases/06-category-icons/06-01-PLAN.md
@@ -0,0 +1,278 @@
+---
+phase: 06-category-icons
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - src/db/schema.ts
+ - src/shared/schemas.ts
+ - src/shared/types.ts
+ - src/db/seed.ts
+ - src/server/services/category.service.ts
+ - src/server/services/item.service.ts
+ - src/server/services/thread.service.ts
+ - src/server/services/setup.service.ts
+ - src/server/services/totals.service.ts
+ - tests/helpers/db.ts
+ - src/client/lib/iconData.ts
+ - package.json
+autonomous: true
+requirements: [CAT-03]
+
+must_haves:
+ truths:
+ - "Database schema uses 'icon' column (not 'emoji') on categories table with default 'package'"
+ - "Zod schemas validate 'icon' field as a string (Lucide icon name) instead of 'emoji'"
+ - "All server services reference categories.icon instead of categories.emoji"
+ - "Curated icon data with ~80-120 gear-relevant Lucide icons is available for the picker"
+ - "A LucideIcon render component exists for displaying icons by name string"
+ - "Existing emoji data in the database is migrated to equivalent Lucide icon names"
+ artifacts:
+ - path: "src/db/schema.ts"
+ provides: "Categories table with icon column"
+ contains: "icon.*text.*default.*package"
+ - path: "src/shared/schemas.ts"
+ provides: "Category Zod schemas with icon field"
+ contains: "icon.*z.string"
+ - path: "src/client/lib/iconData.ts"
+ provides: "Curated icon groups and LucideIcon component"
+ exports: ["iconGroups", "LucideIcon", "EMOJI_TO_ICON_MAP"]
+ - path: "tests/helpers/db.ts"
+ provides: "Test helper with icon column"
+ contains: "icon TEXT NOT NULL DEFAULT"
+ key_links:
+ - from: "src/db/schema.ts"
+ to: "src/shared/types.ts"
+ via: "Drizzle type inference"
+ pattern: "categories\\.\\$inferSelect"
+ - from: "src/shared/schemas.ts"
+ to: "src/server/routes/categories.ts"
+ via: "Zod validation"
+ pattern: "createCategorySchema"
+ - from: "src/client/lib/iconData.ts"
+ to: "downstream icon picker and display components"
+ via: "import"
+ pattern: "iconGroups|LucideIcon"
+---
+
+
+Migrate the category data layer from emoji to Lucide icons and create the icon data infrastructure.
+
+Purpose: Establish the foundation (schema, types, icon data, render helper) that all UI components will consume. Without this, no component can display or select Lucide icons.
+Output: Updated DB schema with `icon` column, Zod schemas with `icon` field, all services updated, curated icon data file with render component, Drizzle migration generated, lucide-react installed.
+
+
+
+@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
+@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/06-category-icons/06-CONTEXT.md
+
+
+
+
+From src/db/schema.ts (CURRENT - will be modified):
+```typescript
+export const categories = sqliteTable("categories", {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ name: text("name").notNull().unique(),
+ emoji: text("emoji").notNull().default("\u{1F4E6}"), // RENAME to icon, default "package"
+ createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
+});
+```
+
+From src/shared/schemas.ts (CURRENT - will be modified):
+```typescript
+export const createCategorySchema = z.object({
+ name: z.string().min(1, "Category name is required"),
+ emoji: z.string().min(1).max(4).default("\u{1F4E6}"), // RENAME to icon, change validation
+});
+export const updateCategorySchema = z.object({
+ id: z.number().int().positive(),
+ name: z.string().min(1).optional(),
+ emoji: z.string().min(1).max(4).optional(), // RENAME to icon
+});
+```
+
+From src/server/services/*.ts (all reference categories.emoji):
+```typescript
+// item.service.ts line 22, thread.service.ts lines 25+70, setup.service.ts line 60, totals.service.ts line 12
+categoryEmoji: categories.emoji, // RENAME to categoryIcon: categories.icon
+```
+
+From src/server/services/category.service.ts:
+```typescript
+export function createCategory(db, data: { name: string; emoji?: string }) { ... }
+export function updateCategory(db, id, data: { name?: string; emoji?: string }) { ... }
+```
+
+
+
+
+
+
+ Task 1: Migrate schema, Zod schemas, services, test helper, and seed to icon field
+
+ src/db/schema.ts,
+ src/shared/schemas.ts,
+ src/server/services/category.service.ts,
+ src/server/services/item.service.ts,
+ src/server/services/thread.service.ts,
+ src/server/services/setup.service.ts,
+ src/server/services/totals.service.ts,
+ src/db/seed.ts,
+ tests/helpers/db.ts
+
+
+ 1. In `src/db/schema.ts`: Rename the `emoji` column on `categories` to `icon` with `text("icon").notNull().default("package")`. The column name in the database changes from `emoji` to `icon`.
+
+ 2. In `src/shared/schemas.ts`:
+ - `createCategorySchema`: Replace `emoji: z.string().min(1).max(4).default("๐ฆ")` with `icon: z.string().min(1).max(50).default("package")`. The max is 50 to allow Lucide icon names like "mountain-snow".
+ - `updateCategorySchema`: Replace `emoji: z.string().min(1).max(4).optional()` with `icon: z.string().min(1).max(50).optional()`.
+
+ 3. In `src/server/services/category.service.ts`:
+ - `createCategory`: Change function parameter type from `{ name: string; emoji?: string }` to `{ name: string; icon?: string }`. Update the spread to use `data.icon` and `{ icon: data.icon }`.
+ - `updateCategory`: Change parameter type from `{ name?: string; emoji?: string }` to `{ name?: string; icon?: string }`.
+
+ 4. In `src/server/services/item.service.ts`: Change `categoryEmoji: categories.emoji` to `categoryIcon: categories.icon` in the select.
+
+ 5. In `src/server/services/thread.service.ts`: Same rename โ `categoryEmoji: categories.emoji` to `categoryIcon: categories.icon` in both `getAllThreads` and `getThreadById` functions.
+
+ 6. In `src/server/services/setup.service.ts`: Same rename โ `categoryEmoji` to `categoryIcon`.
+
+ 7. In `src/server/services/totals.service.ts`: Same rename โ `categoryEmoji` to `categoryIcon`.
+
+ 8. In `src/db/seed.ts`: Change `emoji: "\u{1F4E6}"` to `icon: "package"`.
+
+ 9. In `tests/helpers/db.ts`: Change the CREATE TABLE statement for categories to use `icon TEXT NOT NULL DEFAULT 'package'` instead of `emoji TEXT NOT NULL DEFAULT '๐ฆ'`. Update the seed insert to use `icon: "package"` instead of `emoji: "\u{1F4E6}"`.
+
+ 10. Generate the Drizzle migration: Run `bun run db:generate` to create the migration SQL. The migration needs to handle renaming the column AND converting existing emoji values to icon names. After generation, inspect the migration file and add data conversion SQL if Drizzle doesn't handle it automatically. The emoji-to-icon mapping for migration:
+ - ๐ฆ -> "package"
+ - ๐๏ธ/โบ -> "tent"
+ - ๐ฒ -> "bike"
+ - ๐ท -> "camera"
+ - ๐ -> "backpack"
+ - ๐ -> "shirt"
+ - ๐ง -> "wrench"
+ - ๐ณ -> "cooking-pot"
+ - Any unmapped emoji -> "package" (fallback)
+
+ NOTE: Since SQLite doesn't support ALTER TABLE RENAME COLUMN in all versions, the migration may need to recreate the table. Check the generated migration and ensure it works. If `bun run db:generate` produces a column rename, verify it. If it produces a drop+recreate, ensure data is preserved. You may need to manually write migration SQL that: (a) creates a new column `icon`, (b) updates it from `emoji` with the mapping, (c) drops the `emoji` column. Test with `bun run db:push`.
+
+
+ bun test tests/services/category.service.test.ts -t "create" 2>&1 | head -20; echo "---"; bun run db:push 2>&1 | tail -5
+
+
+ - categories table has `icon` column (text, default "package") instead of `emoji`
+ - All Zod schemas use `icon` field
+ - All services reference `categories.icon` and return `categoryIcon`
+ - Test helper creates table with `icon` column
+ - `bun run db:push` applies migration without errors
+
+
+
+
+ Task 2: Install lucide-react and create icon data file with LucideIcon component
+
+ package.json,
+ src/client/lib/iconData.ts
+
+
+ 1. Install lucide-react: `bun add lucide-react`
+
+ 2. Create `src/client/lib/iconData.ts` with:
+
+ a) An `EMOJI_TO_ICON_MAP` constant (Record<string, string>) mapping emoji characters to Lucide icon names. Cover at minimum:
+ - ๐ฆ -> "package", ๐๏ธ -> "tent", โบ -> "tent", ๐ฒ -> "bike", ๐ท -> "camera"
+ - ๐ -> "backpack", ๐ -> "shirt", ๐ง -> "wrench", ๐ณ -> "cooking-pot"
+ - ๐ฎ -> "gamepad-2", ๐ป -> "laptop", ๐๏ธ -> "mountain-snow", โฐ๏ธ -> "mountain"
+ - ๐๏ธ -> "umbrella-off", ๐งญ -> "compass", ๐ฆ -> "flashlight", ๐ -> "battery"
+ - ๐ฑ -> "smartphone", ๐ง -> "headphones", ๐งค -> "hand", ๐งฃ -> "scarf"
+ - ๐ -> "footprints", ๐ฅพ -> "footprints", ๐งข -> "hard-hat", ๐ถ๏ธ -> "glasses"
+ - Plus any other reasonable gear-related emoji from the old emojiData.ts
+
+ b) An `IconGroup` interface and `iconGroups` array with ~80-120 curated gear-relevant Lucide icons organized into groups:
+ ```typescript
+ interface IconEntry { name: string; keywords: string[] }
+ interface IconGroup { name: string; icon: string; icons: IconEntry[] }
+ ```
+ Groups (matching picker tabs):
+ - **Outdoor**: tent, campfire, mountain, mountain-snow, compass, map, map-pin, binoculars, tree-pine, trees, sun, cloud-rain, snowflake, wind, flame, leaf, flower-2, sunrise, sunset, moon, star, thermometer
+ - **Travel**: backpack, luggage, plane, car, bike, ship, train-front, map-pinned, globe, ticket, route, navigation, milestone, fuel, parking-meter
+ - **Sports**: dumbbell, trophy, medal, timer, heart-pulse, footprints, gauge, target, flag, swords, shield, zap
+ - **Electronics**: laptop, smartphone, tablet-smartphone, headphones, camera, battery, bluetooth, wifi, usb, monitor, keyboard, mouse, gamepad-2, speaker, radio, tv, plug, cable, cpu, hard-drive
+ - **Clothing**: shirt, glasses, watch, gem, scissors, ruler, palette
+ - **Cooking**: cooking-pot, utensils, cup-soda, coffee, beef, fish, apple, wheat, flame-kindling, refrigerator, microwave
+ - **Tools**: wrench, hammer, screwdriver, drill, ruler, tape-measure, flashlight, pocket-knife, axe, shovel, paintbrush, scissors, cog, nut
+ - **General**: package, box, tag, bookmark, archive, folder, grid-3x3, list, layers, circle-dot, square, hexagon, triangle, heart, star, plus, check, x
+
+ Each icon entry has `name` (the Lucide icon name) and `keywords` (array of search terms for filtering).
+
+ c) A `LucideIcon` React component that renders a Lucide icon by name string:
+ ```typescript
+ import { icons } from "lucide-react";
+
+ interface LucideIconProps {
+ name: string;
+ size?: number;
+ className?: string;
+ }
+
+ export function LucideIcon({ name, size = 20, className = "" }: LucideIconProps) {
+ const IconComponent = icons[name as keyof typeof icons];
+ if (!IconComponent) {
+ const FallbackIcon = icons["Package"];
+ return ;
+ }
+ return ;
+ }
+ ```
+
+ IMPORTANT: Lucide icon names in the `icons` map use PascalCase (e.g., "Package", "MountainSnow"). The `name` prop should accept kebab-case (matching Lucide convention) and convert to PascalCase for lookup. Add a conversion helper:
+ ```typescript
+ function toPascalCase(str: string): string {
+ return str.split("-").map(s => s.charAt(0).toUpperCase() + s.slice(1)).join("");
+ }
+ ```
+ Use `icons[toPascalCase(name)]` for lookup.
+
+ NOTE: This approach imports the entire lucide-react icons object for dynamic lookup by name. This is intentional โ the icon picker needs access to all icons by name string. Tree-shaking won't help here since we need runtime lookup. The bundle impact is acceptable for this single-user app.
+
+
+ bun run build 2>&1 | tail -5; echo "---"; grep -c "lucide-react" package.json
+
+
+ - lucide-react is installed as a dependency
+ - `src/client/lib/iconData.ts` exports `iconGroups`, `LucideIcon`, and `EMOJI_TO_ICON_MAP`
+ - `LucideIcon` renders any Lucide icon by kebab-case name string with fallback to Package icon
+ - Icon groups contain ~80-120 curated gear-relevant icons across 8 groups
+ - `bun run build` succeeds without errors
+
+
+
+
+
+
+- `bun test` passes (all existing tests work with icon field)
+- `bun run build` succeeds
+- Database migration applies cleanly via `bun run db:push`
+- `src/client/lib/iconData.ts` exports are importable
+
+
+
+- Categories table uses `icon` text column with "package" default
+- All Zod schemas, services, types reference `icon` not `emoji`
+- lucide-react installed
+- Icon data file with curated groups and LucideIcon render component exists
+- All tests pass, build succeeds
+
+
+
diff --git a/.planning/phases/06-category-icons/06-02-PLAN.md b/.planning/phases/06-category-icons/06-02-PLAN.md
new file mode 100644
index 0000000..8de18a3
--- /dev/null
+++ b/.planning/phases/06-category-icons/06-02-PLAN.md
@@ -0,0 +1,237 @@
+---
+phase: 06-category-icons
+plan: 02
+type: execute
+wave: 2
+depends_on: [06-01]
+files_modified:
+ - src/client/components/IconPicker.tsx
+ - src/client/components/CategoryPicker.tsx
+ - src/client/components/CategoryHeader.tsx
+ - src/client/components/OnboardingWizard.tsx
+ - src/client/components/CreateThreadModal.tsx
+autonomous: true
+requirements: [CAT-01]
+
+must_haves:
+ truths:
+ - "User can open an icon picker popover and browse Lucide icons organized by group tabs"
+ - "User can search icons by name/keyword and results filter in real time"
+ - "User can select a Lucide icon when creating a new category inline (CategoryPicker)"
+ - "User can select a Lucide icon when editing a category (CategoryHeader)"
+ - "User can select a Lucide icon during onboarding category creation"
+ - "Category picker combobox shows Lucide icon + name for each category (not emoji)"
+ artifacts:
+ - path: "src/client/components/IconPicker.tsx"
+ provides: "Lucide icon picker popover component"
+ min_lines: 150
+ - path: "src/client/components/CategoryPicker.tsx"
+ provides: "Updated category combobox with icon display"
+ contains: "LucideIcon"
+ - path: "src/client/components/CategoryHeader.tsx"
+ provides: "Updated category header with icon display and IconPicker for editing"
+ contains: "IconPicker"
+ key_links:
+ - from: "src/client/components/IconPicker.tsx"
+ to: "src/client/lib/iconData.ts"
+ via: "import"
+ pattern: "iconGroups.*iconData"
+ - from: "src/client/components/CategoryPicker.tsx"
+ to: "src/client/components/IconPicker.tsx"
+ via: "import"
+ pattern: "IconPicker"
+ - from: "src/client/components/CategoryHeader.tsx"
+ to: "src/client/components/IconPicker.tsx"
+ via: "import"
+ pattern: "IconPicker"
+---
+
+
+Build the IconPicker component and update all category create/edit components to use Lucide icons instead of emoji.
+
+Purpose: Enable users to browse, search, and select Lucide icons when creating or editing categories. This is the primary user-facing feature of the phase.
+Output: New IconPicker component, updated CategoryPicker, CategoryHeader, OnboardingWizard, and CreateThreadModal.
+
+
+
+@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
+@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/06-category-icons/06-CONTEXT.md
+@.planning/phases/06-category-icons/06-01-SUMMARY.md
+
+
+
+
+From src/client/lib/iconData.ts (created in Plan 01):
+```typescript
+export interface IconEntry { name: string; keywords: string[] }
+export interface IconGroup { name: string; icon: string; icons: IconEntry[] }
+export const iconGroups: IconGroup[];
+export const EMOJI_TO_ICON_MAP: Record;
+
+interface LucideIconProps { name: string; size?: number; className?: string; }
+export function LucideIcon({ name, size, className }: LucideIconProps): JSX.Element;
+```
+
+From src/shared/schemas.ts (updated in Plan 01):
+```typescript
+export const createCategorySchema = z.object({
+ name: z.string().min(1, "Category name is required"),
+ icon: z.string().min(1).max(50).default("package"),
+});
+export const updateCategorySchema = z.object({
+ id: z.number().int().positive(),
+ name: z.string().min(1).optional(),
+ icon: z.string().min(1).max(50).optional(),
+});
+```
+
+From src/client/components/EmojiPicker.tsx (EXISTING - architecture reference, will be replaced):
+```typescript
+interface EmojiPickerProps {
+ value: string;
+ onChange: (emoji: string) => void;
+ size?: "sm" | "md";
+}
+// Uses: createPortal, click-outside, escape key, search, category tabs, positioned popover
+```
+
+
+
+
+
+
+ Task 1: Create IconPicker component
+ src/client/components/IconPicker.tsx
+
+ Create `src/client/components/IconPicker.tsx` following the same portal-based popover pattern as EmojiPicker.tsx but rendering Lucide icons.
+
+ Props interface:
+ ```typescript
+ interface IconPickerProps {
+ value: string; // Current icon name (kebab-case, e.g. "tent")
+ onChange: (icon: string) => void;
+ size?: "sm" | "md";
+ }
+ ```
+
+ Architecture (mirror EmojiPicker exactly for these behaviors):
+ - Portal-based popover via `createPortal(popup, document.body)`
+ - Trigger button: bordered square box showing the selected LucideIcon, or "+" when empty
+ - Position calculation: measure trigger rect, place below (or above if not enough space), clamp left to viewport
+ - Click-outside detection via document mousedown listener
+ - Escape key closes popover
+ - Focus search input on open
+ - `data-icon-picker` attribute on popover div (for click-outside exclusion in CategoryPicker)
+ - Stop mousedown propagation from popover (so parent click-outside handlers don't fire)
+
+ Popover content:
+ - Search input at top (placeholder: "Search icons...")
+ - Group tabs below search (only shown when not searching). Each tab shows a small LucideIcon for that group's `icon` field. Active tab highlighted with blue.
+ - Icon grid: 6 columns. Each cell renders a LucideIcon at 20px, with hover highlight, name as title attribute. On click, call `onChange(icon.name)` and close.
+ - When searching: filter across all groups by matching query against icon `name` and `keywords`. Show flat grid of results. Show "No icons found" if empty.
+ - Popover width: ~w-72 (288px). Max grid height: ~max-h-56 with overflow-y-auto.
+
+ Trigger button styling:
+ - `size="md"`: `w-12 h-12` โ icon at 24px inside, gray-500 color
+ - `size="sm"`: `w-10 h-10` โ icon at 20px inside, gray-500 color
+ - Border, rounded-md, hover:border-gray-300, hover:bg-gray-50
+
+ Import `iconGroups` and `LucideIcon` from `../lib/iconData`.
+ Import `icons` from `lucide-react` only if needed for tab icons (or just use LucideIcon component for tabs too).
+
+
+ bun run build 2>&1 | tail -5
+
+
+ - IconPicker component renders a trigger button with the selected Lucide icon
+ - Clicking trigger opens a portal popover with search + group tabs + icon grid
+ - Search filters icons across all groups by name and keywords
+ - Selecting an icon calls onChange and closes popover
+ - Click-outside and Escape close the popover
+ - Build succeeds
+
+
+
+
+ Task 2: Update CategoryPicker, CategoryHeader, OnboardingWizard, and CreateThreadModal
+
+ src/client/components/CategoryPicker.tsx,
+ src/client/components/CategoryHeader.tsx,
+ src/client/components/OnboardingWizard.tsx,
+ src/client/components/CreateThreadModal.tsx
+
+
+ **CategoryPicker.tsx:**
+ 1. Replace `import { EmojiPicker }` with `import { IconPicker }` from `./IconPicker` and `import { LucideIcon }` from `../lib/iconData`.
+ 2. Change state: `newCategoryEmoji` -> `newCategoryIcon`, default from `"๐ฆ"` to `"package"`.
+ 3. In `handleConfirmCreate`: Change `{ name, emoji: newCategoryIcon }` to `{ name, icon: newCategoryIcon }`.
+ 4. In click-outside handler: Change `data-emoji-picker` check to `data-icon-picker`.
+ 5. Reset: Change all `setNewCategoryEmoji("๐ฆ")` to `setNewCategoryIcon("package")`.
+ 6. In the combobox input display (when closed): Replace `${selectedCategory.emoji} ` text prefix with nothing โ instead, add a LucideIcon before the input or use a different display approach. Best approach: when not open and a category is selected, show a small LucideIcon (size 16, className="text-gray-500 inline") before the category name in the input value.
+
+ Actually, for simplicity with the input element, render the icon as a visual prefix:
+ - Wrap input in a div with `relative` positioning
+ - Add a `LucideIcon` absolutely positioned on the left (pl-8 on input for padding)
+ - Input value when closed: just `selectedCategory.name` (no emoji prefix)
+ - Only show the icon prefix when a category is selected and dropdown is closed
+
+ 7. In the dropdown list items: Replace `{cat.emoji} {cat.name}` with ` {cat.name}`.
+ 8. In the inline create flow: Replace `` with ``.
+
+ **CategoryHeader.tsx:**
+ 1. Replace `import { EmojiPicker }` with `import { IconPicker }` from `./IconPicker` and `import { LucideIcon }` from `../lib/iconData`.
+ 2. Props: rename `emoji` to `icon` (type stays string).
+ 3. State: `editEmoji` -> `editIcon`.
+ 4. In `handleSave`: Change `emoji: editEmoji` to `icon: editIcon`.
+ 5. Edit mode: Replace `` with ``.
+ 6. Display mode: Replace `{emoji}` with ``.
+ 7. Edit button onClick: Change `setEditEmoji(emoji)` to `setEditIcon(icon)`.
+
+ **OnboardingWizard.tsx:**
+ 1. Replace `import { EmojiPicker }` with `import { IconPicker }` from `./IconPicker`.
+ 2. State: `categoryEmoji` -> `categoryIcon`, default from `""` to `""` (empty is fine, picker shows "+").
+ 3. In `handleCreateCategory`: Change `emoji: categoryEmoji.trim() || undefined` to `icon: categoryIcon.trim() || undefined`.
+ 4. In step 2 JSX: Change label from "Emoji (optional)" to "Icon (optional)". Replace `` with ``.
+
+ **CreateThreadModal.tsx:**
+ 1. Import `LucideIcon` from `../lib/iconData`.
+ 2. In the category list: Replace `{cat.emoji} {cat.name}` with ` {cat.name}`.
+
+
+ bun run build 2>&1 | tail -5
+
+
+ - CategoryPicker shows Lucide icons inline for each category and uses IconPicker for inline create
+ - CategoryHeader displays Lucide icon in view mode and offers IconPicker in edit mode
+ - OnboardingWizard uses IconPicker for category creation step
+ - CreateThreadModal shows Lucide icons next to category names
+ - No remaining imports of EmojiPicker in these files
+ - Build succeeds
+
+
+
+
+
+
+- `bun run build` succeeds
+- No TypeScript errors related to emoji/icon types
+- No remaining imports of EmojiPicker in modified files
+
+
+
+- IconPicker component exists with search, group tabs, and icon grid
+- All category create/edit flows use IconPicker instead of EmojiPicker
+- Category display in pickers and headers shows Lucide icons
+- Build succeeds without errors
+
+
+
diff --git a/.planning/phases/06-category-icons/06-03-PLAN.md b/.planning/phases/06-category-icons/06-03-PLAN.md
new file mode 100644
index 0000000..a9fae69
--- /dev/null
+++ b/.planning/phases/06-category-icons/06-03-PLAN.md
@@ -0,0 +1,210 @@
+---
+phase: 06-category-icons
+plan: 03
+type: execute
+wave: 2
+depends_on: [06-01]
+files_modified:
+ - src/client/components/ItemCard.tsx
+ - src/client/components/CandidateCard.tsx
+ - src/client/components/ThreadCard.tsx
+ - src/client/components/ItemPicker.tsx
+ - src/client/routes/collection/index.tsx
+ - src/client/routes/setups/$setupId.tsx
+ - src/client/routes/threads/$threadId.tsx
+ - src/client/components/EmojiPicker.tsx
+ - src/client/lib/emojiData.ts
+autonomous: true
+requirements: [CAT-02]
+
+must_haves:
+ truths:
+ - "Item cards display category Lucide icon in the image placeholder area (not emoji)"
+ - "Item cards display Lucide icon in the category badge/pill (not emoji)"
+ - "Candidate cards display category Lucide icon in placeholder and badge"
+ - "Thread cards display Lucide icon next to category name"
+ - "Collection view category headers use icon prop (not emoji)"
+ - "Setup detail view category headers use icon prop (not emoji)"
+ - "ItemPicker shows Lucide icons next to category names"
+ - "Category filter dropdown in collection view shows Lucide icons"
+ - "Old EmojiPicker.tsx and emojiData.ts files are deleted"
+ - "No remaining emoji references in the codebase"
+ artifacts:
+ - path: "src/client/components/ItemCard.tsx"
+ provides: "Item card with Lucide icon display"
+ contains: "categoryIcon"
+ - path: "src/client/components/ThreadCard.tsx"
+ provides: "Thread card with Lucide icon display"
+ contains: "categoryIcon"
+ key_links:
+ - from: "src/client/components/ItemCard.tsx"
+ to: "src/client/lib/iconData.ts"
+ via: "import LucideIcon"
+ pattern: "LucideIcon"
+ - from: "src/client/routes/collection/index.tsx"
+ to: "src/client/components/CategoryHeader.tsx"
+ via: "icon prop"
+ pattern: "icon=.*categoryIcon"
+---
+
+
+Update all display-only components to render Lucide icons instead of emoji, and remove old emoji code.
+
+Purpose: Complete the visual migration so every category icon in the app renders as a Lucide icon. Clean up old emoji code to leave zero emoji references.
+Output: All display components updated, old EmojiPicker and emojiData files deleted.
+
+
+
+@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
+@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/06-category-icons/06-CONTEXT.md
+@.planning/phases/06-category-icons/06-01-SUMMARY.md
+
+
+
+From src/client/lib/iconData.ts:
+```typescript
+export function LucideIcon({ name, size, className }: { name: string; size?: number; className?: string }): JSX.Element;
+```
+
+
+From services (after Plan 01):
+```typescript
+// All services return: { ...fields, categoryIcon: string } instead of categoryEmoji
+```
+
+
+From src/client/components/CategoryHeader.tsx (after Plan 02):
+```typescript
+interface CategoryHeaderProps {
+ categoryId: number;
+ name: string;
+ icon: string; // was: emoji
+ totalWeight: number;
+ totalCost: number;
+ itemCount: number;
+}
+```
+
+
+
+
+
+
+ Task 1: Update display components to use categoryIcon with LucideIcon
+
+ src/client/components/ItemCard.tsx,
+ src/client/components/CandidateCard.tsx,
+ src/client/components/ThreadCard.tsx,
+ src/client/components/ItemPicker.tsx
+
+
+ Import `LucideIcon` from `../lib/iconData` in each file.
+
+ **ItemCard.tsx:**
+ 1. Props: rename `categoryEmoji: string` to `categoryIcon: string`.
+ 2. Image placeholder area (the 4:3 aspect ratio area when no image): Replace `{categoryEmoji}` with ``. Use size 36 (matching the ~32-40px from CONTEXT.md for card placeholder areas).
+ 3. Category badge/pill below the image: Replace `{categoryEmoji} {categoryName}` with ` {categoryName}`. Use size 14 for inline badge context.
+
+ **CandidateCard.tsx:**
+ Same changes as ItemCard โ rename prop `categoryEmoji` to `categoryIcon`, replace emoji text with LucideIcon in placeholder (size 36) and badge (size 14).
+
+ **ThreadCard.tsx:**
+ 1. Props: rename `categoryEmoji: string` to `categoryIcon: string`.
+ 2. Category display: Replace `{categoryEmoji} {categoryName}` with ` {categoryName}`.
+
+ **ItemPicker.tsx:**
+ 1. In the grouped items type: rename `categoryEmoji: string` to `categoryIcon: string`.
+ 2. Where items are grouped: change `categoryEmoji: item.categoryEmoji` to `categoryIcon: item.categoryIcon`.
+ 3. In the destructuring: change `categoryEmoji` to `categoryIcon`.
+ 4. Import `LucideIcon` and replace `{categoryEmoji} {categoryName}` with ` {categoryName}`.
+
+
+ bun run build 2>&1 | tail -10
+
+
+ - All four components accept `categoryIcon` prop (not `categoryEmoji`)
+ - Icons render as LucideIcon components at appropriate sizes
+ - No emoji text rendering remains in these components
+ - Build succeeds
+
+
+
+
+ Task 2: Update route files and delete old emoji files
+
+ src/client/routes/collection/index.tsx,
+ src/client/routes/setups/$setupId.tsx,
+ src/client/routes/threads/$threadId.tsx,
+ src/client/components/EmojiPicker.tsx,
+ src/client/lib/emojiData.ts
+
+
+ Import `LucideIcon` from the appropriate relative path in each route file.
+
+ **src/client/routes/collection/index.tsx:**
+ 1. In the grouped items type: rename `categoryEmoji` to `categoryIcon` everywhere.
+ 2. Where items are grouped into categories: change `categoryEmoji: item.categoryEmoji` to `categoryIcon: item.categoryIcon`.
+ 3. Where CategoryHeader is rendered: change `emoji={categoryEmoji}` to `icon={categoryIcon}`.
+ 4. Where ItemCard is rendered: change `categoryEmoji={categoryEmoji}` to `categoryIcon={categoryIcon}`.
+ 5. Where ThreadCard is rendered (in planning tab): change `categoryEmoji={thread.categoryEmoji}` to `categoryIcon={thread.categoryIcon}`.
+ 6. In the category filter dropdown: replace `{cat.emoji} {cat.name}` with a LucideIcon + name. Use `` before `{cat.name}`.
+
+ **src/client/routes/setups/$setupId.tsx:**
+ 1. Same pattern โ rename `categoryEmoji` to `categoryIcon` in the grouped type, grouping logic, and where CategoryHeader and ItemCard are rendered.
+ 2. CategoryHeader: `emoji=` -> `icon=`.
+ 3. ItemCard: `categoryEmoji=` -> `categoryIcon=`.
+
+ **src/client/routes/threads/$threadId.tsx:**
+ 1. Where CandidateCard is rendered: change `categoryEmoji={candidate.categoryEmoji}` to `categoryIcon={candidate.categoryIcon}`.
+
+ **Delete old files:**
+ - Delete `src/client/components/EmojiPicker.tsx`
+ - Delete `src/client/lib/emojiData.ts`
+
+ **Final verification sweep:** After all changes, grep the entire `src/` directory for any remaining references to:
+ - `emoji` (should find ZERO in component/route files โ may still exist in migration files which is fine)
+ - `EmojiPicker` (should find ZERO)
+ - `emojiData` (should find ZERO)
+ - `categoryEmoji` (should find ZERO)
+
+ Fix any stragglers found.
+
+
+ bun run build 2>&1 | tail -5; echo "---"; grep -r "categoryEmoji\|EmojiPicker\|emojiData\|emojiCategories" src/ --include="*.ts" --include="*.tsx" | grep -v node_modules | head -10 || echo "No emoji references found"
+
+
+ - Collection route passes `icon` to CategoryHeader and `categoryIcon` to ItemCard/ThreadCard
+ - Setup detail route passes `icon` and `categoryIcon` correctly
+ - Thread detail route passes `categoryIcon` to CandidateCard
+ - Category filter dropdown shows Lucide icons
+ - EmojiPicker.tsx and emojiData.ts are deleted
+ - Zero references to emoji/EmojiPicker/emojiData remain in src/
+ - Build succeeds
+
+
+
+
+
+
+- `bun run build` succeeds with zero errors
+- `grep -r "categoryEmoji\|EmojiPicker\|emojiData" src/ --include="*.ts" --include="*.tsx"` returns nothing
+- `bun test` passes (no test references broken)
+
+
+
+- Every category icon in the app renders as a Lucide icon (cards, headers, badges, lists, pickers)
+- Old EmojiPicker and emojiData files are deleted
+- Zero emoji references remain in source code
+- Build and all tests pass
+
+
+