--- phase: 05-search plan: 02 type: execute wave: 1 depends_on: [] files_modified: - src/lib/utils/filterEntries.ts - src/lib/utils/highlightText.ts autonomous: true must_haves: truths: - "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" artifacts: - path: "src/lib/utils/filterEntries.ts" provides: "Pure function to filter entries by SearchFilters" exports: ["filterEntries"] min_lines: 30 - path: "src/lib/utils/highlightText.ts" provides: "Function to wrap matching text in mark tags" exports: ["highlightText"] min_lines: 15 key_links: - from: "src/lib/utils/filterEntries.ts" to: "SearchFilters type" via: "import" pattern: "import.*SearchFilters.*from.*search" - from: "src/lib/utils/highlightText.ts" to: "mark element" via: "string replacement" pattern: " 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. @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md @.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: ```typescript 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: ```typescript /** * Wraps matching text in 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 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 with bold styling (no background per CONTEXT.md) return escaped.replace(regex, '$1'); } /** * Escapes HTML special characters to prevent XSS. */ function escapeHtml(text: string): string { const map: Record = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; 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 - 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 After completion, create `.planning/phases/05-search/05-02-SUMMARY.md`