---
phase: 04-tags
plan: 03
type: execute
wave: 2
depends_on: ["04-01"]
files_modified:
  - package.json
  - src/lib/components/TagInput.svelte
  - src/lib/components/EntryCard.svelte
  - src/routes/+page.server.ts
  - src/routes/+page.svelte
autonomous: true

must_haves:
  truths:
    - "User can add multiple tags to an entry"
    - "User can remove tags from an entry"
    - "Tag input shows autocomplete suggestions from existing tags"
    - "Tags are case-insensitive (work = Work = WORK)"
    - "New tags can be created inline during entry editing"
  artifacts:
    - path: "src/lib/components/TagInput.svelte"
      provides: "Multi-select tag input with autocomplete and creation"
      contains: "Svelecte"
    - path: "src/lib/components/EntryCard.svelte"
      provides: "Tag display in collapsed view, TagInput in expanded view"
      contains: "TagInput"
    - path: "src/routes/+page.server.ts"
      provides: "updateTags form action and tags in load data"
      contains: "updateTags"
  key_links:
    - from: "TagInput component"
      to: "availableTags prop"
      via: "passed from page data"
      pattern: "availableTags"
    - from: "EntryCard save"
      to: "?/updateTags action"
      via: "fetch call"
      pattern: "updateTags"
    - from: "+page.server.ts load"
      to: "tagRepository.getByEntryId"
      via: "attach tags to entries"
      pattern: "getByEntryId"
---

<objective>
Add tag input UI with Svelecte for autocomplete and inline tag creation, integrated into EntryCard.

Purpose: Enables TAG-01, TAG-02, TAG-03, TAG-04 requirements - full tag management with user-friendly input
Output: TagInput component with Svelecte, tag display on entries, updateTags form action
</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/ROADMAP.md
@.planning/STATE.md
@.planning/phases/04-tags/04-RESEARCH.md
@.planning/phases/04-tags/04-01-SUMMARY.md

@src/lib/server/db/schema.ts
@src/lib/server/db/repository.ts
@src/lib/components/EntryCard.svelte
@src/routes/+page.server.ts
@src/routes/+page.svelte
</context>

<tasks>

<task type="auto">
  <name>Task 1: Install Svelecte and update load/actions for tags</name>
  <files>package.json, src/routes/+page.server.ts</files>
  <action>
**Install Svelecte:**
```bash
npm install svelecte
```

**Update +page.server.ts:**

1. Import tagRepository:
```typescript
import { entryRepository, imageRepository, tagRepository } from '$lib/server/db/repository';
```

2. Update load function to include tags on entries and all available tags:
```typescript
export const load: PageServerLoad = async ({ url }) => {
  const showCompleted = url.searchParams.get('showCompleted') === 'true';
  const entries = entryRepository.getOrdered({ showCompleted });

  // Attach images AND tags to each entry
  const entriesWithData = entries.map((entry) => ({
    ...entry,
    images: imageRepository.getByEntryId(entry.id),
    tags: tagRepository.getByEntryId(entry.id)
  }));

  // Get all tags for autocomplete
  const allTags = tagRepository.getAll();

  return {
    entries: entriesWithData,
    allTags,
    showCompleted
  };
};
```

3. Add updateTags action:
```typescript
updateTags: async ({ request }) => {
  const formData = await request.formData();
  const id = formData.get('id')?.toString();
  const tagsJson = formData.get('tags')?.toString() || '[]';

  if (!id) {
    return fail(400, { error: 'Entry ID is required' });
  }

  const existing = entryRepository.getById(id);
  if (!existing) {
    return fail(404, { error: 'Entry not found' });
  }

  try {
    const tagNames = JSON.parse(tagsJson) as string[];
    tagRepository.updateEntryTags(id, tagNames);
    return { success: true };
  } catch (e) {
    return fail(400, { error: 'Invalid tags format' });
  }
}
```
  </action>
  <verify>Run `npm run check` - TypeScript compiles. Run `npm run dev` and check console for load data including allTags.</verify>
  <done>Svelecte installed, load returns entries with tags and allTags array, updateTags action processes tag changes</done>
</task>

<task type="auto">
  <name>Task 2: Create TagInput component and integrate into EntryCard</name>
  <files>src/lib/components/TagInput.svelte, src/lib/components/EntryCard.svelte, src/routes/+page.svelte</files>
  <action>
**Create TagInput.svelte:**
```svelte
<script lang="ts">
  import Svelecte from 'svelecte';
  import type { Tag } from '$lib/server/db/schema';

  interface Props {
    availableTags: Tag[];
    selectedTags: Tag[];
    onchange: (tags: Tag[]) => void;
  }

  let { availableTags, selectedTags, onchange }: Props = $props();

  // Transform tags to Svelecte format
  let options = $derived(availableTags.map(t => ({ value: t.id, label: t.name })));
  let value = $derived(selectedTags.map(t => ({ value: t.id, label: t.name })));

  function handleChange(event: CustomEvent) {
    // event.detail is array of {value, label} objects
    // For new tags (creatable), value === label (the text)
    const selected = event.detail || [];

    // Map back to Tag format, handling new tags (where value === label means new)
    const tags: Tag[] = selected.map((item: { value: string; label: string }) => {
      // If value matches an existing tag id, use that tag
      const existing = availableTags.find(t => t.id === item.value);
      if (existing) return existing;

      // Otherwise it's a new tag - create placeholder (will be created on save)
      return {
        id: item.label, // Use label as temp id for new tags
        name: item.label,
        createdAt: new Date().toISOString()
      };
    });

    onchange(tags);
  }
</script>

<Svelecte
  {options}
  {value}
  multiple
  creatable
  valueField="value"
  labelField="label"
  placeholder="Add tags..."
  on:change={handleChange}
  class="tag-input"
/>

<style>
  :global(.tag-input) {
    --sv-bg: white;
    --sv-border: 1px solid #e5e7eb;
    --sv-border-radius: 0.5rem;
    --sv-min-height: 42px;
  }
</style>
```

**Update EntryCard.svelte:**

1. Import TagInput and Tag type:
```typescript
import TagInput from './TagInput.svelte';
import type { Tag } from '$lib/server/db/schema';
```

2. Add availableTags prop to Props interface:
```typescript
interface Props {
  entry: EntryWithImages & { tags: Tag[] };
  availableTags: Tag[];
}

let { entry, availableTags }: Props = $props();
```

3. Add state for tag editing:
```typescript
let editTags = $state<Tag[]>(entry.tags || []);
```

4. Sync tags with entry on collapse:
```typescript
$effect(() => {
  if (!expanded) {
    // ... existing syncs
    editTags = entry.tags || [];
  }
});
```

5. Add function to handle tag changes:
```typescript
async function handleTagsChange(newTags: Tag[]) {
  editTags = newTags;

  // Save immediately (no debounce for tags)
  const formData = new FormData();
  formData.append('id', entry.id);
  formData.append('tags', JSON.stringify(newTags.map(t => t.name)));

  await fetch('?/updateTags', {
    method: 'POST',
    body: formData
  });

  await invalidateAll();
}
```

6. Display tags in collapsed view (after type badge):
```svelte
{#if entry.tags?.length > 0}
  <div class="flex flex-wrap gap-1 mt-1">
    {#each entry.tags.slice(0, 3) as tag}
      <span class="text-xs px-2 py-0.5 bg-gray-100 text-gray-600 rounded-full">{tag.name}</span>
    {/each}
    {#if entry.tags.length > 3}
      <span class="text-xs text-gray-400">+{entry.tags.length - 3}</span>
    {/if}
  </div>
{/if}
```

7. Add TagInput in expanded view (after type selector, before delete button):
```svelte
<div>
  <label class="block text-sm font-medium text-gray-700 mb-1">Tags</label>
  <TagInput
    {availableTags}
    selectedTags={editTags}
    onchange={handleTagsChange}
  />
</div>
```

**Update +page.svelte:**

Pass allTags to EntryList, then from EntryList to EntryCard. Or simpler - pass directly:

1. Update EntryList.svelte to accept and pass availableTags:
```typescript
interface Props {
  entries: EntryWithImages[];
  availableTags: Tag[];
}
let { entries, availableTags }: Props = $props();
```

And pass to each EntryCard:
```svelte
<EntryCard {entry} {availableTags} />
```

2. Update +page.svelte to pass allTags:
```svelte
<EntryList entries={data.entries} availableTags={data.allTags} />
```

Import Tag type if needed for type checking.
  </action>
  <verify>Run `npm run dev`, test:
1. Expand an entry - TagInput appears
2. Type a tag name - autocomplete shows matching existing tags
3. Select existing tag - appears as chip
4. Type new tag and press Enter - tag created
5. Click X on tag chip - tag removed
6. Collapse and expand - tags persist</verify>
  <done>TagInput component with Svelecte works, shows autocomplete, allows creation, displays tags on collapsed entries</done>
</task>

</tasks>

<verification>
1. `npm run check` passes
2. Svelecte package installed and loads correctly
3. Tag autocomplete shows all existing tags
4. User can add tags (existing or new) to entries
5. User can remove tags by clicking X
6. Tags display on collapsed entry cards
7. Case-insensitive: typing "Work" matches existing "work" tag
</verification>

<success_criteria>
- User can add multiple tags to entry via Svelecte input (TAG-01)
- User can remove tags by clicking X on chips (TAG-02)
- Autocomplete shows matching existing tags as user types (TAG-03)
- Existing tags matched case-insensitively (TAG-04)
- Tags visible on collapsed entry cards (max 3 with +N indicator)
</success_criteria>

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