Files
GearBox/.planning/milestones/v1.1-phases/05-image-handling/05-01-PLAN.md
Jean-Luc Makiola 407fa45280 chore: complete v1.1 milestone — Fixes & Polish
Archive v1.1 artifacts (roadmap, requirements, phases) to milestones/.
Evolve PROJECT.md with shipped requirements and new key decisions.
Reorganize ROADMAP.md with collapsed milestone groupings.
Update retrospective with v1.1 lessons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 18:16:27 +01:00

199 lines
8.5 KiB
Markdown

---
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>