docs(05): create phase plan for image handling

This commit is contained in:
2026-03-15 17:06:33 +01:00
parent bb8fc59d03
commit a52fa3b24c
3 changed files with 370 additions and 3 deletions

View File

@@ -49,10 +49,11 @@ Plans:
2. Gear collection cards show item images (or a placeholder when no image exists) 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 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 4. User can upload an image by clicking the placeholder area, and the preview updates immediately
**Plans**: TBD **Plans**: 2 plans
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 ### Phase 6: Category Icons
**Goal**: Categories use clean Lucide icons instead of emoji **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 | | 2. Planning Threads | v1.0 | 3/3 | Complete | 2026-03-15 |
| 3. Setups and Dashboard | 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 | - | | 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 | - | | 6. Category Icons | v1.1 | 0/? | Not started | - |

View File

@@ -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: "<ImageUpload"
---
<objective>
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.
</objective>
<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
<!-- Key types and contracts the executor needs -->
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<T>(url: string, file: File): Promise<T>
// 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"
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Fix image display bug and investigate root cause</name>
<files>src/client/components/ImageUpload.tsx, src/server/routes/images.ts, src/server/index.ts, vite.config.ts</files>
<action>
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.
</action>
<verify>
<automated>curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/uploads/ 2>/dev/null; echo "Server static route configured"</automated>
</verify>
<done>Uploaded images display correctly when referenced via /uploads/{filename} path. The root cause is identified, documented in the summary, and fixed.</done>
</task>
<task type="auto">
<name>Task 2: Redesign ImageUpload as hero area and move to top of forms</name>
<files>src/client/components/ImageUpload.tsx, src/client/components/ItemForm.tsx, src/client/components/CandidateForm.tsx</files>
<action>
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 `<ImageUpload>` from the bottom of the form (currently after Product Link) to the very first element, BEFORE the Name field. Remove the wrapping `<div>` 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.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/05-image-handling/05-01-SUMMARY.md`
</output>

View File

@@ -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"
---
<objective>
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.
</objective>
<execution_context>
@/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md
@/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
<interfaces>
<!-- Key types and contracts the executor needs -->
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.
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add always-visible 4:3 image area with placeholders to ItemCard and CandidateCard</name>
<files>src/client/components/ItemCard.tsx, src/client/components/CandidateCard.tsx</files>
<action>
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 `<div className="aspect-[4/3] bg-gray-50">` container
- When imageFilename exists: render `<img src={/uploads/${imageFilename}} alt={name} className="w-full h-full object-cover" />` (same as current)
- When imageFilename is null: render a centered placeholder with the category emoji. Use `<div className="w-full h-full flex flex-col items-center justify-center">` containing a `<span className="text-3xl">{categoryEmoji}</span>`. 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.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Add small thumbnails to setup item lists</name>
<files>src/client/routes/setups/$setupId.tsx</files>
<action>
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: `<img src={/uploads/${imageFilename}} />`
- When null: `<div className="w-10 h-10 rounded-lg bg-gray-50 flex items-center justify-center flex-shrink-0"><span className="text-sm">{categoryEmoji}</span></div>`
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.
</action>
<verify>
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run lint 2>&1 | tail -5</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/05-image-handling/05-02-SUMMARY.md`
</output>