Files
GearBox/.planning/milestones/v1.1-phases/06-category-icons/06-02-PLAN.md
Jean-Luc Makiola 407fa45280 chore: complete v1.1 milestone — Fixes & Polish
Archive v1.1 artifacts (roadmap, requirements, phases) to milestones/.
Evolve PROJECT.md with shipped requirements and new key decisions.
Reorganize ROADMAP.md with collapsed milestone groupings.
Update retrospective with v1.1 lessons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:16:27 +01:00

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
06-category-icons 02 execute 2
06-01
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
true
CAT-01
truths artifacts key_links
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)
path provides min_lines
src/client/components/IconPicker.tsx Lucide icon picker popover component 150
path provides contains
src/client/components/CategoryPicker.tsx Updated category combobox with icon display LucideIcon
path provides contains
src/client/components/CategoryHeader.tsx Updated category header with icon display and IconPicker for editing IconPicker
from to via pattern
src/client/components/IconPicker.tsx src/client/lib/iconData.ts import iconGroups.*iconData
from to via pattern
src/client/components/CategoryPicker.tsx src/client/components/IconPicker.tsx import IconPicker
from to via pattern
src/client/components/CategoryHeader.tsx src/client/components/IconPicker.tsx import 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.

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

@.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):

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):

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):

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 `<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}`.
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

<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>
After completion, create `.planning/phases/06-category-icons/06-02-SUMMARY.md`