---
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"
---

<objective>
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.
</objective>

<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.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

<interfaces>
<!-- From Plan 01 outputs -->

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<string, string>;

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
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create IconPicker component</name>
  <files>src/client/components/IconPicker.tsx</files>
  <action>
    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).
  </action>
  <verify>
    <automated>bun run build 2>&1 | tail -5</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

<task type="auto">
  <name>Task 2: Update CategoryPicker, CategoryHeader, OnboardingWizard, and CreateThreadModal</name>
  <files>
    src/client/components/CategoryPicker.tsx,
    src/client/components/CategoryHeader.tsx,
    src/client/components/OnboardingWizard.tsx,
    src/client/components/CreateThreadModal.tsx
  </files>
  <action>
    **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 `<LucideIcon name={cat.icon} size={16} className="inline-block mr-1.5 text-gray-500" /> {cat.name}`.
    8. In the inline create flow: Replace `<EmojiPicker value={newCategoryEmoji} onChange={setNewCategoryEmoji} size="sm" />` with `<IconPicker value={newCategoryIcon} onChange={setNewCategoryIcon} size="sm" />`.

    **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 `<EmojiPicker value={editEmoji} onChange={setEditEmoji} size="sm" />` with `<IconPicker value={editIcon} onChange={setEditIcon} size="sm" />`.
    6. Display mode: Replace `<span className="text-xl">{emoji}</span>` with `<LucideIcon name={icon} size={22} className="text-gray-500" />`.
    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 `<EmojiPicker value={categoryEmoji} onChange={setCategoryEmoji} size="md" />` with `<IconPicker value={categoryIcon} onChange={setCategoryIcon} size="md" />`.

    **CreateThreadModal.tsx:**
    1. Import `LucideIcon` from `../lib/iconData`.
    2. In the category list: Replace `{cat.emoji} {cat.name}` with `<LucideIcon name={cat.icon} size={16} className="inline-block mr-1.5 text-gray-500" /> {cat.name}`.
  </action>
  <verify>
    <automated>bun run build 2>&1 | tail -5</automated>
  </verify>
  <done>
    - 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
  </done>
</task>

</tasks>

<verification>
- `bun run build` succeeds
- No TypeScript errors related to emoji/icon types
- No remaining imports of EmojiPicker in modified files
</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/06-category-icons/06-02-SUMMARY.md`
</output>
