docs(21): create phase plan — 3 plans across 2 waves
This commit is contained in:
@@ -217,7 +217,11 @@ Plans:
|
|||||||
3. Thread candidates navigate to detail pages instead of opening slide-out panels
|
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
|
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
|
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
|
**UI hint**: yes
|
||||||
|
|
||||||
### Phase 22: Add-from-Catalog & Thread Integration
|
### 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 |
|
| 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 |
|
| 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 |
|
| 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 | - |
|
| 22. Add-from-Catalog & Thread Integration | v2.0 | 0/? | Not started | - |
|
||||||
| 23. Manual Entry Fallback | v2.0 | 0/? | Not started | - |
|
| 23. Manual Entry Fallback | v2.0 | 0/? | Not started | - |
|
||||||
|
|||||||
241
.planning/phases/21-item-catalog-detail-pages/21-01-PLAN.md
Normal file
241
.planning/phases/21-item-catalog-detail-pages/21-01-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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`)
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key hooks the executor needs -->
|
||||||
|
|
||||||
|
From src/client/hooks/useItems.ts:
|
||||||
|
```typescript
|
||||||
|
export function useItem(id: number | null): UseQueryResult<ItemWithCategory>;
|
||||||
|
export function useUpdateItem(): UseMutationResult<ItemWithCategory, Error, { id: number } & Partial<CreateItem>>;
|
||||||
|
export function useDeleteItem(): UseMutationResult<{ success: boolean }, Error, number>;
|
||||||
|
export function useDuplicateItem(): UseMutationResult<Item, Error, number>;
|
||||||
|
|
||||||
|
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>;
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create private item detail page with edit mode toggle</name>
|
||||||
|
<files>src/client/routes/items/$itemId.tsx</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
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 — `<Link to="/collection">← Back to collection</Link>`.
|
||||||
|
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<boolean>(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.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- `/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
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Enhance catalog detail page with Add to Collection button and improved layout</name>
|
||||||
|
<files>src/client/routes/global-items/$globalItemId.tsx</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
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)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- 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)
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/21-item-catalog-detail-pages/21-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
239
.planning/phases/21-item-catalog-detail-pages/21-02-PLAN.md
Normal file
239
.planning/phases/21-item-catalog-detail-pages/21-02-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key hooks and types the executor needs -->
|
||||||
|
|
||||||
|
From src/client/hooks/useCandidates.ts:
|
||||||
|
```typescript
|
||||||
|
export function useCreateCandidate(threadId: number): UseMutationResult<CandidateResponse, Error, CreateCandidate & { imageFilename?: string }>;
|
||||||
|
export function useUpdateCandidate(threadId: number): UseMutationResult<CandidateResponse, Error, UpdateCandidate & { candidateId: number; imageFilename?: string }>;
|
||||||
|
export function useDeleteCandidate(threadId: number): UseMutationResult<{ success: boolean }, Error, number>;
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/client/hooks/useThreads.ts:
|
||||||
|
```typescript
|
||||||
|
export function useThread(id: number): UseQueryResult<ThreadWithCandidates>;
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Restructure thread route and create candidate detail page</name>
|
||||||
|
<files>
|
||||||
|
src/client/routes/threads/$threadId.tsx
|
||||||
|
src/client/routes/threads/$threadId/index.tsx
|
||||||
|
src/client/routes/threads/$threadId/candidates/$candidateId.tsx
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
**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<boolean>(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 — `<Link to="/threads/$threadId" params={{ threadId: String(threadId) }}>← Back to thread</Link>`.
|
||||||
|
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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- 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
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Add candidate modal dialog on thread page</name>
|
||||||
|
<files>
|
||||||
|
src/client/routes/threads/$threadId/index.tsx
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
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 && <AddCandidateModal threadId={threadId} onClose={() => 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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- 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
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/21-item-catalog-detail-pages/21-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
331
.planning/phases/21-item-catalog-detail-pages/21-03-PLAN.md
Normal file
331
.planning/phases/21-item-catalog-detail-pages/21-03-PLAN.md
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
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
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.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
|
||||||
|
|
||||||
|
<!-- Read Plan 01 and 02 SUMMARYs to confirm navigation targets exist -->
|
||||||
|
@.planning/phases/21-item-catalog-detail-pages/21-01-SUMMARY.md
|
||||||
|
@.planning/phases/21-item-catalog-detail-pages/21-02-SUMMARY.md
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Navigation patterns the executor needs -->
|
||||||
|
|
||||||
|
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
|
||||||
|
<SlideOutPanel isOpen={isItemPanelOpen} onClose={closePanel} title={...}>
|
||||||
|
<ItemForm mode="add" | mode="edit" itemId={editingItemId} />
|
||||||
|
</SlideOutPanel>
|
||||||
|
<SlideOutPanel isOpen={isCandidatePanelOpen} onClose={closeCandidatePanel} title={...}>
|
||||||
|
<CandidateForm mode="add" | mode="edit" threadId={...} candidateId={...} />
|
||||||
|
</SlideOutPanel>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Rewire card click handlers to navigate to detail pages</name>
|
||||||
|
<files>
|
||||||
|
src/client/components/ItemCard.tsx
|
||||||
|
src/client/components/CandidateCard.tsx
|
||||||
|
src/client/components/CandidateListItem.tsx
|
||||||
|
src/client/components/CatalogSearchOverlay.tsx
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
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
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- 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
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Remove slide-out panels from root layout and clean UIStore</name>
|
||||||
|
<files>
|
||||||
|
src/client/routes/__root.tsx
|
||||||
|
src/client/stores/uiStore.ts
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
- 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)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Per D-21, D-22: **__root.tsx** — Remove slide-out panel instances.
|
||||||
|
1. Remove the Item SlideOutPanel block (lines ~189-199 in current file):
|
||||||
|
```
|
||||||
|
<SlideOutPanel isOpen={isItemPanelOpen} onClose={closePanel} title={...}>
|
||||||
|
{panelMode === "add" && <ItemForm mode="add" />}
|
||||||
|
{panelMode === "edit" && <ItemForm mode="edit" itemId={editingItemId} />}
|
||||||
|
</SlideOutPanel>
|
||||||
|
```
|
||||||
|
2. Remove the Candidate SlideOutPanel block (lines ~202-221 in current file):
|
||||||
|
```
|
||||||
|
{currentThreadId != null && (
|
||||||
|
<SlideOutPanel isOpen={isCandidatePanelOpen} ...>
|
||||||
|
<CandidateForm ... />
|
||||||
|
</SlideOutPanel>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
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).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
- 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
|
||||||
|
</acceptance_criteria>
|
||||||
|
<done>
|
||||||
|
- 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
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- `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
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- 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
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/21-item-catalog-detail-pages/21-03-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user