diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 493556a..3d8144f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -49,10 +49,11 @@ Plans: 2. Gear collection cards show item images (or a placeholder when no image exists) 3. Item form displays an image preview area at the top with a placeholder icon when no image is set 4. User can upload an image by clicking the placeholder area, and the preview updates immediately -**Plans**: TBD +**Plans**: 2 plans Plans: -- [ ] 05-01: TBD +- [ ] 05-01-PLAN.md — Fix image display bug and redesign ImageUpload as hero preview area +- [ ] 05-02-PLAN.md — Add card image placeholders and setup thumbnails ### Phase 6: Category Icons **Goal**: Categories use clean Lucide icons instead of emoji @@ -78,5 +79,5 @@ Phases execute in numeric order: 4 -> 5 -> 6 | 2. Planning Threads | v1.0 | 3/3 | Complete | 2026-03-15 | | 3. Setups and Dashboard | v1.0 | 3/3 | Complete | 2026-03-15 | | 4. Database & Planning Fixes | v1.1 | 1/2 | In progress | - | -| 5. Image Handling | v1.1 | 0/? | Not started | - | +| 5. Image Handling | v1.1 | 0/2 | Not started | - | | 6. Category Icons | v1.1 | 0/? | Not started | - | diff --git a/.planning/phases/05-image-handling/05-01-PLAN.md b/.planning/phases/05-image-handling/05-01-PLAN.md new file mode 100644 index 0000000..f3d83e8 --- /dev/null +++ b/.planning/phases/05-image-handling/05-01-PLAN.md @@ -0,0 +1,198 @@ +--- +phase: 05-image-handling +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/client/components/ImageUpload.tsx + - src/client/components/ItemForm.tsx + - src/client/components/CandidateForm.tsx +autonomous: true +requirements: [IMG-01, IMG-03, IMG-04] + +must_haves: + truths: + - "Uploaded images display correctly in the ImageUpload preview area (not broken/missing)" + - "Item form shows a full-width 4:3 hero image area at the top of the form" + - "When no image is set, hero area shows gray background with centered icon and 'Click to add photo' text" + - "Clicking the placeholder opens file picker and uploaded image replaces placeholder immediately" + - "When image exists, a small circular X button in top-right removes the image" + - "Clicking an existing image opens file picker to replace it" + - "CandidateForm has the same hero area redesign as ItemForm" + artifacts: + - path: "src/client/components/ImageUpload.tsx" + provides: "Hero image area with placeholder, upload, preview, remove" + min_lines: 60 + - path: "src/client/components/ItemForm.tsx" + provides: "ImageUpload moved to top of form as first element" + - path: "src/client/components/CandidateForm.tsx" + provides: "ImageUpload moved to top of form as first element" + key_links: + - from: "src/client/components/ImageUpload.tsx" + to: "/api/images" + via: "apiUpload call in handleFileChange" + pattern: "apiUpload.*api/images" + - from: "src/client/components/ItemForm.tsx" + to: "src/client/components/ImageUpload.tsx" + via: "ImageUpload component at top of form" + pattern: " +Fix the image display bug so uploaded images render correctly, then redesign the ImageUpload component into a hero image preview area and move it to the top of both ItemForm and CandidateForm. + +Purpose: Images upload but don't display -- fixing this is the prerequisite for all image UX. The hero area redesign makes images prominent and the upload interaction intuitive (click placeholder to add, click image to replace). + +Output: Working image display, redesigned ImageUpload component, updated ItemForm and CandidateForm. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md + +@src/client/components/ImageUpload.tsx +@src/client/components/ItemForm.tsx +@src/client/components/CandidateForm.tsx +@src/client/lib/api.ts +@src/server/routes/images.ts +@src/server/index.ts +@vite.config.ts + + + + +From src/client/components/ImageUpload.tsx: +```typescript +interface ImageUploadProps { + value: string | null; + onChange: (filename: string | null) => void; +} +``` + +From src/client/lib/api.ts: +```typescript +export async function apiUpload(url: string, file: File): Promise +// Uses FormData with field name "image" +``` + +From src/server/routes/images.ts: +```typescript +// POST /api/images -> { filename: string } (201) +// Saves to ./uploads/{timestamp}-{uuid}.{ext} +``` + +From src/server/index.ts: +```typescript +// Static serving: app.use("/uploads/*", serveStatic({ root: "./" })); +``` + +From vite.config.ts: +```typescript +// Dev proxy: "/uploads": "http://localhost:3000" +``` + + + + + + + Task 1: Fix image display bug and investigate root cause + src/client/components/ImageUpload.tsx, src/server/routes/images.ts, src/server/index.ts, vite.config.ts + +Investigate why uploaded images don't render in the UI. The upload flow works (apiUpload POSTs to /api/images, server saves to ./uploads/ with UUID filename, returns { filename }), but images don't display. + +Debugging checklist (work through systematically): +1. Start dev servers (`bun run dev:server` and `bun run dev:client`) and upload a test image +2. Check the uploads/ directory -- does the file exist on disk? +3. Try accessing the image directly via browser: `http://localhost:5173/uploads/{filename}` -- does it load? +4. If not, try `http://localhost:3000/uploads/{filename}` -- does the backend serve it? +5. Check Vite proxy config in vite.config.ts -- `/uploads` proxy to `http://localhost:3000` is configured +6. Check Hono static serving in src/server/index.ts -- `serveStatic({ root: "./" })` should serve `./uploads/*` +7. Check if the `imageFilename` field is actually being saved to the database and returned by GET /api/items + +Common suspects: +- The serveStatic middleware path might not match (root vs rewrite issue) +- The imageFilename might not be persisted in the database (check the item update/create service) +- The Vite proxy might need a rewrite rule + +Fix the root cause. If the issue is in static file serving, fix the serveStatic config. If it's a database persistence issue, fix the service layer. If it's a proxy issue, fix vite.config.ts. + +After fixing, verify an uploaded image displays at `/uploads/{filename}` in the browser. + + + curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/uploads/ 2>/dev/null; echo "Server static route configured" + + Uploaded images display correctly when referenced via /uploads/{filename} path. The root cause is identified, documented in the summary, and fixed. + + + + Task 2: Redesign ImageUpload as hero area and move to top of forms + src/client/components/ImageUpload.tsx, src/client/components/ItemForm.tsx, src/client/components/CandidateForm.tsx + +Redesign ImageUpload.tsx into a hero image preview area per user decisions: + +**ImageUpload component redesign:** +- Full-width container with `aspect-[4/3]` ratio (matches ItemCard) +- Rounded corners (`rounded-xl`), overflow-hidden +- The entire area is clickable (triggers hidden file input) + +**When no image (placeholder state):** +- Light gray background (bg-gray-50 or bg-gray-100) +- Centered Lucide `ImagePlus` icon (install lucide-react if not present, or use inline SVG) in gray-300/gray-400 +- "Click to add photo" text below the icon in text-sm text-gray-400 +- Cursor pointer on hover + +**When image exists (preview state):** +- Full-width image with `object-cover` filling the 4:3 area +- Small circular X button in top-right corner: `absolute top-2 right-2`, white/semi-transparent bg, rounded-full, ~28px, with X icon. onClick calls onChange(null) and stops propagation (so it doesn't trigger file picker) +- Clicking the image itself opens file picker to replace + +**When uploading:** +- Spinner overlay centered on the hero area (simple CSS spinner or Loader2 icon from lucide-react with animate-spin) +- Semi-transparent overlay (bg-white/60 or bg-black/20) over the placeholder/current image + +**Error state:** +- Red text below the hero area (same as current) + +**Move ImageUpload to top of forms:** +- In ItemForm.tsx: Move the `` from the bottom of the form (currently after Product Link) to the very first element, BEFORE the Name field. Remove the wrapping `
` with the "Image" label -- the hero area is self-explanatory. +- In CandidateForm.tsx: Same change -- move ImageUpload to the top, remove the "Image" label wrapper. + +Keep the existing ImageUploadProps interface unchanged ({ value, onChange }) so no other code needs updating. + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + ImageUpload renders as a 4:3 hero area with placeholder icon when empty, full image preview when set, spinner during upload, and X button to remove. Both ItemForm and CandidateForm show ImageUpload as the first form element. + + + + + +1. Upload an image via ItemForm -- it should appear in the hero preview area immediately +2. The hero area shows a placeholder icon when no image is set +3. Clicking the placeholder opens the file picker +4. Clicking an existing image opens the file picker to replace +5. The X button removes the image +6. CandidateForm has identical hero area behavior +7. `bun run lint` passes + + + +- Uploaded images display correctly (bug fixed) +- Hero image area renders at top of ItemForm and CandidateForm +- Placeholder with icon shown when no image set +- Upload via click works, preview updates immediately +- Remove button clears the image + + + +After completion, create `.planning/phases/05-image-handling/05-01-SUMMARY.md` + diff --git a/.planning/phases/05-image-handling/05-02-PLAN.md b/.planning/phases/05-image-handling/05-02-PLAN.md new file mode 100644 index 0000000..e71fc74 --- /dev/null +++ b/.planning/phases/05-image-handling/05-02-PLAN.md @@ -0,0 +1,168 @@ +--- +phase: 05-image-handling +plan: 02 +type: execute +wave: 2 +depends_on: [05-01] +files_modified: + - src/client/components/ItemCard.tsx + - src/client/components/CandidateCard.tsx + - src/client/routes/setups/$setupId.tsx +autonomous: true +requirements: [IMG-02] + +must_haves: + truths: + - "Item cards always show a 4:3 image area, even when no image exists" + - "Cards without images show a gray placeholder with the item's category emoji centered" + - "Cards with images display the image in the 4:3 area" + - "Candidate cards have the same placeholder treatment as item cards" + - "Setup item lists show small square thumbnails (~40px) next to item names" + - "Setup thumbnails show category emoji placeholder when item has no image" + artifacts: + - path: "src/client/components/ItemCard.tsx" + provides: "Always-visible 4:3 image area with placeholder fallback" + - path: "src/client/components/CandidateCard.tsx" + provides: "Always-visible 4:3 image area with placeholder fallback" + - path: "src/client/routes/setups/$setupId.tsx" + provides: "Small square thumbnails in setup item list" + key_links: + - from: "src/client/components/ItemCard.tsx" + to: "/uploads/{imageFilename}" + via: "img src attribute" + pattern: "src=.*uploads" + - from: "src/client/routes/setups/$setupId.tsx" + to: "/uploads/{imageFilename}" + via: "img src for thumbnails" + pattern: "src=.*uploads" +--- + + +Add image placeholders to all gear cards (items and candidates) so every card has a consistent 4:3 image area, and add small thumbnails to setup item lists. + +Purpose: Consistent card heights in the grid (no layout shift between cards with/without images) and visual context in setup lists via thumbnails. + +Output: Updated ItemCard, CandidateCard, and setup detail route with image placeholders and thumbnails. + + + +@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md +@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/05-image-handling/05-01-SUMMARY.md + +@src/client/components/ItemCard.tsx +@src/client/components/CandidateCard.tsx +@src/client/routes/setups/$setupId.tsx + + + + +From src/client/components/ItemCard.tsx: +```typescript +interface ItemCardProps { + id: number; + name: string; + weightGrams: number | null; + priceCents: number | null; + categoryName: string; + categoryEmoji: string; + imageFilename: string | null; + onRemove?: () => void; +} +``` + +From src/client/components/CandidateCard.tsx: +```typescript +interface CandidateCardProps { + id: number; + name: string; + weightGrams: number | null; + priceCents: number | null; + categoryName: string; + categoryEmoji: string; + imageFilename: string | null; + threadId: number; + isActive: boolean; +} +``` + +Setup route renders items via ItemCard with all props including categoryEmoji and imageFilename. + + + + + + + Task 1: Add always-visible 4:3 image area with placeholders to ItemCard and CandidateCard + src/client/components/ItemCard.tsx, src/client/components/CandidateCard.tsx + +Update both ItemCard and CandidateCard to ALWAYS render the 4:3 image area (currently they conditionally render it only when imageFilename exists). + +**ItemCard.tsx changes:** +- Replace the conditional `{imageFilename && (...)}` block with an always-rendered `
` container +- When imageFilename exists: render `{name}` (same as current) +- When imageFilename is null: render a centered placeholder with the category emoji. Use `
` containing a `{categoryEmoji}`. The gray-50 background provides the subtle placeholder look. + +**CandidateCard.tsx changes:** +- Identical treatment: always render the 4:3 area, show image or category emoji placeholder +- Same structure as ItemCard + +Both cards already receive categoryEmoji as a prop, so no prop changes needed. + + + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + Every ItemCard and CandidateCard renders a 4:3 image area. Cards with images show the image; cards without show a gray placeholder with the category emoji centered. + + + + Task 2: Add small thumbnails to setup item lists + src/client/routes/setups/$setupId.tsx + +The setup detail page currently renders items using ItemCard in a grid. The setup also has a concept of item lists. Add small square thumbnails next to item names in the setup's item display. + +Since the setup page uses ItemCard components in a grid (which now have the 4:3 area from Task 1), the card-level display is already handled. The additional work here is for any list-style display of setup items. + +Check the setup detail route for list-view rendering of items. If items are only shown via ItemCard grid, then this task focuses on ensuring the ItemCard placeholder works in the setup context. If there's a separate list view, add thumbnails: + +**Thumbnail spec (for list views):** +- Small square image: `w-10 h-10 rounded-lg object-cover flex-shrink-0` (~40px) +- Placed to the left of the item name in a flex row +- When imageFilename exists: `` +- When null: `
{categoryEmoji}
` + +If the setup page only uses ItemCard (no list view), verify the ItemCard changes from Task 1 render correctly in the setup context and note this in the summary. +
+ + cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5 + + Setup item lists show small square thumbnails (or category emoji placeholders) next to item names. If setup only uses ItemCard grid, the placeholder from Task 1 renders correctly in setup context. +
+ + + + +1. Item cards in the gear collection always show a 4:3 area (no layout jump between cards with/without images) +2. Cards without images show gray background with category emoji centered +3. Cards with images show the image with object-cover +4. Candidate cards have identical placeholder behavior +5. Setup item display includes image context (thumbnails or card placeholders) +6. `bun run lint` passes + + + +- All gear cards have consistent heights due to always-present 4:3 image area +- Placeholder shows category emoji when no image exists +- Setup items show image context (thumbnail or card placeholder) +- No layout shift between cards with and without images + + + +After completion, create `.planning/phases/05-image-handling/05-02-SUMMARY.md` +