Files
taskplaner/.planning/phases/05-search/05-02-PLAN.md
Thomas Richter f6144f4edf docs(05): create phase plan
Phase 05: Search
- 3 plans in 2 waves
- Wave 1: 05-01 (UI components), 05-02 (filtering logic) — parallel
- Wave 2: 05-03 (integration + recent searches + "/" shortcut)
- Ready for execution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:15:38 +01:00

6.3 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
05-search 02 execute 1
src/lib/utils/filterEntries.ts
src/lib/utils/highlightText.ts
true
truths artifacts key_links
Entries can be filtered by text search (title + content)
Entries can be filtered by tag (AND logic for multiple tags)
Entries can be filtered by entry type (task/thought)
Entries can be filtered by date range
Matching text can be highlighted in search results
path provides exports min_lines
src/lib/utils/filterEntries.ts Pure function to filter entries by SearchFilters
filterEntries
30
path provides exports min_lines
src/lib/utils/highlightText.ts Function to wrap matching text in mark tags
highlightText
15
from to via pattern
src/lib/utils/filterEntries.ts SearchFilters type import import.*SearchFilters.*from.*search
from to via pattern
src/lib/utils/highlightText.ts mark element string replacement <mark.*class=.*font-bold
Create the filtering logic and text highlighting utilities.

Purpose: Implement pure functions that filter entries based on SearchFilters criteria and highlight matching text. These are decoupled from UI components for testability. Output: filterEntries.ts and highlightText.ts utilities ready for use in the integrated search view.

<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/ROADMAP.md @.planning/STATE.md @.planning/phases/05-search/05-CONTEXT.md @.planning/phases/05-search/05-RESEARCH.md @src/lib/server/db/schema.ts Task 1: Create filterEntries utility function src/lib/utils/filterEntries.ts Create filterEntries.ts with a pure function that filters entries:
import type { SearchFilters } from '$lib/types/search';
import type { Entry, Tag } from '$lib/server/db/schema';

interface EntryWithTags extends Entry {
  tags: Tag[];
}

export function filterEntries(
  entries: EntryWithTags[],
  filters: SearchFilters
): EntryWithTags[] {
  let result = entries;

  // Text search (title + content) - minimum 2 characters
  if (filters.query.length >= 2) {
    const query = filters.query.toLowerCase();
    result = result.filter(e =>
      (e.title?.toLowerCase().includes(query)) ||
      e.content.toLowerCase().includes(query)
    );
  }

  // Tag filter - AND logic (must have ALL selected tags)
  if (filters.tags.length > 0) {
    result = result.filter(e =>
      filters.tags.every(tagName =>
        e.tags.some(t => t.name.toLowerCase() === tagName.toLowerCase())
      )
    );
  }

  // Type filter
  if (filters.type !== 'all') {
    result = result.filter(e => e.type === filters.type);
  }

  // Date range filter (using createdAt)
  if (filters.dateRange.start) {
    result = result.filter(e => e.createdAt >= filters.dateRange.start!);
  }
  if (filters.dateRange.end) {
    // Include the end date by comparing to end of day
    const endOfDay = filters.dateRange.end + 'T23:59:59';
    result = result.filter(e => e.createdAt <= endOfDay);
  }

  return result;
}

Implementation notes:

  • Case-insensitive text search (lowercase comparison)
  • Case-insensitive tag matching (tags stored with original case but matched insensitively per 04-01 decision)
  • AND logic for tags (every selected tag must be present)
  • Date comparison works with ISO strings (schema uses ISO format)
  • End date is inclusive (compare to end of day) TypeScript compiles: npx tsc --noEmit filterEntries function filters entries by query, tags, type, and date range
Task 2: Create highlightText utility function src/lib/utils/highlightText.ts Create highlightText.ts for search result highlighting:
/**
 * Wraps matching text in <mark> tags for highlighting.
 * Returns HTML string to be used with {@html} in Svelte.
 *
 * @param text - The text to search within
 * @param query - The search query to highlight
 * @returns HTML string with matches wrapped in <mark> tags
 */
export function highlightText(text: string, query: string): string {
  // Don't highlight if query is too short
  if (!query || query.length < 2) return escapeHtml(text);

  // Escape HTML in the original text first
  const escaped = escapeHtml(text);

  // Escape regex special characters in query
  const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const regex = new RegExp(`(${escapedQuery})`, 'gi');

  // Wrap matches in <mark> with bold styling (no background per CONTEXT.md)
  return escaped.replace(regex, '<mark class="font-bold bg-transparent">$1</mark>');
}

/**
 * Escapes HTML special characters to prevent XSS.
 */
function escapeHtml(text: string): string {
  const map: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  return text.replace(/[&<>"']/g, char => map[char]);
}

Security notes:

  • Always escape HTML before highlighting to prevent XSS
  • The escapeHtml function runs BEFORE regex replacement
  • Result is safe to use with {@html} directive

Styling notes:

  • Uses font-bold for emphasis (per CONTEXT.md: "bold, not background highlight")
  • bg-transparent ensures no background color on TypeScript compiles: npx tsc --noEmit highlightText function wraps matching text in styled mark tags with XSS protection
- `npm run check` passes - filterEntries correctly filters by each criterion - highlightText properly escapes HTML and highlights matches

<success_criteria>

  • filterEntries handles all filter types (query, tags, type, dateRange)
  • Tag filtering uses AND logic (entry must have ALL selected tags)
  • Text search is case-insensitive with 2-character minimum
  • highlightText escapes HTML to prevent XSS
  • Highlighted text uses bold styling without background </success_criteria>
After completion, create `.planning/phases/05-search/05-02-SUMMARY.md`