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