---
phase: 24-search-history-branching
plan: 02
subsystem: ui
tags: [react, tanstack-query, cmdk, lucide-react, typescript]

# Dependency graph
requires:
  - phase: 24-00
    provides: shared types (ChatMessageSearchResult, ChatBookmarkWithMessage, ChatBookmarkToggleResponse, ChatBookmarkListResponse, ChatConversation with branch fields)

provides:
  - chatApi.searchMessages — FTS endpoint client method
  - chatApi.toggleBookmark — bookmark toggle client method
  - chatApi.getBookmarks — bookmark list client method
  - chatApi.branchConversation — branch creation client method
  - chatApi.listBranches — branch list client method
  - chatApi.exportConversation — export download URL helper
  - useChatSearch hook — debounced FTS query hook with placeholderData
  - useChatBookmarks hook — bookmark list query hook
  - useToggleBookmark hook — bookmark mutation with cache invalidation
  - ChatSearchDialog component — CommandDialog FTS overlay with term highlighting
  - ChatMessageBookmark component — bookmark toggle icon button
  - ChatBookmarkList component — filterable scrollable bookmark list
  - ChatBranchSelector component — horizontal branch picker bar

affects: [24-03]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "useChatSearch: placeholderData=(prev)=>prev keeps previous results while new query loads"
    - "ChatSearchDialog: shouldFilter=false on Command for server-side FTS (not cmdk client filtering)"
    - "HighlightedText: React component splits text into segments to avoid unsafe innerHTML XSS risk"
    - "useToggleBookmark: invalidates both [chat, bookmarks] and [chat, search] queries after toggle"

key-files:
  created:
    - ui/src/hooks/useChatSearch.ts
    - ui/src/hooks/useChatBookmarks.ts
    - ui/src/components/ChatSearchDialog.tsx
    - ui/src/components/ChatMessageBookmark.tsx
    - ui/src/components/ChatBookmarkList.tsx
    - ui/src/components/ChatBranchSelector.tsx
  modified:
    - ui/src/api/chat.ts

key-decisions:
  - "Used HighlightedText React component instead of innerHTML for term highlighting — eliminates XSS surface"
  - "exportConversation returns URL string (not fetch call) since server sends file download via browser navigation"
  - "ChatBranchSelector renders null when branches.length === 0 — no empty bar shown"

patterns-established:
  - "shouldFilter=false pattern: all server-side search dialogs set this to bypass cmdk client filtering"
  - "Cache invalidation pattern: bookmark mutations invalidate both bookmarks and search query caches"

requirements-completed: [CHAT-07, CHAT-13, CHAT-14, HIST-04, HIST-12, PERF-04]

# Metrics
duration: 3min
completed: 2026-04-01
---

# Phase 24 Plan 02: Search History Branching UI Summary

**Six chatApi methods, two React Query hooks, and four components — search/bookmark/branch UI layer built independently from server routes, ready for wiring in Plan 03.**

## Performance

- **Duration:** ~3 min
- **Started:** 2026-04-01T22:29:56Z
- **Completed:** 2026-04-01T22:32:55Z
- **Tasks:** 2 completed
- **Files modified:** 7

## Accomplishments

- Extended `chatApi` in `ui/src/api/chat.ts` with six new methods covering search, bookmarks, branches, and export
- Created `useChatSearch` and `useChatBookmarks`/`useToggleBookmark` TanStack Query hooks with proper cache invalidation
- Built four UI components: `ChatSearchDialog` (CommandDialog FTS overlay), `ChatMessageBookmark` (toggle icon button), `ChatBookmarkList` (scrollable list with skeletons/empty state), `ChatBranchSelector` (horizontal branch picker)
- UI build passes cleanly with no TypeScript errors

## Task Commits

Each task was committed atomically:

1. **Task 1: API client methods and React Query hooks** - `e1ab0ca0` (feat)
2. **Task 2: UI components** - `11145afe` (feat)

**Plan metadata:** (pending — docs commit)

## Files Created/Modified

- `ui/src/api/chat.ts` — Added searchMessages, toggleBookmark, getBookmarks, branchConversation, listBranches, exportConversation methods; added shared type imports
- `ui/src/hooks/useChatSearch.ts` — TanStack Query hook for debounced FTS; enabled when query >= 2 chars; placeholderData keeps previous results during load; 30s staleTime
- `ui/src/hooks/useChatBookmarks.ts` — useChatBookmarks query hook + useToggleBookmark mutation; invalidates both bookmarks and search caches on success
- `ui/src/components/ChatSearchDialog.tsx` — CommandDialog-based FTS overlay; shouldFilter=false for server-side search; HighlightedText component splits text into marked segments without XSS risk
- `ui/src/components/ChatMessageBookmark.tsx` — Ghost icon button (h-6 w-6 / h-3.5 w-3.5) matching ChatMessageActions sizing; fill-current on bookmarked state; aria-label toggles
- `ui/src/components/ChatBookmarkList.tsx` — Scrollable list using useChatBookmarks; skeleton loading placeholders (matching ChatConversationList pattern); Bookmark icon empty state
- `ui/src/components/ChatBranchSelector.tsx` — Horizontal bar with GitBranch icon; "Original" button for parent conv; branch buttons with bg-accent for active; renders null when no branches

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Security] Replaced innerHTML approach with React HighlightedText component for term highlighting**
- **Found during:** Task 2 implementation
- **Issue:** Security hook flagged setting innerHTML for XSS risk when highlighting matched terms
- **Fix:** Extracted HighlightedText React component that splits text into plain/highlighted segments using regex, rendering mark elements directly without setting innerHTML
- **Files modified:** ui/src/components/ChatSearchDialog.tsx
- **Commit:** 11145afe

## Known Stubs

None. All components accept callback props (onNavigate, onToggle, onSelectBranch) — no routing or state is hardcoded inside. Data fetching is wired to real chatApi endpoints. Plan 03 will integrate these into ChatPanel.

## Self-Check: PASSED

All 7 files confirmed present on disk. Both task commits (e1ab0ca0, 11145afe) confirmed in git log.
