--- 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 After completion, create `.planning/phases/03-images/03-04-SUMMARY.md`