---
phase: 05-search
plan: 03
type: execute
wave: 2
depends_on: ["05-01", "05-02"]
files_modified:
  - src/routes/+page.svelte
  - src/lib/components/EntryList.svelte
  - src/lib/components/EntryCard.svelte
  - src/lib/stores/recentSearches.ts
autonomous: true

must_haves:
  truths:
    - "User can search entries by typing in search bar"
    - "User can filter entries using filter controls"
    - "Search results show matching text highlighted with bold"
    - "Pinned entries appear in flat list during search/filter (not separated)"
    - "Empty state shows friendly message when no matches"
    - "Pressing '/' key focuses search input"
    - "Recent searches appear as quick picks"
    - "Clear button resets all filters"
  artifacts:
    - path: "src/routes/+page.svelte"
      provides: "Integrated search UI at page level"
      contains: "SearchBar"
    - path: "src/lib/components/EntryList.svelte"
      provides: "Filtered entry rendering with flat list mode"
      contains: "filterEntries"
    - path: "src/lib/components/EntryCard.svelte"
      provides: "Highlighted text display"
      contains: "highlightText"
    - path: "src/lib/stores/recentSearches.ts"
      provides: "Persisted recent searches store"
      exports: ["recentSearches", "addRecentSearch"]
  key_links:
    - from: "src/routes/+page.svelte"
      to: "src/lib/components/SearchBar.svelte"
      via: "component import"
      pattern: "import SearchBar"
    - from: "src/lib/components/EntryList.svelte"
      to: "src/lib/utils/filterEntries.ts"
      via: "$derived filtering"
      pattern: "filterEntries.*filters"
    - from: "src/lib/components/EntryCard.svelte"
      to: "src/lib/utils/highlightText.ts"
      via: "{@html} rendering"
      pattern: "\\{@html.*highlightText"
---

<objective>
Integrate search components, filtering logic, and highlighting into the main page.

Purpose: Wire together SearchBar, FilterBar, filterEntries, and highlightText to create a complete search experience. Add recent searches persistence and "/" keyboard shortcut.
Output: Fully functional search and filtering on the main entries page.
</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/05-search/05-CONTEXT.md
@.planning/phases/05-search/05-RESEARCH.md
@.planning/phases/05-search/05-01-SUMMARY.md
@.planning/phases/05-search/05-02-SUMMARY.md
@src/routes/+page.svelte
@src/lib/components/EntryList.svelte
@src/lib/components/EntryCard.svelte
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create recent searches store and add "/" keyboard shortcut to SearchBar</name>
  <files>src/lib/stores/recentSearches.ts, src/lib/components/SearchBar.svelte</files>
  <action>
1. Create src/lib/stores/recentSearches.ts:

```typescript
import { persisted } from 'svelte-persisted-store';

const MAX_RECENT = 5;

export const recentSearches = persisted<string[]>('taskplaner-recent-searches', []);

export function addRecentSearch(query: string): void {
  if (!query || query.length < 2) return;

  recentSearches.update(searches => {
    // Remove if already exists (will re-add at front)
    const filtered = searches.filter(s => s.toLowerCase() !== query.toLowerCase());
    // Add to front, limit to MAX_RECENT
    return [query, ...filtered].slice(0, MAX_RECENT);
  });
}
```

2. Update SearchBar.svelte to add:
   - "/" keyboard shortcut using native document.addEventListener in onMount
   - Skip shortcut if already in input/textarea (check e.target)
   - e.preventDefault() to prevent "/" from being typed
   - Recent searches dropdown (show when input focused and empty)
   - Clicking a recent search fills the input
   - Call addRecentSearch when search is executed (on blur with value >= 2 chars)

Implementation notes for "/" shortcut:
- Use native document.addEventListener (NOT svelte:window) to avoid known Svelte 5 bug
- Return cleanup function from onMount to remove listener
- Check if target is HTMLInputElement or HTMLTextAreaElement before handling

Recent searches UI:
- Show dropdown below input when: focused AND inputValue is empty AND recentSearches.length > 0
- List shows "Recent searches" header + up to 5 items
- onmousedown (not onclick) to fire before onblur
  </action>
  <verify>Pressing "/" focuses search when not in an input field; recent searches appear on focus</verify>
  <done>Search input has "/" shortcut and shows recent searches dropdown</done>
</task>

<task type="auto">
  <name>Task 2: Integrate search/filter into EntryList with flat list mode</name>
  <files>src/lib/components/EntryList.svelte</files>
  <action>
Modify EntryList.svelte to:

1. Accept new props:
```typescript
interface Props {
  entries: EntryWithData[];
  availableTags: Tag[];
  filters: SearchFilters;         // NEW
  searchQuery: string;            // NEW - for highlighting
}
```

2. Apply filtering using $derived:
```typescript
import { filterEntries } from '$lib/utils/filterEntries';
import { hasActiveFilters } from '$lib/types/search';

let filteredEntries = $derived(filterEntries(entries, filters));
let isFiltering = $derived(hasActiveFilters(filters));
```

3. Modify display logic:
- When NOT filtering: Keep current pinned/unpinned separation
- When filtering: Flat list (no pinned section), just render all filteredEntries
- Pass searchQuery to each EntryCard for highlighting

4. Update empty state:
- When no entries at all: "No entries yet" (existing)
- When filtering with no results: "No entries match your search" (new)

Implementation:
```svelte
{#if filteredEntries.length === 0}
  {#if isFiltering}
    <div class="text-center py-12 text-gray-500">
      <p class="text-lg">No entries match your search</p>
      <p class="text-sm mt-1">Try adjusting your filters or search term</p>
    </div>
  {:else}
    <!-- existing empty state -->
  {/if}
{:else if isFiltering}
  <!-- Flat list when filtering -->
  <div class="divide-y divide-gray-100 md:divide-y-0 md:space-y-3">
    {#each filteredEntries as entry (entry.id)}
      <EntryCard {entry} {availableTags} {searchQuery} />
    {/each}
  </div>
{:else}
  <!-- Pinned/unpinned separation when not filtering -->
  <!-- existing code -->
{/if}
```
  </action>
  <verify>EntryList filters entries and shows flat list during search</verify>
  <done>EntryList applies filters via $derived and displays flat list when filtering is active</done>
</task>

<task type="auto">
  <name>Task 3: Add highlighting to EntryCard and integrate search UI in +page.svelte</name>
  <files>src/lib/components/EntryCard.svelte, src/routes/+page.svelte</files>
  <action>
1. Update EntryCard.svelte:
   - Add new prop: searchQuery: string (default '')
   - Import highlightText from '$lib/utils/highlightText'
   - Use {@html highlightText(entry.title, searchQuery)} for title display
   - Use {@html highlightText(entry.content, searchQuery)} for content preview
   - Only apply highlighting in collapsed view (where preview text shows)

```typescript
interface Props {
  entry: EntryWithData;
  availableTags: Tag[];
  searchQuery?: string;  // NEW, optional with default ''
}

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

For title (collapsed view):
```svelte
<h3 class="...">
  {@html highlightText(entry.title || 'Untitled', searchQuery)}
</h3>
```

For content preview (collapsed view):
```svelte
<p class="...">
  {@html highlightText(entry.content, searchQuery)}
</p>
```

2. Update src/routes/+page.svelte:
   - Import SearchBar, FilterBar from components
   - Import SearchFilters, defaultFilters from types/search
   - Import recentSearches, addRecentSearch from stores
   - Add filter state with $state(defaultFilters)
   - Add searchQuery state bound to SearchBar
   - Add FilterBar with onchange handler
   - Place SearchBar above EntryList (visible at top, in header or just below)
   - Place FilterBar below SearchBar
   - Pass filters and searchQuery to EntryList
   - Handle onSearch callback to add to recent searches

Layout in +page.svelte:
```svelte
<header class="bg-white border-b border-gray-200 sticky top-0 z-10">
  <div class="max-w-2xl mx-auto px-4 py-4">
    <div class="flex items-center justify-between mb-3">
      <h1 class="text-xl font-bold text-gray-900">TaskPlaner</h1>
      <CompletedToggle />
    </div>
    <SearchBar bind:value={searchQuery} onSearch={handleSearch} {recentSearches} onSelectRecent={handleSelectRecent} />
  </div>
</header>

<div class="max-w-2xl mx-auto px-4">
  <FilterBar
    {filters}
    availableTags={data.allTags}
    onchange={(f) => filters = f}
  />
</div>

<div class="max-w-2xl mx-auto px-4 py-4">
  <EntryList
    entries={data.entries}
    availableTags={data.allTags}
    {filters}
    {searchQuery}
  />
</div>
```
  </action>
  <verify>Full search flow works: type in search, see highlighted results, use filters</verify>
  <done>Search and filters are fully integrated with highlighting in search results</done>
</task>

</tasks>

<verification>
- `npm run check` passes
- `npm run dev` starts and search/filter UI is visible
- Typing in search filters entries (after 2 chars, debounced)
- Matching text is highlighted with bold
- Pressing "/" focuses search input (when not already in input)
- Recent searches appear on focus
- Filters work correctly (type, tags, date range)
- Clear button resets all filters
- Pinned entries appear in flat list during search (no separation)
- Empty state shows "No entries match your search" when filtering yields nothing
</verification>

<success_criteria>
- Search bar visible at top of page
- "/" key focuses search input
- Text search filters entries with 2-char minimum and debounce
- Recent searches show as quick picks (last 5)
- Type filter toggles between All/Tasks/Thoughts
- Tag filter with AND logic (multiple tags)
- Date range with presets and custom option
- Clear button appears when filters active
- Matching text highlighted with bold (not background)
- Flat list during search (no pinned separation)
- Friendly empty state message
</success_criteria>

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