diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 62d4f9a..6e1a287 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -217,7 +217,11 @@ Plans: 3. Thread candidates navigate to detail pages instead of opening slide-out panels 4. Item slide-out panel and candidate slide-out panel are removed from the root layout 5. No visual distinction between reference items and standalone items — same layout, some fields may be empty -**Plans**: TBD +**Plans:** 3 plans +Plans: +- [ ] 21-01-PLAN.md — Item detail page with edit mode + catalog detail page enhancement +- [ ] 21-02-PLAN.md — Candidate detail page with edit mode + add-candidate modal +- [ ] 21-03-PLAN.md — Rewire card navigation + remove slide-out panels + UIStore cleanup **UI hint**: yes ### Phase 22: Add-from-Catalog & Thread Integration @@ -266,6 +270,6 @@ Plans: | 18. Global Items & Public Profiles | v2.0 | 4/5 | Complete | 2026-04-05 | | 19. Reference Item Model & Tags Schema | v2.0 | 3/3 | Complete | 2026-04-05 | | 20. FAB & Full-Screen Catalog Search | v2.0 | 2/2 | Complete | 2026-04-06 | -| 21. Item & Catalog Detail Pages | v2.0 | 0/? | Not started | - | +| 21. Item & Catalog Detail Pages | v2.0 | 0/3 | Planning | - | | 22. Add-from-Catalog & Thread Integration | v2.0 | 0/? | Not started | - | | 23. Manual Entry Fallback | v2.0 | 0/? | Not started | - | diff --git a/.planning/phases/21-item-catalog-detail-pages/21-01-PLAN.md b/.planning/phases/21-item-catalog-detail-pages/21-01-PLAN.md new file mode 100644 index 0000000..4e08c31 --- /dev/null +++ b/.planning/phases/21-item-catalog-detail-pages/21-01-PLAN.md @@ -0,0 +1,241 @@ +--- +phase: 21-item-catalog-detail-pages +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/client/routes/items/$itemId.tsx + - src/client/routes/global-items/$globalItemId.tsx +autonomous: true +requirements: [DETAIL-01, DETAIL-02, DETAIL-03] +must_haves: + truths: + - "Navigating to /items/:id renders a full detail page with all item data" + - "Item detail page shows hero image, name, weight/price/category badges, notes, quantity, purchase price, product link" + - "Clicking Edit toggles fields to editable inputs; Save persists via updateItem mutation" + - "Navigating to /global-items/:id shows enhanced catalog page with Add to Collection button" + - "Catalog detail page shows hero image, brand, model, specs, description, owner count" + artifacts: + - path: "src/client/routes/items/$itemId.tsx" + provides: "Private item detail page with edit mode" + exports: ["Route"] + - path: "src/client/routes/global-items/$globalItemId.tsx" + provides: "Enhanced catalog detail page with Add to Collection button" + exports: ["Route"] + key_links: + - from: "src/client/routes/items/$itemId.tsx" + to: "useItem hook" + via: "useItem(Number(itemId))" + pattern: "useItem\\(Number" + - from: "src/client/routes/items/$itemId.tsx" + to: "useUpdateItem mutation" + via: "updateItem.mutate" + pattern: "useUpdateItem" + - from: "src/client/routes/global-items/$globalItemId.tsx" + to: "Add to Collection button" + via: "button element" + pattern: "Add to Collection" +--- + + +Create the private item detail page at `/items/:id` with edit mode toggle, and enhance the existing catalog detail page at `/global-items/:id` with an "Add to Collection" button and improved layout. + +Purpose: These are the navigation targets that card click handlers will point to in Plan 03. They must exist before rewiring. +Output: Two route files — one new (`items/$itemId.tsx`), one enhanced (`global-items/$globalItemId.tsx`) + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/21-item-catalog-detail-pages/21-CONTEXT.md +@.planning/phases/21-item-catalog-detail-pages/21-RESEARCH.md + +@src/client/routes/global-items/$globalItemId.tsx +@src/client/hooks/useItems.ts +@src/client/hooks/useGlobalItems.ts +@src/client/hooks/useFormatters.ts +@src/client/components/ItemForm.tsx +@src/client/components/CategoryPicker.tsx +@src/client/components/ImageUpload.tsx + + + + +From src/client/hooks/useItems.ts: +```typescript +export function useItem(id: number | null): UseQueryResult; +export function useUpdateItem(): UseMutationResult>; +export function useDeleteItem(): UseMutationResult<{ success: boolean }, Error, number>; +export function useDuplicateItem(): UseMutationResult; + +interface ItemWithCategory { + id: number; name: string; weightGrams: number | null; priceCents: number | null; + quantity: number; categoryId: number; notes: string | null; productUrl: string | null; + imageFilename: string | null; createdAt: string; updatedAt: string; + categoryName: string; categoryIcon: string; +} +``` + +From src/client/hooks/useGlobalItems.ts: +```typescript +export function useGlobalItem(id: number): UseQueryResult; +// GlobalItemWithDetails includes: id, brand, model, description, category, weightGrams, priceCents, imageUrl, ownerCount +``` + +From src/client/hooks/useFormatters.ts: +```typescript +export function useFormatters(): { weight: (g: number) => string; price: (cents: number) => string; }; +``` + +From src/client/components/ItemForm.tsx: +```typescript +interface FormData { + name: string; weightGrams: string; priceDollars: string; quantity: number; + categoryId: number; notes: string; productUrl: string; imageFilename: string | null; +} +``` + + + + + + + Task 1: Create private item detail page with edit mode toggle + src/client/routes/items/$itemId.tsx + + - src/client/routes/global-items/$globalItemId.tsx (existing detail page pattern to follow) + - src/client/components/ItemForm.tsx (form fields, validation logic, FormData interface to reuse) + - src/client/hooks/useItems.ts (useItem, useUpdateItem, useDeleteItem, useDuplicateItem hooks) + - src/client/hooks/useFormatters.ts (weight/price formatting) + - src/client/components/CategoryPicker.tsx (category selection for edit mode) + - src/client/components/ImageUpload.tsx (image upload for edit mode) + - src/client/stores/uiStore.ts (openConfirmDelete for delete action, openExternalLink for product links) + + + Create `src/client/routes/items/$itemId.tsx` with `createFileRoute("/items/$itemId")`. + + Per D-01: Route at `/items/:id`, full page showing all item data. + Per D-02: Hero section — large product image (or placeholder icon using LucideIcon with categoryIcon), item name as h1 title, key specs as badges (weight via `useFormatters().weight`, price via `useFormatters().price`, category name). + Per D-03: Personal section below hero — notes (rendered as paragraph), quantity (if > 1), purchase price (priceCents formatted), product link (as external link button), image. For reference items, COALESCE merge is transparent — useItem already returns merged data, no special handling needed. + Per D-05: Page is read-only by default. Clean aesthetic, spacious gallery-like layout. + Per D-06: Back navigation at top — `← Back to collection`. + Per D-07: Actions section — "Edit" button (top-right), "Duplicate" button (uses `useDuplicateItem`), "Delete" button (uses `useUIStore.openConfirmDelete` which triggers the existing ConfirmDialog). + + Per D-04: Edit mode toggle. Local `useState(false)` for `isEditing`. When "Edit" clicked, `setIsEditing(true)`. In edit mode: + - Name becomes text input + - Weight becomes number input (display in grams, store as string like ItemForm does) + - Price becomes dollar input (convert cents to dollars for display, back to cents on save) + - Quantity becomes number input + - Category becomes CategoryPicker component + - Notes becomes textarea + - Product URL becomes text input + - Image becomes ImageUpload component + - Show Save and Cancel buttons. Save calls `useUpdateItem().mutate({ id, name, weightGrams: parsed, priceCents: parsed, quantity, categoryId, notes, productUrl, imageFilename })` with `onSuccess: () => setIsEditing(false)`. Cancel calls `setIsEditing(false)`. + - Initialize form state from item data when entering edit mode (in the toggle handler or via useEffect when isEditing becomes true AND item data is available). + + Layout: `max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6` (matches existing global item detail page). + Loading state: shimmer placeholders (same pattern as `$globalItemId.tsx`). + Error state: "Item not found" with back link. + Image placeholder when no image: centered LucideIcon with categoryIcon, bg-gray-50 container. + + Do NOT import from UIStore for panel state (openEditPanel, closePanel, etc.) — this page replaces the panel. + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - grep -q "createFileRoute.*items.*itemId" src/client/routes/items/\$itemId.tsx + - grep -q "useItem" src/client/routes/items/\$itemId.tsx + - grep -q "useUpdateItem" src/client/routes/items/\$itemId.tsx + - grep -q "isEditing" src/client/routes/items/\$itemId.tsx + - grep -q "Back to collection" src/client/routes/items/\$itemId.tsx + - grep -q "useDuplicateItem\|useDeleteItem" src/client/routes/items/\$itemId.tsx + - grep -q "CategoryPicker" src/client/routes/items/\$itemId.tsx + - grep -q "ImageUpload" src/client/routes/items/\$itemId.tsx + + + - `/items/:id` route renders item detail with hero image, name, badges, personal fields + - Edit button toggles between read-only and editable states + - Save persists changes via useUpdateItem and exits edit mode + - Cancel exits edit mode without saving + - Duplicate and Delete actions work via existing hooks/dialogs + - Back link navigates to /collection + - Loading and error states handled + + + + + Task 2: Enhance catalog detail page with Add to Collection button and improved layout + src/client/routes/global-items/$globalItemId.tsx + + - src/client/routes/global-items/$globalItemId.tsx (current implementation to enhance) + - src/client/hooks/useGlobalItems.ts (useGlobalItem hook) + - src/client/routes/items/$itemId.tsx (just created — follow same layout proportions) + + + Enhance the existing `src/client/routes/global-items/$globalItemId.tsx` file. + + Per D-08: Enhance existing route — add "Add to Collection" button and improve layout. + Per D-09: Layout improvements: + - Hero image section: if `item.imageUrl` exists, show in `aspect-[16/9] bg-gray-50 rounded-xl overflow-hidden` (already exists). If no image, show a placeholder div with a package icon. + - Brand as small uppercase label above model title (already exists). + - Model as h1 title (already exists). + - Manufacturer specs as badges: weight, price, category (already exists). + - Description section (already exists). + - Owner count badge (already exists). + + Per D-10: "Add to Collection" button — prominent placement after the header/badges section. Styled as `bg-gray-700 text-white rounded-lg px-5 py-2.5 text-sm font-medium hover:bg-gray-800 transition-colors`. This is a STUB — onClick shows a console.log("Add to collection — wired in Phase 22") or a brief toast/alert. The actual flow is Phase 22. Match the same stub pattern as the Add button in CatalogSearchOverlay. + + Per D-11: No edit functionality on this page. Read-only display only. + Per D-12: This page is accessible from catalog search overlay — no changes needed here (overlay card click wiring happens in Plan 03). + + Specific enhancements to the existing page: + 1. Add image placeholder when no imageUrl (currently the image section is skipped entirely — add a placeholder div) + 2. Add "Add to Collection" button between badges and description + 3. Ensure layout is consistent with the new item detail page from Task 1 (same max-width, padding, spacing) + 4. Add tags display if the item has tags (optional, Claude's discretion — subtle tag chips below badges) + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - grep -q "Add to Collection" src/client/routes/global-items/\$globalItemId.tsx + - grep -q "button" src/client/routes/global-items/\$globalItemId.tsx + - grep -q "createFileRoute.*global-items.*globalItemId" src/client/routes/global-items/\$globalItemId.tsx + + + - Catalog detail page shows "Add to Collection" button (stub, not wired to actual add flow) + - Image placeholder shown when no image exists + - Layout is clean and consistent with item detail page styling + - All existing functionality preserved (back link, specs, owner count) + + + + + + +- `bun run lint` passes with no errors +- `bun run dev` starts and both routes render correctly: + - Navigate to `/items/1` — shows item detail with edit toggle + - Navigate to `/global-items/1` — shows catalog detail with Add to Collection button +- Edit mode on item detail: clicking Edit shows form fields, Save persists, Cancel reverts + + + +- Item detail page at `/items/:id` renders all item data in gallery-like layout +- Edit mode toggle works: read-only by default, editable when toggled +- Catalog detail page shows "Add to Collection" button (stub) +- Both pages follow consistent layout patterns (max-w-3xl, same spacing) +- Lint passes cleanly + + + +After completion, create `.planning/phases/21-item-catalog-detail-pages/21-01-SUMMARY.md` + diff --git a/.planning/phases/21-item-catalog-detail-pages/21-02-PLAN.md b/.planning/phases/21-item-catalog-detail-pages/21-02-PLAN.md new file mode 100644 index 0000000..8a353f4 --- /dev/null +++ b/.planning/phases/21-item-catalog-detail-pages/21-02-PLAN.md @@ -0,0 +1,239 @@ +--- +phase: 21-item-catalog-detail-pages +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/client/routes/threads/$threadId/index.tsx + - src/client/routes/threads/$threadId/candidates/$candidateId.tsx +autonomous: true +requirements: [DETAIL-04] +must_haves: + truths: + - "Navigating to /threads/:threadId/candidates/:candidateId renders a full candidate detail page" + - "Candidate detail page shows name, weight, price, notes, pros, cons, status, product link, image" + - "Edit mode toggle works: read-only by default, editable inputs when toggled" + - "Back navigation returns to parent thread" + - "Pick as winner and delete candidate actions are available" + - "Thread detail page at /threads/:threadId still works after route restructuring" + - "Add candidate button on thread page uses modal dialog instead of slide-out panel" + artifacts: + - path: "src/client/routes/threads/$threadId/index.tsx" + provides: "Restructured thread detail page (moved from $threadId.tsx)" + - path: "src/client/routes/threads/$threadId/candidates/$candidateId.tsx" + provides: "Candidate detail page with edit mode" + exports: ["Route"] + key_links: + - from: "src/client/routes/threads/$threadId/candidates/$candidateId.tsx" + to: "useThread hook" + via: "useThread(threadId) to get candidate data" + pattern: "useThread" + - from: "src/client/routes/threads/$threadId/candidates/$candidateId.tsx" + to: "useUpdateCandidate mutation" + via: "updateCandidate.mutate" + pattern: "useUpdateCandidate" +--- + + +Create the candidate detail page at `/threads/:threadId/candidates/:candidateId` with edit mode toggle, and restructure the thread route to support nested candidate routes. Replace the "Add Candidate" slide-out trigger on the thread page with a modal dialog. + +Purpose: Creates the navigation target for candidate card clicks (rewired in Plan 03). The thread route restructuring is a prerequisite for the nested candidate route. +Output: Restructured thread route directory + new candidate detail page + add-candidate modal on thread page + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/21-item-catalog-detail-pages/21-CONTEXT.md +@.planning/phases/21-item-catalog-detail-pages/21-RESEARCH.md + +@src/client/routes/threads/$threadId.tsx +@src/client/hooks/useCandidates.ts +@src/client/hooks/useThreads.ts +@src/client/components/CandidateForm.tsx +@src/client/components/CandidateCard.tsx +@src/client/components/CandidateListItem.tsx +@src/client/stores/uiStore.ts + + + + +From src/client/hooks/useCandidates.ts: +```typescript +export function useCreateCandidate(threadId: number): UseMutationResult; +export function useUpdateCandidate(threadId: number): UseMutationResult; +export function useDeleteCandidate(threadId: number): UseMutationResult<{ success: boolean }, Error, number>; +``` + +From src/client/hooks/useThreads.ts: +```typescript +export function useThread(id: number): UseQueryResult; +export function useResolveThread(): UseMutationResult; +// ThreadWithCandidates.candidates[]: { id, threadId, name, weightGrams, priceCents, categoryId, categoryName, categoryIcon, notes, productUrl, imageFilename, imageUrl, status, pros, cons, sortOrder, createdAt, updatedAt } +``` + +From src/client/components/CandidateForm.tsx: +```typescript +interface CandidateFormProps { mode: "add" | "edit"; threadId: number; candidateId?: number | null; } +interface FormData { + name: string; weightGrams: string; priceDollars: string; categoryId: number; + notes: string; productUrl: string; imageFilename: string | null; pros: string; cons: string; +} +``` + +From src/client/stores/uiStore.ts (properties used by thread page): +```typescript +openCandidateAddPanel: () => void; // WILL BE REMOVED in Plan 03 +openResolveDialog: (threadId: number, candidateId: number) => void; // KEEP +openConfirmDeleteCandidate: (id: number) => void; // KEEP +``` + + + + + + + Task 1: Restructure thread route and create candidate detail page + + src/client/routes/threads/$threadId.tsx + src/client/routes/threads/$threadId/index.tsx + src/client/routes/threads/$threadId/candidates/$candidateId.tsx + + + - src/client/routes/threads/$threadId.tsx (full file — must move to index.tsx intact) + - src/client/hooks/useCandidates.ts (useUpdateCandidate, useDeleteCandidate hooks) + - src/client/hooks/useThreads.ts (useThread — returns ThreadWithCandidates with candidates array) + - src/client/components/CandidateForm.tsx (form fields and FormData interface to reuse for edit mode) + - src/client/components/CategoryPicker.tsx (for edit mode category picker) + - src/client/components/ImageUpload.tsx (for edit mode image upload) + - src/client/routes/global-items/$globalItemId.tsx (layout pattern reference) + - src/client/stores/uiStore.ts (openResolveDialog, openConfirmDeleteCandidate — keep using these) + + + **Step 1: Restructure thread route directory.** + - Move `src/client/routes/threads/$threadId.tsx` to `src/client/routes/threads/$threadId/index.tsx` + - Delete the original `$threadId.tsx` file + - The route path `/threads/$threadId` continues to work via the index.tsx convention + - IMPORTANT: Do NOT manually edit `routeTree.gen.ts` — it auto-regenerates + + **Step 2: Create candidate detail page.** + Create `src/client/routes/threads/$threadId/candidates/$candidateId.tsx` with `createFileRoute("/threads/$threadId/candidates/$candidateId")`. + + Per D-13: Shows candidate data — name, weight, price, notes, pros, cons, status, product link, image. + Per D-14: Edit mode toggle — same pattern as item detail page (local `useState(false)`). In edit mode: + - Name, weight (grams string), price (dollars string), category (CategoryPicker), notes (textarea), product URL (text input), image (ImageUpload), pros (textarea), cons (textarea) + - Save button calls `useUpdateCandidate(threadId).mutate({ candidateId, name, weightGrams: parsed, priceCents: parsed, categoryId, notes, productUrl, imageFilename, pros, cons })` with `onSuccess: () => setIsEditing(false)` + - Cancel resets and exits edit mode + - Initialize form from candidate data when entering edit mode + + Per D-15: Back navigation — `← Back to thread`. + Per D-16: Thread-specific actions: + - "Pick as winner" button (only if thread is active/not resolved) — calls `useUIStore.openResolveDialog(threadId, candidateId)` which triggers the existing ResolveDialog in __root.tsx + - "Delete candidate" button — calls `useUIStore.openConfirmDeleteCandidate(candidateId)` which triggers the existing CandidateDeleteDialog in __root.tsx + + Data fetching: Use `useThread(Number(threadIdParam))` to get the thread, then find the candidate via `thread.candidates.find(c => c.id === Number(candidateIdParam))`. This avoids needing a new API endpoint. + + Layout: Same gallery-like pattern as item detail — `max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-6`. Hero image section, name as h1, badges (weight, price, category, status), pros/cons sections, notes section. + + Loading state: shimmer placeholders. + Error state: "Candidate not found" with back link to thread. + + Status display: Show current status using the StatusBadge component (but read-only — status changes happen via the existing status cycle on the card, not on the detail page). + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - test -f src/client/routes/threads/\$threadId/index.tsx + - test ! -f src/client/routes/threads/\$threadId.tsx + - grep -q "createFileRoute.*threads.*threadId.*candidates.*candidateId" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + - grep -q "useThread" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + - grep -q "useUpdateCandidate" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + - grep -q "isEditing" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + - grep -q "Back to thread" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + - grep -q "openResolveDialog\|Pick as winner" src/client/routes/threads/\$threadId/candidates/\$candidateId.tsx + + + - Thread route restructured: `$threadId.tsx` moved to `$threadId/index.tsx`, `/threads/:threadId` still works + - Candidate detail page renders at `/threads/:threadId/candidates/:candidateId` + - Shows all candidate data: name, weight, price, notes, pros, cons, status, image + - Edit mode toggle works with form fields for all editable properties + - Back link returns to parent thread + - Pick as winner and delete actions available via existing dialogs + + + + + Task 2: Add candidate modal dialog on thread page + + src/client/routes/threads/$threadId/index.tsx + + + - src/client/routes/threads/$threadId/index.tsx (just restructured — find the "Add Candidate" button) + - src/client/components/CandidateForm.tsx (form fields to replicate in modal) + - src/client/hooks/useCandidates.ts (useCreateCandidate hook) + - src/client/components/CategoryPicker.tsx (for category field) + - src/client/components/ImageUpload.tsx (for image field) + + + Per D-28: Replace the `openCandidateAddPanel()` call on the thread page with a local modal dialog for adding candidates. + + In `src/client/routes/threads/$threadId/index.tsx`: + 1. Add local state: `const [addCandidateOpen, setAddCandidateOpen] = useState(false)` + 2. Replace the existing "Add Candidate" button's `onClick={() => openCandidateAddPanel()}` with `onClick={() => setAddCandidateOpen(true)}` + 3. Remove the `openCandidateAddPanel` import from useUIStore + 4. Add an `AddCandidateModal` component (can be defined in the same file or as a separate component — Claude's discretion) that: + - Renders as a fixed overlay with backdrop (`fixed inset-0 z-50 flex items-center justify-center`) + - Contains a form with fields: name (required), weight (grams), price (dollars), category (CategoryPicker), notes, product URL, image (ImageUpload), pros, cons + - Uses `useCreateCandidate(threadId)` for submission + - On success: closes modal, form resets + - On cancel/backdrop click: closes modal + - Style consistent with the existing CandidateDeleteDialog and ResolveDialog patterns in `__root.tsx` + 5. Render the modal conditionally: `{addCandidateOpen && setAddCandidateOpen(false)} />}` + + The modal should be a reasonable size (`max-w-lg`) with scrollable content if needed. Form fields should match CandidateForm's field set. Validation: name is required (show inline error if empty on submit). + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - grep -q "addCandidateOpen\|AddCandidateModal" src/client/routes/threads/\$threadId/index.tsx + - grep -q "useCreateCandidate" src/client/routes/threads/\$threadId/index.tsx + - grep -qv "openCandidateAddPanel" src/client/routes/threads/\$threadId/index.tsx + + + - Thread page "Add Candidate" button opens a modal dialog instead of a slide-out panel + - Modal contains all candidate form fields (name, weight, price, category, notes, URL, image, pros, cons) + - Creating a candidate via the modal works and refreshes the thread data + - The `openCandidateAddPanel` UIStore call is no longer used on the thread page + + + + + + +- `bun run lint` passes with no errors +- `bun run dev` starts and routes work: + - `/threads/:threadId` renders thread detail (unchanged behavior after restructuring) + - `/threads/:threadId/candidates/:candidateId` renders candidate detail with edit toggle + - "Add Candidate" button on thread page opens modal, candidate creation works + + + +- Thread route restructured without breaking existing `/threads/:threadId` functionality +- Candidate detail page at `/threads/:threadId/candidates/:candidateId` with edit mode +- Add candidate modal replaces slide-out panel trigger on thread page +- All existing thread page functionality preserved (reorder, view modes, impact deltas) +- Lint passes cleanly + + + +After completion, create `.planning/phases/21-item-catalog-detail-pages/21-02-SUMMARY.md` + diff --git a/.planning/phases/21-item-catalog-detail-pages/21-03-PLAN.md b/.planning/phases/21-item-catalog-detail-pages/21-03-PLAN.md new file mode 100644 index 0000000..803047b --- /dev/null +++ b/.planning/phases/21-item-catalog-detail-pages/21-03-PLAN.md @@ -0,0 +1,331 @@ +--- +phase: 21-item-catalog-detail-pages +plan: 03 +type: execute +wave: 2 +depends_on: [21-01, 21-02] +files_modified: + - src/client/components/ItemCard.tsx + - src/client/components/CandidateCard.tsx + - src/client/components/CandidateListItem.tsx + - src/client/components/CatalogSearchOverlay.tsx + - src/client/routes/__root.tsx + - src/client/stores/uiStore.ts +autonomous: true +requirements: [DETAIL-04, DETAIL-05] +must_haves: + truths: + - "Clicking an ItemCard navigates to /items/:id instead of opening a slide-out panel" + - "Clicking a CandidateCard navigates to /threads/:threadId/candidates/:candidateId" + - "Clicking a CandidateListItem navigates to the candidate detail page" + - "Clicking a catalog search result card navigates to /global-items/:id" + - "Item slide-out panel is removed from __root.tsx" + - "Candidate slide-out panel is removed from __root.tsx" + - "Panel-related state is removed from UIStore" + - "SlideOutPanel.tsx, ItemForm.tsx, and CandidateForm.tsx files still exist" + artifacts: + - path: "src/client/components/ItemCard.tsx" + provides: "ItemCard with navigation instead of panel open" + - path: "src/client/components/CandidateCard.tsx" + provides: "CandidateCard with navigation instead of panel open" + - path: "src/client/components/CandidateListItem.tsx" + provides: "CandidateListItem with navigation instead of panel open" + - path: "src/client/routes/__root.tsx" + provides: "Root layout without slide-out panels" + - path: "src/client/stores/uiStore.ts" + provides: "UIStore without panel state properties" + key_links: + - from: "src/client/components/ItemCard.tsx" + to: "/items/$itemId route" + via: "useNavigate → /items/$itemId" + pattern: "navigate.*items.*itemId" + - from: "src/client/components/CandidateCard.tsx" + to: "/threads/$threadId/candidates/$candidateId route" + via: "useNavigate → /threads/$threadId/candidates/$candidateId" + pattern: "navigate.*threads.*candidates" + - from: "src/client/components/CatalogSearchOverlay.tsx" + to: "/global-items/$globalItemId route" + via: "useNavigate → /global-items/$globalItemId" + pattern: "navigate.*global-items" +--- + + +Rewire all card click handlers to navigate to detail pages instead of opening slide-out panels, then remove the slide-out panels from the root layout and clean up the UIStore. + +Purpose: Completes the transition from panel-based editing to full detail pages. This is the final step — navigation targets from Plans 01 and 02 must exist first. +Output: All cards navigate to detail pages, panels removed, UIStore cleaned + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/21-item-catalog-detail-pages/21-CONTEXT.md +@.planning/phases/21-item-catalog-detail-pages/21-RESEARCH.md + +@src/client/components/ItemCard.tsx +@src/client/components/CandidateCard.tsx +@src/client/components/CandidateListItem.tsx +@src/client/components/CatalogSearchOverlay.tsx +@src/client/routes/__root.tsx +@src/client/stores/uiStore.ts + + +@.planning/phases/21-item-catalog-detail-pages/21-01-SUMMARY.md +@.planning/phases/21-item-catalog-detail-pages/21-02-SUMMARY.md + + + + +ItemCard currently uses: +```typescript +const openEditPanel = useUIStore((s) => s.openEditPanel); +onClick={() => openEditPanel(id)} +// Also: duplicateItem onSuccess calls openEditPanel(newItem.id) +``` + +CandidateCard currently uses: +```typescript +const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel); +onClick={() => openCandidateEditPanel(id)} +// Props include: threadId (available for navigation) +``` + +CandidateListItem currently uses: +```typescript +const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel); +onClick={() => openCandidateEditPanel(candidate.id)} +// candidate.threadId is available +``` + +__root.tsx panel instances (lines 189-221): +```typescript + + + + + + +``` + +UIStore properties to REMOVE: +```typescript +panelMode, editingItemId, openAddPanel, openEditPanel, closePanel +candidatePanelMode, editingCandidateId, openCandidateAddPanel, openCandidateEditPanel, closeCandidatePanel +``` + +UIStore properties to KEEP: +```typescript +confirmDeleteItemId, openConfirmDelete, closeConfirmDelete +confirmDeleteCandidateId, openConfirmDeleteCandidate, closeConfirmDeleteCandidate +resolveThreadId, resolveCandidateId, openResolveDialog, closeResolveDialog +// all other non-panel state +``` + + + + + + + Task 1: Rewire card click handlers to navigate to detail pages + + src/client/components/ItemCard.tsx + src/client/components/CandidateCard.tsx + src/client/components/CandidateListItem.tsx + src/client/components/CatalogSearchOverlay.tsx + + + - src/client/components/ItemCard.tsx (full file — find openEditPanel usage) + - src/client/components/CandidateCard.tsx (full file — find openCandidateEditPanel usage) + - src/client/components/CandidateListItem.tsx (full file — find openCandidateEditPanel usage) + - src/client/components/CatalogSearchOverlay.tsx (full file — find card rendering, add click navigation) + - src/client/stores/uiStore.ts (verify which store properties each component uses) + + + Per D-17: **ItemCard.tsx** — Replace panel open with navigation. + 1. Remove: `const openEditPanel = useUIStore((s) => s.openEditPanel);` + 2. Add: `import { useNavigate } from "@tanstack/react-router";` and `const navigate = useNavigate();` + 3. Change main button `onClick`: `() => navigate({ to: "/items/$itemId", params: { itemId: String(id) } })` + 4. Change duplicate onSuccess: `(newItem) => navigate({ to: "/items/$itemId", params: { itemId: String(newItem.id) } })` (navigate to the new item's detail page instead of opening edit panel) + 5. Keep all other UIStore usage (openExternalLink, etc.) + 6. If the component no longer imports anything from useUIStore, remove the import entirely. If it still uses openExternalLink, keep the import. + + Per D-18: **CandidateCard.tsx** — Replace panel open with navigation. + 1. Remove: `const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel);` + 2. Add: `import { useNavigate } from "@tanstack/react-router";` and `const navigate = useNavigate();` + 3. Change main button `onClick`: `() => navigate({ to: "/threads/$threadId/candidates/$candidateId", params: { threadId: String(threadId), candidateId: String(id) } })` + 4. Keep: openConfirmDeleteCandidate, openResolveDialog, openExternalLink from UIStore. + + Per D-20: **CandidateListItem.tsx** — Replace panel open with navigation. + 1. Remove: `const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel);` + 2. Add: `import { useNavigate } from "@tanstack/react-router";` and `const navigate = useNavigate();` + 3. Change the click handler: `() => navigate({ to: "/threads/$threadId/candidates/$candidateId", params: { threadId: String(candidate.threadId), candidateId: String(candidate.id) } })` + 4. Keep all other UIStore usage. + + Per D-19: **CatalogSearchOverlay.tsx** — Add card click navigation to `/global-items/:id`. + 1. Add: `import { useNavigate } from "@tanstack/react-router";` and `const navigate = useNavigate();` + 2. Find the card/grid item rendering for search results. Wrap the card body (not the "Add" button) in a clickable element that: + - Calls `closeCatalogSearch()` then `navigate({ to: "/global-items/$globalItemId", params: { globalItemId: String(item.id) } })` + - The existing "Add" button stays separate with `e.stopPropagation()` to prevent navigation when clicking Add + 3. The card body should have `cursor-pointer` and hover styling to indicate clickability + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - grep -q "useNavigate" src/client/components/ItemCard.tsx + - grep -q "items.*itemId" src/client/components/ItemCard.tsx + - grep -qv "openEditPanel" src/client/components/ItemCard.tsx + - grep -q "useNavigate" src/client/components/CandidateCard.tsx + - grep -q "threads.*candidates" src/client/components/CandidateCard.tsx + - grep -qv "openCandidateEditPanel" src/client/components/CandidateCard.tsx + - grep -q "useNavigate" src/client/components/CandidateListItem.tsx + - grep -qv "openCandidateEditPanel" src/client/components/CandidateListItem.tsx + - grep -q "useNavigate" src/client/components/CatalogSearchOverlay.tsx + - grep -q "global-items" src/client/components/CatalogSearchOverlay.tsx + + + - ItemCard click navigates to /items/:id + - CandidateCard click navigates to /threads/:threadId/candidates/:candidateId + - CandidateListItem click navigates to candidate detail page + - Catalog search result card click navigates to /global-items/:id (closing overlay first) + - "Add" button on catalog cards still works independently + - No component references openEditPanel or openCandidateEditPanel + + + + + Task 2: Remove slide-out panels from root layout and clean UIStore + + src/client/routes/__root.tsx + src/client/stores/uiStore.ts + + + - src/client/routes/__root.tsx (full file — identify panel JSX to remove, imports to clean) + - src/client/stores/uiStore.ts (full file — identify panel state to remove) + - src/client/components/ItemCard.tsx (verify openEditPanel no longer imported — done in Task 1) + - src/client/components/CandidateCard.tsx (verify openCandidateEditPanel no longer imported) + - src/client/routes/threads/$threadId/index.tsx (verify openCandidateAddPanel no longer imported — done in Plan 02) + + + Per D-21, D-22: **__root.tsx** — Remove slide-out panel instances. + 1. Remove the Item SlideOutPanel block (lines ~189-199 in current file): + ``` + + {panelMode === "add" && } + {panelMode === "edit" && } + + ``` + 2. Remove the Candidate SlideOutPanel block (lines ~202-221 in current file): + ``` + {currentThreadId != null && ( + + + + )} + ``` + 3. Remove all panel-related state reads from the component: + - `const panelMode = useUIStore((s) => s.panelMode);` + - `const editingItemId = useUIStore((s) => s.editingItemId);` + - `const closePanel = useUIStore((s) => s.closePanel);` + - `const candidatePanelMode = useUIStore((s) => s.candidatePanelMode);` + - `const editingCandidateId = useUIStore((s) => s.editingCandidateId);` + - `const closeCandidatePanel = useUIStore((s) => s.closeCandidatePanel);` + - `const isItemPanelOpen = panelMode !== "closed";` + - `const isCandidatePanelOpen = candidatePanelMode !== "closed";` + 4. Remove unused imports: `SlideOutPanel`, `ItemForm`, `CandidateForm` (from the import block) + 5. Per D-25: Keep the `SlideOutPanel.tsx` component FILE — just remove its usage from __root.tsx + 6. Per D-26: Keep `ItemForm.tsx` and `CandidateForm.tsx` files — just remove their import from __root.tsx + 7. The `currentThreadId` variable may still be needed by CandidateDeleteDialog and ResolveDialog — check if it's still referenced. If those dialogs use it, keep the `threadMatch` and `currentThreadId` logic. If not, remove. + + Per D-23, D-24: **uiStore.ts** — Remove panel state. + Remove from UIState interface AND implementation: + - `panelMode: "closed" | "add" | "edit"` + - `editingItemId: number | null` + - `openAddPanel: () => void` + - `openEditPanel: (itemId: number) => void` + - `closePanel: () => void` + - `candidatePanelMode: "closed" | "add" | "edit"` + - `editingCandidateId: number | null` + - `openCandidateAddPanel: () => void` + - `openCandidateEditPanel: (id: number) => void` + - `closeCandidatePanel: () => void` + + KEEP all other state: + - confirmDeleteItemId, openConfirmDelete, closeConfirmDelete + - confirmDeleteCandidateId, openConfirmDeleteCandidate, closeConfirmDeleteCandidate + - resolveThreadId, resolveCandidateId, openResolveDialog, closeResolveDialog + - itemPickerOpen, openItemPicker, closeItemPicker + - confirmDeleteSetupId, openConfirmDeleteSetup, closeConfirmDeleteSetup + - createThreadModalOpen, openCreateThreadModal, closeCreateThreadModal + - externalLinkUrl, openExternalLink, closeExternalLink + - candidateViewMode, setCandidateViewMode + - selectedSetupId, setSelectedSetupId + - fabMenuOpen, openFabMenu, closeFabMenu + - catalogSearchOpen, catalogSearchMode, openCatalogSearch, closeCatalogSearch + + After cleanup, do a project-wide search for any remaining references to the removed properties: + `grep -r "openEditPanel\|openAddPanel\|closePanel\|openCandidateEditPanel\|openCandidateAddPanel\|closeCandidatePanel\|panelMode\|editingItemId\|editingCandidateId\|candidatePanelMode" src/client/ --include="*.tsx" --include="*.ts"` + If any references remain, update those files to remove the dead references (likely just removing unused imports or store selectors). + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + + - grep -qv "SlideOutPanel" src/client/routes/__root.tsx + - grep -qv "ItemForm" src/client/routes/__root.tsx + - grep -qv "CandidateForm" src/client/routes/__root.tsx + - grep -qv "panelMode" src/client/routes/__root.tsx + - grep -qv "openEditPanel" src/client/stores/uiStore.ts + - grep -qv "openAddPanel" src/client/stores/uiStore.ts + - grep -qv "openCandidateEditPanel" src/client/stores/uiStore.ts + - grep -qv "openCandidateAddPanel" src/client/stores/uiStore.ts + - grep -qv "candidatePanelMode" src/client/stores/uiStore.ts + - grep -q "openConfirmDelete" src/client/stores/uiStore.ts + - grep -q "openResolveDialog" src/client/stores/uiStore.ts + - test -f src/client/components/SlideOutPanel.tsx + - test -f src/client/components/ItemForm.tsx + - test -f src/client/components/CandidateForm.tsx + + + - Item and candidate SlideOutPanel instances removed from __root.tsx + - SlideOutPanel, ItemForm, CandidateForm imports removed from __root.tsx + - Panel-related state (panelMode, editingItemId, candidatePanelMode, editingCandidateId) removed from UIStore + - Panel-related actions (openEditPanel, openAddPanel, closePanel, openCandidateEditPanel, openCandidateAddPanel, closeCandidatePanel) removed from UIStore + - Non-panel state preserved (confirm delete, resolve dialog, FAB, catalog search, etc.) + - SlideOutPanel.tsx, ItemForm.tsx, CandidateForm.tsx files still exist on disk + - No remaining references to removed properties anywhere in src/client/ + - Lint passes cleanly + + + + + + +- `bun run lint` passes with no errors +- `bun run dev` starts and the full flow works: + - Click an item card → navigates to `/items/:id` detail page + - Click a candidate card → navigates to `/threads/:threadId/candidates/:candidateId` + - Click a catalog search result → closes overlay, navigates to `/global-items/:id` + - No slide-out panels appear anywhere in the app + - Confirm delete, resolve dialog, FAB menu, catalog search overlay all still work +- `grep -r "openEditPanel\|openCandidateEditPanel\|panelMode\|candidatePanelMode" src/client/ --include="*.tsx" --include="*.ts"` returns no results + + + +- All card components navigate to detail pages instead of opening panels +- Slide-out panels completely removed from the application +- UIStore cleaned of all panel-related state +- All non-panel functionality preserved (dialogs, FAB, catalog search) +- No broken references or dead imports +- Lint passes cleanly + + + +After completion, create `.planning/phases/21-item-catalog-detail-pages/21-03-SUMMARY.md` +