---
phase: 03-images
plan: 04
type: execute
wave: 3
depends_on: ["03-02", "03-03"]
files_modified:
- src/lib/components/EntryCard.svelte
- src/lib/components/ImageGallery.svelte
- src/lib/components/ImageLightbox.svelte
- src/routes/+page.server.ts
- src/routes/+page.svelte
autonomous: false
must_haves:
truths:
- "User sees thumbnail on entry card when images are attached"
- "User sees horizontal scrolling gallery in expanded entry"
- "User can tap image to view fullscreen in lightbox"
- "User can delete images in edit mode"
- "User can add images to existing entries"
artifacts:
- path: "src/lib/components/EntryCard.svelte"
provides: "Entry card with image thumbnail and gallery"
contains: "ImageGallery"
- path: "src/lib/components/ImageGallery.svelte"
provides: "Horizontal scrolling image gallery"
min_lines: 40
- path: "src/lib/components/ImageLightbox.svelte"
provides: "Fullscreen image viewer"
min_lines: 30
- path: "src/routes/+page.server.ts"
provides: "deleteImage form action"
contains: "deleteImage:"
key_links:
- from: "src/lib/components/EntryCard.svelte"
to: "src/lib/components/ImageGallery.svelte"
via: "component import"
pattern: "import.*ImageGallery"
- from: "src/lib/components/ImageGallery.svelte"
to: "src/lib/components/ImageLightbox.svelte"
via: "lightbox trigger"
pattern: "ImageLightbox"
---
Integrate image display and management into the entry card UI with gallery, lightbox, and delete functionality.
Purpose: Completes the image feature by letting users see their images within entries, view them fullscreen, and manage (add/delete) them.
Output:
- EntryCard shows thumbnail indicator when entry has images
- Expanded entry shows horizontal gallery with all images
- Clicking image opens fullscreen lightbox
- Edit mode shows delete buttons on images
- Upload controls available in expanded entry
@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
@/home/tho/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/phases/03-images/03-RESEARCH.md
@.planning/phases/03-images/03-02-SUMMARY.md
@.planning/phases/03-images/03-03-SUMMARY.md
@src/lib/components/EntryCard.svelte
@src/routes/+page.server.ts
@src/routes/+page.svelte
Task 1: ImageGallery and ImageLightbox components
src/lib/components/ImageGallery.svelte
src/lib/components/ImageLightbox.svelte
1. Install svelte-lightbox: `npm install svelte-lightbox`
2. Create ImageGallery.svelte:
Props:
- images: Array<{ id: string, ext: string, filename: string }>
- entryId: string
- editMode?: boolean (default false)
- onDelete?: (imageId: string) => void
State:
- lightboxOpen: boolean ($state)
- lightboxIndex: number ($state)
Template:
```svelte
{#each images as image, i}
{#if editMode}
{/if}
{/each}
{#if lightboxOpen}
lightboxOpen = false}
/>
{/if}
```
3. Create ImageLightbox.svelte:
Props:
- images: Array<{ id: string, ext: string, filename: string }>
- startIndex: number
- onClose: () => void
Use svelte-lightbox or create simple custom lightbox:
- Full screen overlay (fixed inset-0 bg-black/90 z-50)
- Close button in corner
- Image centered, max-width/max-height to fit
- Left/right navigation if multiple images
- Keyboard support: Escape closes, Arrow keys navigate
- Click outside image closes
- Show image from /api/images/{id} (full size, not thumbnail)
Keep it simple - a modal with the full-size image. Touch gestures (swipe, pinch-zoom) are nice-to-have, not required.
Run `npm run check` - TypeScript compiles.
Files exist: `ls src/lib/components/Image*.svelte`
ImageGallery renders horizontal scrolling thumbnails.
Edit mode shows delete buttons.
Clicking image opens ImageLightbox.
ImageLightbox shows full-size image with close/navigation.
Task 2: Integrate images into EntryCard
src/lib/components/EntryCard.svelte
src/routes/+page.svelte
1. Update +page.svelte to pass images to EntryCard:
- The load function already returns entries with images attached (from 03-02)
- Ensure EntryCard receives entry.images
2. Update EntryCard.svelte:
Add to Props interface:
- entry should include images: Array<{ id: string, ext: string, filename: string }>
Add imports:
- import ImageGallery from './ImageGallery.svelte'
- import ImageUpload from './ImageUpload.svelte'
- import CameraCapture from './CameraCapture.svelte'
Add state:
- showCamera: boolean ($state) - controls camera modal
- editMode: boolean ($state) - toggle for showing delete buttons
In collapsed view (next to title/content):
- If entry.images?.length > 0, show small image indicator
- Could be first thumbnail tiny (24x24) or just an image count badge
In expanded view:
- Add ImageGallery component showing all images
- Add "Edit Images" toggle button to enable delete mode
- Add upload section with:
- ImageUpload component (drag-drop/button)
- Camera button that opens CameraCapture modal
```svelte
{#if expanded}
{#if entry.images?.length > 0}
{/if}
{/if}
{#if showCamera}
showCamera = false}
onCapture={handleUploadComplete}
/>
{/if}
```
Add handler functions:
- handleDeleteImage(imageId: string): calls delete form action
- handleUploadComplete(): calls invalidateAll() to refresh
Run `npm run check` - TypeScript compiles.
Run `npm run dev` - verify EntryCard shows images when expanded.
EntryCard shows image indicator in collapsed view.
Expanded view shows ImageGallery with all images.
Edit mode reveals delete buttons on images.
Upload and camera buttons available for adding images.
Task 3: Delete image form action
src/routes/+page.server.ts
Add deleteImage action to +page.server.ts:
1. Import deleteImage from storage:
`import { deleteImage as deleteImageFile } from '$lib/server/images/storage'`
2. Add action:
```typescript
deleteImage: async ({ request }) => {
const formData = await request.formData();
const imageId = formData.get('imageId')?.toString();
if (!imageId) {
return fail(400, { error: 'Image ID required' });
}
// Get image to find extension for file deletion
const image = imageRepository.getById(imageId);
if (!image) {
return fail(404, { error: 'Image not found' });
}
// Delete files from filesystem
try {
await deleteImageFile(imageId, image.ext);
} catch (err) {
console.error('Failed to delete image files:', err);
// Continue to delete from database even if files missing
}
// Delete from database
imageRepository.delete(imageId);
return { success: true };
}
```
3. Wire up the handler in EntryCard:
```typescript
async function handleDeleteImage(imageId: string) {
const formData = new FormData();
formData.append('imageId', imageId);
await fetch('?/deleteImage', {
method: 'POST',
body: formData
});
await invalidateAll();
}
```
Run `npm run check` - TypeScript compiles.
Verify action exists: `grep -n "deleteImage:" src/routes/+page.server.ts`
deleteImage action removes image from database and filesystem.
EntryCard can delete images via the gallery edit mode.
Complete image attachment feature:
- File upload via drag-drop and button
- Camera capture on mobile
- Thumbnail display in entry cards
- Horizontal gallery in expanded view
- Fullscreen lightbox viewer
- Delete images in edit mode
1. Start dev server: `npm run dev`
2. Open http://localhost:5173 in browser
Test file upload (desktop):
- Create a new entry
- Expand the entry
- Drag an image onto the upload zone OR click to select file
- Verify image appears in gallery after upload
- Verify thumbnail shows in collapsed entry view
Test camera capture (use mobile device or browser with webcam):
- Expand an entry
- Click "Camera" button
- Grant camera permission when prompted
- Verify live camera preview appears
- Take a photo, verify preview shows
- Try "Retake" - should return to live camera
- Click "Use Photo" - verify image uploads and appears in gallery
Test image viewing:
- Click on a thumbnail in the gallery
- Verify lightbox opens with full-size image
- Close lightbox (click X or outside image)
Test image deletion:
- Expand entry with images
- Click "Edit" button above gallery
- Verify delete buttons appear on images
- Delete an image
- Verify image removed from gallery
- Verify files deleted: check data/uploads/ directory
Test error cases:
- Try camera on desktop without webcam - should show friendly error
- Try uploading non-image file - should show error
Type "approved" if all tests pass, or describe issues found
1. `npm run check` passes
2. `npm run dev` - server starts
3. All components exist:
- src/lib/components/ImageGallery.svelte
- src/lib/components/ImageLightbox.svelte
4. EntryCard imports and uses image components
5. Human verification of complete image flow
- Entry cards show image indicator when images attached
- Expanded entry shows horizontal scrolling gallery
- Clicking image opens fullscreen lightbox
- Edit mode allows deleting images
- Upload and camera available for adding images to existing entries
- Human verified: complete image flow works on desktop and mobile