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>
This commit is contained in:
198
.planning/milestones/v1.1-phases/05-image-handling/05-01-PLAN.md
Normal file
198
.planning/milestones/v1.1-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>
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
phase: 05-image-handling
|
||||
plan: 01
|
||||
subsystem: ui
|
||||
tags: [image-upload, hero-area, zod, tailwind, forms]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: none
|
||||
provides: existing ImageUpload, ItemForm, CandidateForm components
|
||||
provides:
|
||||
- Working image persistence (Zod schema fix)
|
||||
- Hero image preview area component
|
||||
- Redesigned form layout with image-first UX
|
||||
affects: [06-category-icons]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [hero-image-area, inline-svg-icons]
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- src/shared/schemas.ts
|
||||
- src/client/components/ImageUpload.tsx
|
||||
- src/client/components/ItemForm.tsx
|
||||
- src/client/components/CandidateForm.tsx
|
||||
|
||||
key-decisions:
|
||||
- "Used inline SVGs instead of adding lucide-react dependency -- keeps bundle lean for 3 icons"
|
||||
- "Root cause of image bug: Zod schemas missing imageFilename field, validator silently stripped it"
|
||||
|
||||
patterns-established:
|
||||
- "Hero image area: full-width 4:3 aspect ratio clickable area with placeholder/preview states"
|
||||
|
||||
requirements-completed: [IMG-01, IMG-03, IMG-04]
|
||||
|
||||
# Metrics
|
||||
duration: 3min
|
||||
completed: 2026-03-15
|
||||
---
|
||||
|
||||
# Phase 5 Plan 1: Image Display Fix & Hero Area Summary
|
||||
|
||||
**Fixed image persistence bug (Zod schema missing imageFilename) and redesigned ImageUpload as 4:3 hero area at top of item/candidate forms**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 3 min
|
||||
- **Started:** 2026-03-15T16:08:51Z
|
||||
- **Completed:** 2026-03-15T16:11:27Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 4
|
||||
|
||||
## Accomplishments
|
||||
- Identified and fixed root cause of image display bug: imageFilename was missing from Zod validation schemas, causing @hono/zod-validator to silently strip it from payloads
|
||||
- Redesigned ImageUpload into a full-width 4:3 hero image area with placeholder, preview, upload spinner, and remove states
|
||||
- Moved ImageUpload to first element in both ItemForm and CandidateForm, removing redundant labels
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Fix image display bug and investigate root cause** - `8c0529c` (fix)
|
||||
2. **Task 2: Redesign ImageUpload as hero area and move to top of forms** - `3243be4` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
- `src/shared/schemas.ts` - Added imageFilename to createItemSchema and createCandidateSchema
|
||||
- `src/client/components/ImageUpload.tsx` - Redesigned as 4:3 hero area with placeholder/preview/spinner states
|
||||
- `src/client/components/ItemForm.tsx` - Moved ImageUpload to top, removed label wrapper
|
||||
- `src/client/components/CandidateForm.tsx` - Moved ImageUpload to top, removed label wrapper
|
||||
|
||||
## Decisions Made
|
||||
- Used inline SVGs instead of adding lucide-react dependency -- only 3 icons needed, avoids bundle bloat
|
||||
- Root cause identified as Zod schema issue, not static file serving or Vite proxy (both were working correctly)
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
- Image display and upload flow fully functional
|
||||
- Hero area component ready for any future image-related enhancements in plan 05-02
|
||||
- Forms have clean image-first layout
|
||||
|
||||
---
|
||||
*Phase: 05-image-handling*
|
||||
*Completed: 2026-03-15*
|
||||
168
.planning/milestones/v1.1-phases/05-image-handling/05-02-PLAN.md
Normal file
168
.planning/milestones/v1.1-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>
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
phase: 05-image-handling
|
||||
plan: 02
|
||||
subsystem: ui
|
||||
tags: [image-placeholder, card-layout, tailwind, aspect-ratio]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 05-image-handling
|
||||
provides: Working image persistence and hero area component from plan 01
|
||||
provides:
|
||||
- Always-visible 4:3 image area on all gear cards with category emoji placeholders
|
||||
- Consistent card heights across grid layouts
|
||||
affects: [06-category-icons]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [category-emoji-placeholder, always-visible-image-area]
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- src/client/components/ItemCard.tsx
|
||||
- src/client/components/CandidateCard.tsx
|
||||
|
||||
key-decisions:
|
||||
- "Setup detail page only uses ItemCard grid (no separate list view), so no thumbnail component needed"
|
||||
- "Category emoji as placeholder provides visual context without requiring default images"
|
||||
|
||||
patterns-established:
|
||||
- "Always-visible image area: 4:3 aspect ratio container with conditional image or emoji placeholder"
|
||||
|
||||
requirements-completed: [IMG-02]
|
||||
|
||||
# Metrics
|
||||
duration: 1min
|
||||
completed: 2026-03-15
|
||||
---
|
||||
|
||||
# Phase 5 Plan 2: Image Placeholders & Thumbnails Summary
|
||||
|
||||
**Always-visible 4:3 image area on ItemCard and CandidateCard with category emoji placeholders for consistent grid layouts**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 1 min
|
||||
- **Started:** 2026-03-15T16:13:39Z
|
||||
- **Completed:** 2026-03-15T16:14:40Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 2
|
||||
|
||||
## Accomplishments
|
||||
- Replaced conditional image rendering with always-present 4:3 aspect ratio area on both ItemCard and CandidateCard
|
||||
- Cards without images now show category emoji centered on gray background, providing visual context
|
||||
- Verified setup detail page uses ItemCard grid (no separate list view), so card placeholders serve both contexts
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Add always-visible 4:3 image area with placeholders to ItemCard and CandidateCard** - `acf34c3` (feat)
|
||||
2. **Task 2: Add small thumbnails to setup item lists** - No commit needed (setup page only uses ItemCard grid, already updated in Task 1)
|
||||
|
||||
## Files Created/Modified
|
||||
- `src/client/components/ItemCard.tsx` - Always-visible 4:3 image area with emoji placeholder fallback
|
||||
- `src/client/components/CandidateCard.tsx` - Same treatment as ItemCard for consistent behavior
|
||||
|
||||
## Decisions Made
|
||||
- Setup detail page only uses ItemCard in a grid layout (no separate list view exists), so no additional thumbnail component was needed
|
||||
- Category emoji serves as an effective placeholder, providing category context without requiring default images
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written. The plan anticipated the possibility that the setup page only uses ItemCard grid and specified to verify and note in summary.
|
||||
|
||||
## Issues Encountered
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
- All image display components complete (upload, hero area, card placeholders)
|
||||
- Phase 5 image handling fully complete
|
||||
- Ready for Phase 6 category icon system
|
||||
|
||||
---
|
||||
*Phase: 05-image-handling*
|
||||
*Completed: 2026-03-15*
|
||||
100
.planning/milestones/v1.1-phases/05-image-handling/05-CONTEXT.md
Normal file
100
.planning/milestones/v1.1-phases/05-image-handling/05-CONTEXT.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Phase 5: Image Handling - Context
|
||||
|
||||
**Gathered:** 2026-03-15
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Phase Boundary
|
||||
|
||||
Fix image display throughout the app (images upload but don't render), redesign the upload UX with a hero image preview area and placeholder icons, and add image display to gear cards, candidate cards, and setup item lists. No new image features (galleries, editing, tagging) — those would be separate phases.
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### Image preview area (item form)
|
||||
- Move image from bottom of form to a full-width hero area at the top
|
||||
- 4:3 landscape aspect ratio (matches ItemCard's existing aspect-[4/3])
|
||||
- When no image: light gray background with centered Lucide icon (ImagePlus or Camera) and "Click to add photo" text below
|
||||
- When image exists: full-width image with object-cover, small circular X button in top-right to remove
|
||||
- Clicking the image opens file picker to replace (same behavior as clicking placeholder)
|
||||
|
||||
### Card placeholders
|
||||
- All cards (items and candidates) show the 4:3 image area always — consistent card heights in grid
|
||||
- When no image: light gray (gray-50/gray-100) background with the item's category icon centered
|
||||
- Category icons are currently emoji — use whatever is current (Phase 6 will migrate to Lucide)
|
||||
- Candidate cards get the same placeholder treatment as item cards
|
||||
|
||||
### Upload interaction
|
||||
- Click only — no drag-and-drop (keeps it simple for side panel form)
|
||||
- Spinner overlay centered on hero area while uploading
|
||||
- No client-side image processing (no crop, no resize) — CSS object-cover handles display
|
||||
- CandidateForm gets the same hero area redesign as ItemForm
|
||||
|
||||
### Image in detail/setup views
|
||||
- Clicking uploaded image in form opens file picker to replace (no lightbox/zoom)
|
||||
- Setup item lists show small square thumbnails (~40px) with rounded corners next to item name
|
||||
- Setup thumbnails show category icon placeholder when item has no image
|
||||
|
||||
### Image display bug fix
|
||||
- Investigate and fix root cause of images uploading but not rendering (likely path/proxy issue)
|
||||
- This is prerequisite work — fix before redesigning the UX
|
||||
|
||||
### Claude's Discretion
|
||||
- Exact placeholder icon choice (ImagePlus vs Camera vs similar)
|
||||
- Spinner animation style
|
||||
- Exact gray shade for placeholder backgrounds
|
||||
- Transition/animation on image load
|
||||
- Error state design for failed uploads
|
||||
|
||||
</decisions>
|
||||
|
||||
<code_context>
|
||||
## Existing Code Insights
|
||||
|
||||
### Reusable Assets
|
||||
- `ImageUpload` component (`src/client/components/ImageUpload.tsx`): Existing upload logic with file validation, apiUpload call, preview, and remove button — needs restructuring into hero area pattern
|
||||
- `ItemCard` (`src/client/components/ItemCard.tsx`): Already renders imageFilename with `aspect-[4/3]` but skips image area when null — needs placeholder addition
|
||||
- `CandidateCard` / `CandidateForm`: Candidate equivalents that need same treatment
|
||||
- `apiUpload` helper in `lib/api.ts`: Upload function already works
|
||||
|
||||
### Established Patterns
|
||||
- Images stored as UUID filenames in `./uploads/` directory
|
||||
- Server serves `/uploads/*` via `hono/bun` serveStatic
|
||||
- Vite dev proxy forwards `/uploads` to `http://localhost:3000`
|
||||
- Image upload API at `POST /api/images` returns `{ filename }` (201 status)
|
||||
- `imageFilename` field on items and candidates — string or null
|
||||
- 5MB max, JPG/PNG/WebP accepted
|
||||
|
||||
### Integration Points
|
||||
- `src/client/components/ItemForm.tsx`: Move ImageUpload from bottom to top, redesign as hero area
|
||||
- `src/client/components/CandidateForm.tsx`: Same hero area redesign
|
||||
- `src/client/components/ItemCard.tsx`: Add placeholder when imageFilename is null
|
||||
- `src/client/components/CandidateCard.tsx`: Add placeholder when imageFilename is null
|
||||
- `src/client/routes/setups/$setupId.tsx`: Add small thumbnails to setup item list
|
||||
- Server static file serving: Verify `/uploads/*` path works in both dev and production
|
||||
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
- Hero area should feel like a product photo section — clean, prominent, image-first
|
||||
- Placeholder with category icon adds visual meaning even before images are uploaded
|
||||
- Consistent 4:3 aspect ratio across hero area and cards keeps everything aligned
|
||||
- Setup thumbnails should be compact (40px square) — don't dominate the list layout
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## Deferred Ideas
|
||||
|
||||
None — discussion stayed within phase scope
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 05-image-handling*
|
||||
*Context gathered: 2026-03-15*
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
phase: 05-image-handling
|
||||
verified: 2026-03-15T17:30:00Z
|
||||
status: passed
|
||||
score: 13/13 must-haves verified
|
||||
re_verification: false
|
||||
---
|
||||
|
||||
# Phase 5: Image Handling Verification Report
|
||||
|
||||
**Phase Goal:** Users can see and manage gear images throughout the app
|
||||
**Verified:** 2026-03-15T17:30:00Z
|
||||
**Status:** PASSED
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
---
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths — Plan 05-01
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|----|----------------------------------------------------------------------------------------------|------------|---------------------------------------------------------------------------------------|
|
||||
| 1 | Uploaded images display correctly in the ImageUpload preview area (not broken/missing) | VERIFIED | Zod schema fix in `schemas.ts` adds `imageFilename` to both item and candidate schemas; static serving at `/uploads/*` via `serveStatic({root:"./"})` and Vite proxy confirmed present |
|
||||
| 2 | Item form shows a full-width 4:3 hero image area at the top of the form | VERIFIED | `ImageUpload` is the first element in `ItemForm` JSX (line 122), component uses `aspect-[4/3]` |
|
||||
| 3 | When no image is set, hero area shows gray background with centered icon and 'Click to add photo' text | VERIFIED | `bg-gray-100` + inline ImagePlus SVG + "Click to add photo" span at lines 90–108 of `ImageUpload.tsx` |
|
||||
| 4 | Clicking the placeholder opens file picker and uploaded image replaces placeholder immediately | VERIFIED | `onClick={() => inputRef.current?.click()}` on hero div; `onChange(result.filename)` updates state on success |
|
||||
| 5 | When image exists, a small circular X button in top-right removes the image | VERIFIED | `absolute top-2 right-2 w-7 h-7 … rounded-full` button calls `handleRemove` → `onChange(null)` with `stopPropagation` |
|
||||
| 6 | Clicking an existing image opens file picker to replace it | VERIFIED | Entire hero div has `onClick` trigger; `value ? <img …> : <placeholder>` branch — img is inside the clickable div |
|
||||
| 7 | CandidateForm has the same hero area redesign as ItemForm | VERIFIED | `<ImageUpload>` is first element in `CandidateForm` JSX (line 138); identical prop wiring |
|
||||
|
||||
### Observable Truths — Plan 05-02
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|----|----------------------------------------------------------------------------------------------|------------|---------------------------------------------------------------------------------------|
|
||||
| 8 | Item cards always show a 4:3 image area, even when no image exists | VERIFIED | `ItemCard.tsx` line 65: unconditional `<div className="aspect-[4/3] bg-gray-50">` |
|
||||
| 9 | Cards without images show a gray placeholder with the item's category emoji centered | VERIFIED | `imageFilename ? <img …> : <div …><span className="text-3xl">{categoryEmoji}</span></div>` |
|
||||
| 10 | Cards with images display the image in the 4:3 area | VERIFIED | `<img src={/uploads/${imageFilename}} alt={name} className="w-full h-full object-cover" />` |
|
||||
| 11 | Candidate cards have the same placeholder treatment as item cards | VERIFIED | `CandidateCard.tsx` lines 35–47 are structurally identical to `ItemCard.tsx` image section |
|
||||
| 12 | Setup item lists show small square thumbnails (~40px) next to item names | VERIFIED | Setup page uses `ItemCard` grid exclusively; each card passes `imageFilename={item.imageFilename}` (line 210), so 4:3 placeholder renders in setup context. Plan explicitly anticipated this case and specified it as acceptable. |
|
||||
| 13 | Setup thumbnails show category emoji placeholder when item has no image | VERIFIED | Same `ItemCard` component — placeholder renders category emoji when `imageFilename` is null |
|
||||
|
||||
**Score:** 13/13 truths verified
|
||||
|
||||
---
|
||||
|
||||
## Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|-----------------------------------------------------|------------------------------------------------------|------------|-----------------------------------------------------------------|
|
||||
| `src/client/components/ImageUpload.tsx` | Hero image area with placeholder, upload, preview, remove | VERIFIED | 147 lines; full implementation with all 4 states: placeholder, preview, uploading spinner, error |
|
||||
| `src/client/components/ItemForm.tsx` | ImageUpload moved to top of form as first element | VERIFIED | `<ImageUpload>` is first element at line 122, before Name field |
|
||||
| `src/client/components/CandidateForm.tsx` | ImageUpload moved to top of form as first element | VERIFIED | `<ImageUpload>` is first element at line 138, before Name field |
|
||||
| `src/client/components/ItemCard.tsx` | Always-visible 4:3 image area with placeholder fallback | VERIFIED | Unconditional `aspect-[4/3]` container with image/emoji conditional |
|
||||
| `src/client/components/CandidateCard.tsx` | Always-visible 4:3 image area with placeholder fallback | VERIFIED | Identical structure to ItemCard |
|
||||
| `src/shared/schemas.ts` | imageFilename field in createItemSchema and createCandidateSchema | VERIFIED | Both schemas have `imageFilename: z.string().optional()` (lines 10, 47) |
|
||||
|
||||
---
|
||||
|
||||
## Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|----------------------------------------------|-----------------------------|--------------------------------------------------|----------|-----------------------------------------------------------------------------------|
|
||||
| `src/client/components/ImageUpload.tsx` | `/api/images` | `apiUpload` call in `handleFileChange` | WIRED | `apiUpload<{filename: string}>("/api/images", file)` at line 35; result.filename fed to `onChange` |
|
||||
| `src/client/components/ItemForm.tsx` | `ImageUpload.tsx` | `<ImageUpload>` at top of form | WIRED | Imported (line 5) and rendered as first element (line 122) with `value` + `onChange` props wired to form state |
|
||||
| `src/client/components/CandidateForm.tsx` | `ImageUpload.tsx` | `<ImageUpload>` at top of form | WIRED | Imported (line 9) and rendered as first element (line 138) with props wired to form state |
|
||||
| `src/client/components/ItemCard.tsx` | `/uploads/{imageFilename}` | `img src` attribute | WIRED | `src={/uploads/${imageFilename}}` at line 68 |
|
||||
| `src/client/components/CandidateCard.tsx` | `/uploads/{imageFilename}` | `img src` attribute | WIRED | `src={/uploads/${imageFilename}}` at line 39 |
|
||||
| `src/client/routes/setups/$setupId.tsx` | `ItemCard.tsx` | `imageFilename={item.imageFilename}` prop | WIRED | Line 210 passes `imageFilename` from setup query result to `ItemCard` |
|
||||
| `src/server/index.ts` | `./uploads/` directory | `serveStatic({ root: "./" })` for `/uploads/*` | WIRED | Line 32: `app.use("/uploads/*", serveStatic({ root: "./" }))` |
|
||||
| `vite.config.ts` | `http://localhost:3000` | Proxy `/uploads` in dev | WIRED | Line 21: `"/uploads": "http://localhost:3000"` in proxy config |
|
||||
|
||||
---
|
||||
|
||||
## Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|-------------|-------------|----------------------------------------------------------------------|-----------|-------------------------------------------------------------------------------|
|
||||
| IMG-01 | 05-01 | User can see uploaded images displayed on item detail views | SATISFIED | Zod schema fix ensures `imageFilename` persists to DB; `ItemCard` renders `/uploads/{filename}` |
|
||||
| IMG-02 | 05-02 | User can see item images on gear collection cards | SATISFIED | `ItemCard` always renders 4:3 image area; images display via `/uploads/` path |
|
||||
| IMG-03 | 05-01 | User sees image preview area at top of item form with placeholder icon when no image is set | SATISFIED | `ImageUpload` renders at top of `ItemForm` and `CandidateForm`; gray placeholder with ImagePlus SVG + "Click to add photo" text |
|
||||
| IMG-04 | 05-01 | User can upload an image by clicking the placeholder area | SATISFIED | Entire hero div is click-to-open-file-picker; `apiUpload` sends to `/api/images`; preview updates on success |
|
||||
|
||||
All 4 requirements satisfied. No orphaned requirements — REQUIREMENTS.md Traceability table maps IMG-01 through IMG-04 to Phase 5, and all are claimed by the two plans.
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns Found
|
||||
|
||||
No anti-patterns detected in modified files.
|
||||
|
||||
| File | Pattern checked | Result |
|
||||
|-------------------------------------------------|-----------------------------------------|--------|
|
||||
| `src/client/components/ImageUpload.tsx` | TODO/FIXME/placeholder comments | None |
|
||||
| `src/client/components/ImageUpload.tsx` | Empty implementations / stubs | None |
|
||||
| `src/client/components/ItemForm.tsx` | TODO/FIXME, return null stubs | None |
|
||||
| `src/client/components/CandidateForm.tsx` | TODO/FIXME, return null stubs | None |
|
||||
| `src/client/components/ItemCard.tsx` | TODO/FIXME, conditional-only rendering | None |
|
||||
| `src/client/components/CandidateCard.tsx` | TODO/FIXME, conditional-only rendering | None |
|
||||
| `src/shared/schemas.ts` | Missing imageFilename fields | None — both schemas include it |
|
||||
|
||||
---
|
||||
|
||||
## Human Verification Required
|
||||
|
||||
### 1. Upload → immediate preview
|
||||
|
||||
**Test:** Open ItemForm, click the gray hero area, select a JPEG file.
|
||||
**Expected:** Hero area immediately shows the uploaded image (no page reload). The X button appears in the top-right corner.
|
||||
**Why human:** Dynamic state update after async upload cannot be verified statically.
|
||||
|
||||
### 2. Remove image
|
||||
|
||||
**Test:** With an image displayed in the ItemForm hero area, click the X button.
|
||||
**Expected:** Hero area reverts to gray placeholder with the ImagePlus icon and "Click to add photo" text. The X button disappears.
|
||||
**Why human:** State transition after user interaction.
|
||||
|
||||
### 3. Image persists after save
|
||||
|
||||
**Test:** Upload an image, fill in a name, click "Add Item". Reopen the item in edit mode.
|
||||
**Expected:** The hero area shows the previously uploaded image (not the placeholder). Confirms the Zod schema fix persists imageFilename through the full create-item API round-trip.
|
||||
**Why human:** End-to-end persistence across API round-trips.
|
||||
|
||||
### 4. Gear collection card consistency
|
||||
|
||||
**Test:** View gear collection with a mix of items (some with images, some without).
|
||||
**Expected:** All cards are the same height due to the always-present 4:3 area. Cards without images show the category emoji centered on a gray background. No layout shift between card types.
|
||||
**Why human:** Visual layout consistency requires visual inspection.
|
||||
|
||||
### 5. Setup page image display
|
||||
|
||||
**Test:** Open a setup that contains both items with images and items without.
|
||||
**Expected:** All ItemCards in the setup grid show consistent heights. Items with images display them; items without show the category emoji placeholder.
|
||||
**Why human:** Visual confirmation in the setup context.
|
||||
|
||||
---
|
||||
|
||||
## Gaps Summary
|
||||
|
||||
No gaps. All 13 observable truths verified, all 5 artifacts substantive and wired, all 8 key links confirmed present in code, all 4 requirements satisfied with evidence.
|
||||
|
||||
The root cause fix (Zod schema missing `imageFilename`) is verified in `src/shared/schemas.ts` with both `createItemSchema` and `createCandidateSchema` now including the field. The server-side persistence chain is complete: Zod allows the field → service layer writes `imageFilename` to DB → GET returns it → cards render `/uploads/{filename}`.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-03-15T17:30:00Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user