docs(05): create phase plan for image handling
This commit is contained in:
@@ -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 | - |
|
||||||
|
|||||||
198
.planning/phases/05-image-handling/05-01-PLAN.md
Normal file
198
.planning/phases/05-image-handling/05-01-PLAN.md
Normal 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>
|
||||||
168
.planning/phases/05-image-handling/05-02-PLAN.md
Normal file
168
.planning/phases/05-image-handling/05-02-PLAN.md
Normal 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>
|
||||||
Reference in New Issue
Block a user