15 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 24-search-history-branching | 03 | execute | 3 |
|
|
false |
|
|
Purpose: Final integration — turns standalone components into a working user experience. Output: Fully functional search, bookmarks, branching, and export features.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/24-search-history-branching/24-RESEARCH.md @.planning/phases/24-search-history-branching/24-00-SUMMARY.md @.planning/phases/24-search-history-branching/24-01-SUMMARY.md @.planning/phases/24-search-history-branching/24-02-SUMMARY.md@ui/src/context/ChatPanelContext.tsx @ui/src/components/ChatPanel.tsx @ui/src/components/ChatMessage.tsx @ui/src/components/ChatMessageActions.tsx @ui/src/components/ChatMessageList.tsx @ui/src/components/ChatConversationList.tsx @ui/src/components/CommandPalette.tsx @ui/src/hooks/useKeyboardShortcuts.ts
ChatSearchDialog: { open, onOpenChange, companyId, onNavigate: (conversationId, messageId) => void } ChatMessageBookmark: { messageId, conversationId, isBookmarked, onToggle } ChatBookmarkList: { companyId, onNavigate: (conversationId, messageId) => void } ChatBranchSelector: { conversationId, branches, activeBranchId, onSelectBranch }useChatSearch(companyId, query) => { data, isLoading } useChatBookmarks(companyId, conversationId?) => { data, isLoading } useToggleBookmark() => { mutate(vars), isPending }
chatApi.branchConversation(conversationId, branchFromMessageId) => ChatConversation chatApi.listBranches(conversationId) => { items: ChatConversation[] } chatApi.exportConversation(conversationId, format) => URL string
POST /conversations/:id/branch { branchFromMessageId } GET /conversations/:id/branches GET /conversations/:id/export?format=markdown|json
Task 1: ChatPanelContext + ChatPanel wiring (search, branch, export, scroll-to) ui/src/context/ChatPanelContext.tsx, ui/src/components/ChatPanel.tsx, ui/src/components/ChatMessageList.tsx, ui/src/components/CommandPalette.tsx, ui/src/components/ChatConversationList.tsx ui/src/context/ChatPanelContext.tsx, ui/src/components/ChatPanel.tsx, ui/src/components/ChatMessageList.tsx, ui/src/components/CommandPalette.tsx, ui/src/components/ChatConversationList.tsx, ui/src/hooks/useKeyboardShortcuts.ts, ui/src/hooks/useChatSearch.ts, ui/src/hooks/useChatBookmarks.ts, ui/src/components/ChatSearchDialog.tsx, ui/src/components/ChatBranchSelector.tsx, ui/src/components/ChatBookmarkList.tsx **ChatPanelContext.tsx — add scrollToMessageId:** - Add to interface: `scrollToMessageId: string | null; setScrollToMessageId: (id: string | null) => void;` - Add state: `const [scrollToMessageId, setScrollToMessageId] = useState(null);` - Add to provider value object**CommandPalette.tsx — add "Search chat messages" item:**
- Add a new `CommandItem` in the existing command list: value "search-chat", label "Search chat messages", icon `Search` from lucide-react
- On select: dispatch custom event `window.dispatchEvent(new CustomEvent("nexus:open-chat-search"))` and close the palette
- This avoids Cmd+K conflict (Pitfall 3 from research) — routes through existing CommandPalette
**ChatPanel.tsx — integrate search dialog, branch selector, export, bookmarks:**
- Add state: `const [searchOpen, setSearchOpen] = useState(false)`
- Listen for `nexus:open-chat-search` custom event: `useEffect` that adds event listener, sets `setSearchOpen(true)`, returns cleanup
- Render `<ChatSearchDialog>` with `onNavigate` that: sets `setActiveConversationId(conversationId)`, then `setScrollToMessageId(messageId)`, closes dialog
- Fetch branches for active conversation: `useQuery(["chat", "branches", activeConversationId], () => chatApi.listBranches(activeConversationId!), { enabled: !!activeConversationId })`
- Render `<ChatBranchSelector>` above `ChatMessageList` when branches exist — `onSelectBranch` calls `setActiveConversationId(branchId)`
- Add export buttons (Markdown/JSON) in the conversation header area (next to title). Use `Download` icon. On click: `window.location.href = chatApi.exportConversation(activeConversationId!, format)`
- Add a bookmarks panel toggle (show/hide `ChatBookmarkList`): small `Bookmark` icon button in header. When active, shows `ChatBookmarkList` in a side panel or below the header
- `ChatBookmarkList` `onNavigate` wired same as search: `setActiveConversationId` + `setScrollToMessageId`
- Modify `handleEdit` callback: When editing a message that already has subsequent messages (assistant reply exists after the edited message), call `chatApi.branchConversation(activeConversationId, messageId)` FIRST, then switch to the new branch via `setActiveConversationId(newConv.id)`, then proceed with the edit on the new branch. This is the branching trigger per CHAT-14.
- After any edit/retry, invalidate `["chat", "search"]` queries (per Pitfall 6)
**ChatMessageList.tsx — scroll-to-message support:**
- Get `scrollToMessageId` and `setScrollToMessageId` from `useChatPanel()`
- When `scrollToMessageId` changes (useEffect): find the message index in the flattened messages array, call `virtualizer.scrollToIndex(index, { align: "center" })`, then `setScrollToMessageId(null)` to reset
- If message not found in current page, this is a best-effort scroll (message may not be loaded yet). Add a TODO comment for infinite-scroll-then-scroll-to in a future iteration.
**ChatConversationList.tsx — branch indicators:**
- Read `parentConversationId` and `branchFromMessageId` from each conversation item
- If `parentConversationId` is set, render a small `GitBranch` icon next to the conversation title
- Optionally indent branch conversations under their parent (client-side grouping: group by parentConversationId, then render parent followed by its children)
cd /opt/nexus && pnpm --filter @paperclipai/ui build 2>&1 | tail -10
- grep -q "scrollToMessageId" ui/src/context/ChatPanelContext.tsx
- grep -q "setScrollToMessageId" ui/src/context/ChatPanelContext.tsx
- grep -q "nexus:open-chat-search" ui/src/components/CommandPalette.tsx
- grep -q "Search chat" ui/src/components/CommandPalette.tsx
- grep -q "ChatSearchDialog" ui/src/components/ChatPanel.tsx
- grep -q "ChatBranchSelector" ui/src/components/ChatPanel.tsx
- grep -q "exportConversation" ui/src/components/ChatPanel.tsx
- grep -q "branchConversation" ui/src/components/ChatPanel.tsx
- grep -q "scrollToMessageId" ui/src/components/ChatMessageList.tsx
- grep -q "scrollToIndex" ui/src/components/ChatMessageList.tsx
- grep -q "GitBranch" ui/src/components/ChatConversationList.tsx
ChatPanelContext has scrollToMessageId. CommandPalette has "Search chat messages" item that dispatches custom event. ChatPanel integrates search dialog, branch selector, export buttons, and bookmark panel. ChatMessageList scrolls to a target message. ChatConversationList shows branch indicator icons. Edit-with-responses triggers branch creation.
Task 2: ChatMessage + ChatMessageActions bookmark integration
ui/src/components/ChatMessage.tsx,
ui/src/components/ChatMessageActions.tsx
ui/src/components/ChatMessage.tsx,
ui/src/components/ChatMessageActions.tsx,
ui/src/components/ChatMessageBookmark.tsx,
ui/src/hooks/useChatBookmarks.ts
**ChatMessageActions.tsx:**
- Add props: `onBookmark?: () => void; isBookmarked?: boolean`
- Import `ChatMessageBookmark` component
- Add `` as the LAST action button (after edit/retry), visible for both user and assistant messages (not system)
- Pass `isBookmarked` and `onToggle={onBookmark}` from props
- Only render bookmark button when `onBookmark` is provided (same pattern as onEdit/onRetry)
**ChatMessage.tsx:**
- Add props: `onBookmark?: (messageId: string) => void; isBookmarked?: boolean`
- Pass `onBookmark={() => id && onBookmark?.(id)}` and `isBookmarked` to `ChatMessageActions`
- Do NOT render bookmark for system messages (messageType checks)
The actual `onBookmark` callback wiring from ChatPanel (calling `useToggleBookmark`) and the `isBookmarked` state (from `useChatBookmarks` data) will be set up in ChatPanel.tsx (Task 1 handles ChatPanel, but the bookmark prop threading from ChatPanel -> ChatMessageList -> ChatMessage needs to be connected).
**In ChatPanel.tsx (addendum to Task 1 wiring):**
- Use `useChatBookmarks(companyId, activeConversationId)` to get bookmark data for active conversation
- Use `useToggleBookmark()` mutation
- Create `bookmarkedMessageIds` Set from bookmark data for O(1) lookup
- Pass `onBookmark={(messageId) => toggleBookmark({ conversationId: activeConversationId!, messageId })}` and `isBookmarked={bookmarkedMessageIds.has(messageId)}` through ChatMessageList to each ChatMessage
- This means ChatMessageList also needs `onBookmark` and `bookmarkedMessageIds` props threaded through
cd /opt/nexus && pnpm --filter @paperclipai/ui build 2>&1 | tail -10
- grep -q "onBookmark" ui/src/components/ChatMessageActions.tsx
- grep -q "isBookmarked" ui/src/components/ChatMessageActions.tsx
- grep -q "ChatMessageBookmark" ui/src/components/ChatMessageActions.tsx
- grep -q "onBookmark" ui/src/components/ChatMessage.tsx
- grep -q "isBookmarked" ui/src/components/ChatMessage.tsx
- grep -q "useToggleBookmark" ui/src/components/ChatPanel.tsx
- grep -q "bookmarkedMessageIds" ui/src/components/ChatPanel.tsx
ChatMessageActions renders a bookmark toggle button for user and assistant messages. ChatMessage threads onBookmark and isBookmarked props. ChatPanel manages bookmark state via useChatBookmarks and useToggleBookmark, passing bookmarkedMessageIds set down through ChatMessageList to each ChatMessage.
Task 3: Verify complete Phase 24 functionality
none
Human verifies all Phase 24 features end-to-end in the browser:
1. Open Nexus in browser. Open the chat panel.
2. Search: Press Cmd+K, find "Search chat messages", select it. Type a term. Verify results with snippets. Click a result — verify navigation and scroll-to-message.
3. Bookmarks: Hover a message, click bookmark icon (fills in). Check bookmarks list in header. Navigate from bookmark. Un-bookmark.
4. Branching: Edit a mid-conversation message. Verify branch created, branch icon in list, branch selector appears. Switch branches.
5. Export: Click export in header. Download Markdown (agent names, not UUIDs). Download JSON (all messages).
Human confirms all 4 features work: search, bookmarks, branching, export.
User types "approved" confirming all Phase 24 success criteria are met.
- `pnpm --filter @paperclipai/ui build` passes
- Cmd+K > "Search chat messages" opens ChatSearchDialog
- Search results navigate to conversation + scroll to message
- Bookmark toggle works on user and assistant messages
- Edit-with-responses creates branch conversation
- Branch selector switches between original and branch
- Export downloads Markdown/JSON with agent names
<success_criteria> All four Phase 24 features are integrated and functional: (1) FTS search via Cmd+K with scroll-to-message navigation, (2) bookmark toggle on messages with bookmark list panel, (3) conversation branching on edit with branch selector UI, (4) export as Markdown/JSON with agent names. Human verification confirms all flows work end-to-end. </success_criteria>
After completion, create `.planning/phases/24-search-history-branching/24-03-SUMMARY.md`