+ ```
+ 8. Export the searchInputRef via an imperative handle: Add `useImperativeHandle` from React. Change the component to use `forwardRef`. Define the handle interface:
+ ```typescript
+ export interface ChatConversationListHandle {
+ focusSearch: () => void;
+ }
+ ```
+ In `useImperativeHandle(ref, () => ({ focusSearch: () => searchInputRef.current?.focus() }))`.
+
+ **ui/src/hooks/useKeyboardShortcuts.ts** — Add onSearch handler for Cmd+K:
+ 1. Add `onSearch?: () => void` to the `ShortcutHandlers` interface
+ 2. Add a new handler block BEFORE the existing shortcut checks (Cmd+K uses metaKey/ctrlKey, so it won't conflict with the input-guard since Cmd+K is a global shortcut that should work even from inputs):
+ ```typescript
+ // Cmd+K / Ctrl+K → Search (global, works even from inputs)
+ if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ onSearch?.();
+ return;
+ }
+ ```
+ Place this check BEFORE the `if (target.tagName === "INPUT" ...)` early return, so Cmd+K fires even when focused in an input/textarea.
+ 3. Add `onSearch` to the useEffect dependency array
+
+ **ui/src/components/Layout.tsx** — Wire Cmd+K to focus chat search:
+ 1. Add `import { useRef } from "react"` (add useRef to the existing React import)
+ 2. Add `import type { ChatConversationListHandle } from "./ChatConversationList"`
+ 3. Create a ref: `const chatSearchRef = useRef(null)`
+ 4. Add `onSearch` to the `useKeyboardShortcuts` call:
+ ```typescript
+ useKeyboardShortcuts({
+ onNewIssue: () => openNewIssue(),
+ onToggleSidebar: toggleSidebar,
+ onTogglePanel: togglePanel,
+ onSearch: () => {
+ if (!chatOpen) setChatOpen(true);
+ // Use requestAnimationFrame to ensure panel is visible before focusing
+ requestAnimationFrame(() => chatSearchRef.current?.focusSearch());
+ },
+ });
+ ```
+ 5. Pass the ref down: The ChatConversationList is rendered inside ChatPanel, which is rendered inside Layout. The simplest approach is to expose a `searchRef` prop on ChatPanel and pass it through.
+
+ ALTERNATIVE (simpler): Instead of threading refs, add a dedicated `useEffect` in `ChatConversationList` that listens for a custom event:
+ - In ChatConversationList, add: `useEffect(() => { const handler = () => searchInputRef.current?.focus(); window.addEventListener("nexus:focus-chat-search", handler); return () => window.removeEventListener("nexus:focus-chat-search", handler); }, []);`
+ - In Layout's onSearch callback: `if (!chatOpen) setChatOpen(true); requestAnimationFrame(() => window.dispatchEvent(new Event("nexus:focus-chat-search")));`
+ - This avoids drilling refs through ChatPanel. Use this approach.
+
+ With the custom event approach, you do NOT need chatSearchRef, ChatConversationListHandle, or forwardRef. Simplify:
+ - ChatConversationList: do NOT use forwardRef or useImperativeHandle. Just add the event listener useEffect with searchInputRef.
+ - Layout: just dispatch the event in onSearch.
+ - useKeyboardShortcuts: add onSearch to interface and handler as described above.
+
+
+ cd /opt/nexus && pnpm --filter @paperclipai/ui exec -- tsc --noEmit 2>&1 | tail -5; echo "---"; pnpm vitest run ui/src/components/ChatInput.test.tsx 2>&1 | tail -5
+
+
+ - ChatConversationList renders an `` with placeholder "Search conversations..."
+ - The search input has a Search icon on the left and a clear (X) button when non-empty
+ - Typing in the search input debounces at 300ms then passes the search term to useChatConversations
+ - useKeyboardShortcuts has an `onSearch` handler that fires on Cmd+K (metaKey+k) or Ctrl+K (ctrlKey+k)
+ - The Cmd+K handler fires even when focus is in an input or textarea (it is checked before the input-guard early return)
+ - Layout.tsx wires onSearch to open the chat panel (if closed) and dispatch "nexus:focus-chat-search" event
+ - ChatConversationList listens for "nexus:focus-chat-search" and focuses the search input
+ - TypeScript compilation passes with no new errors
+ - Existing ChatInput tests still pass
+
+ Users can type in the search input to filter conversations by title. Cmd+K (Mac) or Ctrl+K (other) opens the chat panel if needed and focuses the search input. Clearing the search restores the full list.
+
+
+
+
+
+1. TypeScript: `pnpm --filter @paperclipai/server exec -- tsc --noEmit` and `pnpm --filter @paperclipai/ui exec -- tsc --noEmit` — no new errors in chat files
+2. Existing tests: `pnpm vitest run server/src/__tests__/chat-service.test.ts server/src/__tests__/chat-routes.test.ts ui/src/components/ChatMarkdownMessage.test.tsx ui/src/components/ChatInput.test.tsx` — all pass
+3. Search input visible in ChatConversationList (grep for `Search conversations` in ChatConversationList.tsx)
+4. Cmd+K handler present (grep for `metaKey.*ctrlKey.*k` in useKeyboardShortcuts.ts)
+5. Server route passes search param (grep for `search` in server/src/routes/chat.ts GET handler)
+
+
+
+- HIST-02 gap closed: conversation list is searchable via a search input with server-side ilike filtering on title; agentId filter parameter accepted by service and route
+- INPUT-07 gap closed: Cmd+K / Ctrl+K keyboard shortcut opens chat panel and focuses the search input
+- All existing tests continue to pass
+- TypeScript compilation clean
+
+
+