Files
GearBox/.planning/phases/05-image-handling/05-01-PLAN.md

8.5 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
05-image-handling 01 execute 1
src/client/components/ImageUpload.tsx
src/client/components/ItemForm.tsx
src/client/components/CandidateForm.tsx
true
IMG-01
IMG-03
IMG-04
truths artifacts key_links
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
path provides min_lines
src/client/components/ImageUpload.tsx Hero image area with placeholder, upload, preview, remove 60
path provides
src/client/components/ItemForm.tsx ImageUpload moved to top of form as first element
path provides
src/client/components/CandidateForm.tsx ImageUpload moved to top of form as first element
from to via pattern
src/client/components/ImageUpload.tsx /api/images apiUpload call in handleFileChange apiUpload.*api/images
from to via pattern
src/client/components/ItemForm.tsx src/client/components/ImageUpload.tsx ImageUpload component at top of form <ImageUpload
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.

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

@.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:

interface ImageUploadProps {
  value: string | null;
  onChange: (filename: string | null) => void;
}

From src/client/lib/api.ts:

export async function apiUpload<T>(url: string, file: File): Promise<T>
// Uses FormData with field name "image"

From src/server/routes/images.ts:

// POST /api/images -> { filename: string } (201)
// Saves to ./uploads/{timestamp}-{uuid}.{ext}

From src/server/index.ts:

// Static serving: app.use("/uploads/*", serveStatic({ root: "./" }));

From vite.config.ts:

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

<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>
After completion, create `.planning/phases/05-image-handling/05-01-SUMMARY.md`