--- phase: 04-database-planning-fixes plan: 02 type: execute wave: 2 depends_on: [04-01] files_modified: - src/client/stores/uiStore.ts - src/client/components/CreateThreadModal.tsx - src/client/components/ThreadCard.tsx - src/client/routes/collection/index.tsx autonomous: false requirements: [PLAN-01, PLAN-02] must_haves: truths: - "User can create a thread via a modal dialog with name and category fields" - "User sees an inviting empty state explaining the 3-step planning workflow when no threads exist" - "User can switch between Active and Resolved threads using pill tabs" - "Thread cards display category icon and name" artifacts: - path: "src/client/components/CreateThreadModal.tsx" provides: "Modal dialog for thread creation with name + category picker" min_lines: 60 - path: "src/client/routes/collection/index.tsx" provides: "PlanningView with empty state, pill tabs, category filter, modal trigger" contains: "CreateThreadModal" - path: "src/client/components/ThreadCard.tsx" provides: "Thread card with category display" contains: "categoryEmoji" key_links: - from: "src/client/components/CreateThreadModal.tsx" to: "src/client/hooks/useThreads.ts" via: "useCreateThread mutation with { name, categoryId }" pattern: "useCreateThread" - from: "src/client/routes/collection/index.tsx" to: "src/client/components/CreateThreadModal.tsx" via: "createThreadModalOpen state from uiStore" pattern: "CreateThreadModal" - from: "src/client/components/ThreadCard.tsx" to: "ThreadListItem" via: "categoryName and categoryEmoji props" pattern: "categoryEmoji|categoryName" --- Build the frontend for thread creation modal, polished empty state, Active/Resolved pill tabs, category filter, and category display on thread cards. Purpose: PLAN-01 (user can create threads without errors via modal) and PLAN-02 (polished empty state with CTA). This completes the planning tab UX overhaul. Output: Working planning tab with modal-based thread creation, educational empty state, pill tab filtering, and category-aware thread cards. @/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md @/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/phases/04-database-planning-fixes/04-CONTEXT.md @.planning/phases/04-database-planning-fixes/04-01-SUMMARY.md From src/client/hooks/useThreads.ts (after Plan 01): ```typescript interface ThreadListItem { id: number; name: string; status: "active" | "resolved"; resolvedCandidateId: number | null; createdAt: string; updatedAt: string; candidateCount: number; minPriceCents: number | null; maxPriceCents: number | null; categoryId: number; categoryName: string; categoryEmoji: string; } // useCreateThread expects { name: string; categoryId: number } ``` From src/client/hooks/useCategories.ts: ```typescript export function useCategories(): UseQueryResult; // Category = { id: number; name: string; emoji: string; createdAt: Date } ``` From src/client/stores/uiStore.ts (needs createThreadModal state added): ```typescript // Existing pattern for dialogs: // resolveThreadId: number | null; // openResolveDialog: (threadId, candidateId) => void; // closeResolveDialog: () => void; ``` From src/client/routes/collection/index.tsx (CollectionView empty state pattern): ```typescript // Lines 58-93: empty state with emoji, heading, description, CTA button // Follow this pattern for planning empty state ``` Task 1: Create thread modal and update uiStore src/client/stores/uiStore.ts, src/client/components/CreateThreadModal.tsx 1. In `src/client/stores/uiStore.ts`, add create-thread modal state following the existing dialog pattern: ``` createThreadModalOpen: boolean; openCreateThreadModal: () => void; closeCreateThreadModal: () => void; ``` Initialize `createThreadModalOpen: false` and wire up the actions. 2. Create `src/client/components/CreateThreadModal.tsx`: - A modal overlay (fixed inset-0, bg-black/50 backdrop, centered white panel) following the same pattern as the app's existing dialog styling. - Form fields: Thread name (text input, required, min 1 char) and Category (select dropdown populated from `useCategories()` hook). - Category select shows emoji + name for each option. Pre-select the first category. - Submit calls `useCreateThread().mutate({ name, categoryId })`. - On success: close modal (via `closeCreateThreadModal` from uiStore), reset form. - On error: show inline error message. - Cancel button and clicking backdrop closes modal. - Disable submit button while `isPending`. - Use Tailwind classes consistent with existing app styling (rounded-xl, text-sm, blue-600 primary buttons, gray-200 borders). cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 CreateThreadModal component renders a modal with name input and category dropdown, submits via useCreateThread, uiStore has createThreadModalOpen state Task 2: Overhaul PlanningView with empty state, pill tabs, category filter, and thread card category display src/client/routes/collection/index.tsx, src/client/components/ThreadCard.tsx 1. In `src/client/components/ThreadCard.tsx`: - Add `categoryName: string` and `categoryEmoji: string` props to `ThreadCardProps`. - Display category as a pill badge (emoji + name) in the card's badge row, using a style like `bg-blue-50 text-blue-700` to distinguish from existing badges. 2. In `src/client/routes/collection/index.tsx`, rewrite the `PlanningView` function: **Remove:** The inline text input + button form for thread creation. Remove the `showResolved` checkbox. **Add state:** - `activeTab: "active" | "resolved"` (default "active") for the pill tab selector. - `categoryFilter: number | null` (default null = all categories) for filtering. - Import `useCategories` hook, `useUIStore`, and `CreateThreadModal`. **Layout (top to bottom):** a. **Header row:** "Planning Threads" heading on the left, "New Thread" button on the right. Button calls `openCreateThreadModal()` from uiStore. Use a plus icon (inline SVG, same pattern as collection empty state button). b. **Filter row:** Active/Resolved pill tab selector on the left, category filter dropdown on the right. - Pill tabs: Two buttons styled as a segment control. Active pill gets `bg-blue-600 text-white`, inactive gets `bg-gray-100 text-gray-600 hover:bg-gray-200`. Rounded-full, px-4 py-1.5, text-sm font-medium. Wrap in a `flex bg-gray-100 rounded-full p-0.5 gap-0.5` container. - Category filter: A `