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>
13 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 | 01 | execute | 1 |
|
true |
|
|
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.
<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.mdFrom src/db/schema.ts (CURRENT - will be modified):
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):
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):
// 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:
export function createCategory(db, data: { name: string; emoji?: string }) { ... }
export function updateCategory(db, id, data: { name?: string; emoji?: string }) { ... }
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 <FallbackIcon size={size} className={className} />;
}
return <IconComponent size={size} className={className} />;
}
```
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
<success_criteria>
- Categories table uses
icontext column with "package" default - All Zod schemas, services, types reference
iconnotemoji - lucide-react installed
- Icon data file with curated groups and LucideIcon render component exists
- All tests pass, build succeeds </success_criteria>