---
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"
---

<objective>
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
</objective>

<execution_context>
@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
@/home/tho/.claude/get-shit-done/templates/summary.md
</execution_context>

<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
</context>

<tasks>

<task type="auto">
  <name>Task 1: ImageGallery and ImageLightbox components</name>
  <files>
    src/lib/components/ImageGallery.svelte
    src/lib/components/ImageLightbox.svelte
  </files>
  <action>
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
<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}
```

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.
  </action>
  <verify>
Run `npm run check` - TypeScript compiles.
Files exist: `ls src/lib/components/Image*.svelte`
  </verify>
  <done>
ImageGallery renders horizontal scrolling thumbnails.
Edit mode shows delete buttons.
Clicking image opens ImageLightbox.
ImageLightbox shows full-size image with close/navigation.
  </done>
</task>

<task type="auto">
  <name>Task 2: Integrate images into EntryCard</name>
  <files>
    src/lib/components/EntryCard.svelte
    src/routes/+page.svelte
  </files>
  <action>
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}
  <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
  </action>
  <verify>
Run `npm run check` - TypeScript compiles.
Run `npm run dev` - verify EntryCard shows images when expanded.
  </verify>
  <done>
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.
  </done>
</task>

<task type="auto">
  <name>Task 3: Delete image form action</name>
  <files>
    src/routes/+page.server.ts
  </files>
  <action>
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();
}
```
  </action>
  <verify>
Run `npm run check` - TypeScript compiles.
Verify action exists: `grep -n "deleteImage:" src/routes/+page.server.ts`
  </verify>
  <done>
deleteImage action removes image from database and filesystem.
EntryCard can delete images via the gallery edit mode.
  </done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>
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
  </what-built>
  <how-to-verify>
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
  </how-to-verify>
  <resume-signal>Type "approved" if all tests pass, or describe issues found</resume-signal>
</task>

</tasks>

<verification>
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
</verification>

<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>

<output>
After completion, create `.planning/phases/03-images/03-04-SUMMARY.md`
</output>
