Phase 03: Images - 4 plan(s) in 3 wave(s) - Wave 1: 03-01 (foundation) - Wave 2: 03-02, 03-03 (parallel - upload + camera) - Wave 3: 03-04 (integration + verification) - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-images | 04 | execute | 3 |
|
|
false |
|
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
<execution_context> @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md </execution_context>
@.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`- 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:
<div class="flex gap-2 overflow-x-auto py-2 -mx-2 px-2">
{#each images as image, i}
<div class="relative flex-shrink-0">
<button
type="button"
onclick={() => { lightboxIndex = i; lightboxOpen = true; }}
class="block"
>
<img
src="/api/images/{image.id}/thumbnail"
alt={image.filename}
class="w-20 h-20 object-cover rounded-lg"
/>
</button>
{#if editMode}
<button
type="button"
onclick={() => onDelete?.(image.id)}
class="absolute -top-2 -right-2 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center"
aria-label="Delete image"
>
X
</button>
{/if}
</div>
{/each}
</div>
{#if lightboxOpen}
<ImageLightbox
images={images}
startIndex={lightboxIndex}
onClose={() => lightboxOpen = false}
/>
{/if}
- 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.
- 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
{#if expanded}
<div class="mt-4 pl-9 space-y-3">
<!-- Existing edit fields... -->
<!-- Images section -->
{#if entry.images?.length > 0}
<div>
<div class="flex items-center justify-between mb-2">
<label class="text-sm font-medium text-gray-700">Images</label>
<button
type="button"
onclick={() => editMode = !editMode}
class="text-sm text-blue-600"
>
{editMode ? 'Done' : 'Edit'}
</button>
</div>
<ImageGallery
images={entry.images}
entryId={entry.id}
editMode={editMode}
onDelete={handleDeleteImage}
/>
</div>
{/if}
<!-- Add image section -->
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">Add Image</label>
<div class="flex gap-2">
<ImageUpload entryId={entry.id} onUploadComplete={handleUploadComplete} />
<button
type="button"
onclick={() => showCamera = true}
class="px-4 py-2 border rounded-lg flex items-center gap-2"
>
Camera
</button>
</div>
</div>
<!-- Existing type selector and delete button... -->
</div>
{/if}
{#if showCamera}
<CameraCapture
entryId={entry.id}
onClose={() => 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. Runnpm 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.
-
Import deleteImage from storage:
import { deleteImage as deleteImageFile } from '$lib/server/images/storage' -
Add action:
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 };
}
- Wire up the handler in EntryCard:
async function handleDeleteImage(imageId: string) {
const formData = new FormData();
formData.append('imageId', imageId);
await fetch('?/deleteImage', {
method: 'POST',
body: formData
});
await invalidateAll();
}
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
<success_criteria>
- 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 </success_criteria>